Conversation
With code splitting, a dynamic import() of a CommonJS module that is also an entry point resolves to that chunk's namespace object, which nests the CommonJS exports object on "default". Property accesses like "mod.foo" then silently return undefined at run-time, while the same import() works both unbundled and when bundled without code splitting (where esbuild wraps the require call with __toESM). Wrap such cross-chunk dynamic imports with a new __toDynamicImportESM runtime helper that expands the chunk's default export into an ESM namespace via __toESM. The "default" property still points at the CommonJS exports object exactly as before, so existing code that reaches through "mod.default" keeps working; named accesses now work too, matching the behavior of import() without code splitting.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4377 (analysis in this comment).
Problem
With
--splitting, a dynamicimport()of a CommonJS module that is also an entry point resolves to the chunk's namespace object, which nests the CommonJS exports object ondefault. Named accesses likemod.foosilently returnundefinedat run-time, while the same code works unbundled and when bundled without code splitting:Without splitting, the same
import()prints asPromise.resolve().then(() => __toESM(require_impl())), so the two modes disagree about the namespace shape.Fix
The linker now flags cross-chunk dynamic imports whose target is a CommonJS entry point (
WrapDynamicImportWithToESM), and the printer appends a call to a new parameterless runtime helper:A helper is used instead of an inline
.then((m) => __toESM(m.default))because a printed parameter name could collide with minified runtime symbols; a bare identifier goes through the renamer and is collision-free (verified under--minify, where it prints as.then(e())).The
isNodeModeargument mirrors the existing behavior of the non-splitting path (p.moduleType.IsESM()), so the split and non-split namespaces match in the Babel-style__esModulecase too.Compatibility
__toESMkeepsdefaultpointing at the CommonJS exports object, so existing code that adapted to the old shape withmod.default.fookeeps working — the existing end-to-end test assertingns2.default.foopasses unchanged. Onlyimport()records are affected; static cross-chunk imports, external imports, andrequire()are untouched.Tests
TestSplittingDynamicCommonJSIntoES6,TestSplittingDynamicAndNotDynamicCommonJSIntoES6,TestGlobBasicSplitting,TestTSGlobBasicSplitting(glob dynamic imports of CJS get the same treatment), plus hash-only churn inTestSplittingHybridESMAndCJSIssue617from the runtime edit.mod.foo()andmod.default.foo()), and a Babel-style__esModulemodule asserting parity with the non-splitting output.go test ./...andnode scripts/end-to-end-tests.jspass (the only failures on this machine are pre-existing environment issues: a Windows symlink-permission test and a Node 26 async-iterator behavior change, both identical on an unmodified tree).Disclosure: this PR was written with AI assistance (Claude Code); the analysis and output were verified end-to-end locally as described above.