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
- Upgrade rolldown to the latest version; wrapper-creation gaps for dynamic entries are patched upstream.
- Workaround: avoid dynamic-importing a barrel/re-export module directly — import a concrete module instead.
- Temporarily disable consumer-local re-export / order-wrap experimental options if enabled.
- 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
- Dynamically import concrete modules, never re-export barrels
- Keep experimental order-wrap/consumer-local flags off in production builds until stable
- Add dynamic-entry bundling to CI to catch wrapper regressions after upgrades
- Run builds in isolated worker threads to contain panics
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
- importee chunk should exist
- CJS entry should have a wrapper
- pre-chunk order CJS carrier should exist
- Should have pre_rendered_chunk
- consumer-local route should have complete namespace targets
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)