rolldown/rolldown · critical

dynamic-entry module target should have a wrapper

Error message

dynamic-entry module target should have a wrapper

What it means

Rolldown panics with 'dynamic-entry module target should have a wrapper' while computing chunk imports. For a dynamic entry's ESM init target, `order_state.esm_init_target(module_idx, meta)` must yield a target carrying a `wrapper_ref` (the symbol that lazily initializes the dynamic entry's ESM namespace). The panic means an init target was recorded in the order state without its wrapper being created — order-wrap metadata is incomplete when the cross-chunk import graph is built.

Solutions

  1. Upgrade rolldown to the latest version; wrapper-creation gaps for dynamic entries are patched upstream.
  2. Workaround: avoid dynamic-importing a barrel/re-export module directly — import a concrete module instead.
  3. Temporarily disable consumer-local re-export / order-wrap experimental options if enabled.
  4. Contributors: compare where `esm_init_target`s are registered in the order pass against where `wrapper_ref` is assigned to find the missing path.

Example fix

// before (panic)
let wrapper_ref = match target {
  WrappedEsmInitTarget::Module(module_idx) => {
    order_state
      .esm_init_target(module_idx, &self.link_output.metas[module_idx])
      .expect("dynamic-entry module target should have a wrapper")
      .wrapper_ref
  }
  // ...
};
// after (defensive library fix)
let wrapper_ref = match target {
  WrappedEsmInitTarget::Module(module_idx) => {
    match order_state.esm_init_target(module_idx, &self.link_output.metas[module_idx]) {
      Some(t) => t.wrapper_ref,
      None => continue,
    }
  }
  // ...
};
Defensive patterns

Strategy: try-catch

Validate before calling

// Prefer dynamic-importing concrete modules over barrels
function isBarrelImport(spec) {
  return /(^|\/)index(\.js)?$/.test(spec) && !spec.includes('entry');
}
if (dynamicImports.some(isBarrelImport)) console.warn('dynamic import targets a barrel — may hit wrapper bugs');

Type guard

fn has_wrapper(state: &OrderWrapState, idx: ModuleIdx, meta: &LinkingMetadata) -> bool {
  state.esm_init_target(idx, meta).map(|t| !t.wrapper_ref.is_synthetic()).unwrap_or(false)
}

Try / catch

try {
  await bundle.generate();
} catch (err) {
  if (String(err).includes('dynamic-entry') && String(err).includes('wrapper')) {
    // fallback: disable experimental order-wrap options and retry
    return rebuild({ experimental: {} });
  }
  throw err;
}

Prevention

When it happens

Trigger: In `compute_chunk_imports` (from `compute_cross_chunk_link_state`): iterating `consumer_local_namespace_targets`, a `WrappedEsmInitTarget::Module(module_idx)` resolves via `esm_init_target(...)` to `None`, or the companion `CjsCarrier` lookup misses. Triggered by dynamic `import()` entries participating in consumer-local re-export routes under code splitting — an internal bookkeeping bug.

Common situations: Hit with apps that dynamically import entry points re-exported through barrels (consumer-local namespace targets), typically under experimental order-wrap/code-splitting flags after a rolldown upgrade that altered when wrappers are created for dynamic entries.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of rolldown/rolldown@91b44b9d7b (2026-09-07). Data as JSON: /api/errors/78368177cd1e34ca. Report an issue: GitHub.

Appendix: source

Thrown at crates/rolldown/src/stages/generate_stage/compute_cross_chunk_links.rs:1361

            }
            let meta = &self.link_output.metas[*dynamic_entry_module];
            if matches!(meta.wrap_kind(), WrapKind::Cjs) {
              // For CJS modules, export only wrapper_ref (require_xxx)
              // Generated code: `import('./chunk.js').then((n) => __toESM(n.require_xxx()))`
              if let Some(wrapper_ref) = meta.wrapper_ref {
                index_chunk_exported_symbols[chunk_id].entry(wrapper_ref).or_default();
              }
            } else if let Some(targets) =
              order_state.consumer_local_namespace_targets(*dynamic_entry_module)
            {
              // A consumer-local namespace is activated by its complete leaf/carrier target list,
              // never by the intentionally empty shared barrel wrapper.
              for &target in targets {
                let wrapper_ref = match target {
                  WrappedEsmInitTarget::Module(module_idx) => {
                    order_state
                      .esm_init_target(module_idx, &self.link_output.metas[module_idx])
                      .expect("dynamic-entry module target should have a wrapper")
                      .wrapper_ref
                  }
                  WrappedEsmInitTarget::CjsCarrier(key) => {
                    order_state
                      .order_cjs_carrier(key)
                      .expect("dynamic-entry CJS carrier should have a wrapper")
                      .wrapper_ref
                  }
                };
                index_chunk_exported_symbols[chunk_id].entry(wrapper_ref).or_default();
              }
              let ns_ref = self.link_output.module_table[*dynamic_entry_module]
                .namespace_object_ref()
                .expect("dynamic entry should be normal module");
              index_chunk_exported_symbols[chunk_id].entry(ns_ref).or_default();
            } else if let Some(target) = order_state.esm_init_target(*dynamic_entry_module, meta) {
              // For ESM modules, export both wrapper_ref (init_xxx) and namespace
              // Generated code: `import('./chunk.js').then((n) => (n.init_xxx(), n.namespace))`

View on GitHub (pinned to 91b44b9d7b)