Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions internal/bundler_tests/bundler_importstar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1810,6 +1810,31 @@ entry-nope.js: WARNING: Import "nope" will always be undefined because the file
})
}

// https://github.com/evanw/esbuild/issues/4440 — repeated `require()` of a
// bundled ESM module must return the same wrapper object so that identity
// matches Node.js CJS cache semantics. The inline-require path uses
// "__toCommonJSCached" so the two `require()` calls below produce a single
// wrapper.
func TestRequireOfESMPreservesIdentity(t *testing.T) {
importstar_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
const a = require('./esm')
const b = require('./esm')
console.log(a === b)
`,
"/esm.js": `
export const value = 123
`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/out.js",
},
})
}

// Failure case due to a bug in https://github.com/evanw/esbuild/pull/2059
func TestReExportStarEntryPointAndInnerFile(t *testing.T) {
importstar_suite.expectBundled(t, bundled{
Expand Down
2 changes: 1 addition & 1 deletion internal/bundler_tests/snapshots/snapshots_dce.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3002,7 +3002,7 @@ var init_cjs = __esm({

// entry.js
init_lib();
console.log(keep1(), (init_cjs(), __toCommonJS(cjs_exports)));
console.log(keep1(), (init_cjs(), __toCommonJSCached(cjs_exports)));

================================================================================
TestTreeShakingJSWithAssociatedCSS
Expand Down
14 changes: 7 additions & 7 deletions internal/bundler_tests/snapshots/snapshots_default.txt
Original file line number Diff line number Diff line change
Expand Up @@ -793,9 +793,9 @@ var init_bar = __esm({
});

// entry.js
var { foo: foo2 } = (init_foo(), __toCommonJS(foo_exports));
var { foo: foo2 } = (init_foo(), __toCommonJSCached(foo_exports));
console.log(foo2(), bar2());
var { bar: bar2 } = (init_bar(), __toCommonJS(bar_exports));
var { bar: bar2 } = (init_bar(), __toCommonJSCached(bar_exports));

================================================================================
TestConditionalImport
Expand Down Expand Up @@ -1230,7 +1230,7 @@ var init_types = __esm({
});

// entry.js
console.log((init_types(), __toCommonJS(types_exports)));
console.log((init_types(), __toCommonJSCached(types_exports)));

================================================================================
TestEntryNamesChunkNamesExtPlaceholder
Expand Down Expand Up @@ -1593,7 +1593,7 @@ var init_lib = __esm({
});

// entry.js
var lib = (init_lib(), __toCommonJS(lib_exports));
var lib = (init_lib(), __toCommonJSCached(lib_exports));
console.log(lib.__proto__);

================================================================================
Expand Down Expand Up @@ -3858,7 +3858,7 @@ var require_cjs = __commonJS({
// entry-cjs.js
var require_entry_cjs = __commonJS({
"entry-cjs.js"(exports) {
var { b: esm_foo_2 } = (init_esm(), __toCommonJS(esm_exports));
var { b: esm_foo_2 } = (init_esm(), __toCommonJSCached(esm_exports));
var { a: cjs_foo_ } = require_cjs();
exports.c = [
esm_foo_2,
Expand Down Expand Up @@ -6561,7 +6561,7 @@ var require_es6_import_stmt = __commonJS({
// es6-import-assign.ts
var require_es6_import_assign = __commonJS({
"es6-import-assign.ts"(exports) {
var x2 = (init_dummy(), __toCommonJS(dummy_exports));
var x2 = (init_dummy(), __toCommonJSCached(dummy_exports));
console.log(exports);
}
});
Expand Down Expand Up @@ -6757,7 +6757,7 @@ console.log(void 0);
var import_es6_export_assign = __toESM(require_es6_export_assign());

// es6-export-import-assign.ts
var x = (init_dummy(), __toCommonJS(dummy_exports));
var x = (init_dummy(), __toCommonJSCached(dummy_exports));
console.log(void 0);

// entry.js
Expand Down
24 changes: 22 additions & 2 deletions internal/bundler_tests/snapshots/snapshots_importstar.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ var foo;
var init_entry = __esm({
"entry.js"() {
foo = 123;
console.log((init_entry(), __toCommonJS(entry_exports)));
console.log((init_entry(), __toCommonJSCached(entry_exports)));
}
});
init_entry();
Expand Down Expand Up @@ -459,7 +459,7 @@ var init_foo = __esm({

// entry.js
init_foo();
var ns2 = (init_foo(), __toCommonJS(foo_exports));
var ns2 = (init_foo(), __toCommonJSCached(foo_exports));
console.log(foo, ns2.foo);

================================================================================
Expand Down Expand Up @@ -1039,3 +1039,23 @@ var x = 1;

// entry.js
console.log(x);

================================================================================
TestRequireOfESMPreservesIdentity
---------- /out.js ----------
// esm.js
var esm_exports = {};
__export(esm_exports, {
value: () => value
});
var value;
var init_esm = __esm({
"esm.js"() {
value = 123;
}
});

// entry.js
var a = (init_esm(), __toCommonJSCached(esm_exports));
var b = (init_esm(), __toCommonJSCached(esm_exports));
console.log(a === b);
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var init_foo = __esm({

// entry.js
init_foo();
var ns2 = (init_foo(), __toCommonJS(foo_exports));
var ns2 = (init_foo(), __toCommonJSCached(foo_exports));
console.log(foo, ns2.foo);

================================================================================
Expand Down
4 changes: 2 additions & 2 deletions internal/bundler_tests/snapshots/snapshots_packagejson.txt
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ var init_module = __esm({
});

// Users/user/project/src/test-main.js
console.log((init_module(), __toCommonJS(module_exports)));
console.log((init_module(), __toCommonJSCached(module_exports)));

// Users/user/project/src/test-module.js
init_module();
Expand Down Expand Up @@ -542,7 +542,7 @@ var init_module = __esm({
});

// Users/user/project/src/test-index.js
console.log((init_module(), __toCommonJS(module_exports)));
console.log((init_module(), __toCommonJSCached(module_exports)));

// Users/user/project/src/test-module.js
init_module();
Expand Down
12 changes: 6 additions & 6 deletions internal/bundler_tests/snapshots/snapshots_splitting.txt
Original file line number Diff line number Diff line change
Expand Up @@ -370,26 +370,26 @@ TestSplittingHybridESMAndCJSIssue617
import {
foo,
init_a
} from "./chunk-PDZFCFBH.js";
} from "./chunk-GBWBY5I7.js";
init_a();
export {
foo
};

---------- /out/b.js ----------
import {
__toCommonJS,
__toCommonJSCached,
a_exports,
init_a
} from "./chunk-PDZFCFBH.js";
} from "./chunk-GBWBY5I7.js";

// b.js
var bar = (init_a(), __toCommonJS(a_exports));
var bar = (init_a(), __toCommonJSCached(a_exports));
export {
bar
};

---------- /out/chunk-PDZFCFBH.js ----------
---------- /out/chunk-GBWBY5I7.js ----------
// a.js
var a_exports = {};
__export(a_exports, {
Expand All @@ -402,7 +402,7 @@ var init_a = __esm({
});

export {
__toCommonJS,
__toCommonJSCached,
foo,
a_exports,
init_a
Expand Down
8 changes: 6 additions & 2 deletions internal/js_printer/js_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -1583,10 +1583,13 @@ func (p *printer) printRequireOrImportExpr(importRecordIndex uint32, level js_as
p.print(",")
p.printSpace()

// Wrap this with a call to "__toCommonJS()" if this is an ESM file
// Wrap this with a call to "__toCommonJSCached()" if this is an ESM
// file. The cached variant is used (rather than "__toCommonJS") so
// that two require() calls for the same bundled ESM module return
// the same wrapper object, matching Node.js CJS cache semantics.
wrapWithTpCJS := record.Flags.Has(ast.WrapWithToCJS)
if wrapWithTpCJS {
p.printIdentifier(p.renamer.NameForSymbol(p.options.ToCommonJSRef))
p.printIdentifier(p.renamer.NameForSymbol(p.options.ToCommonJSCachedRef))
p.print("(")
}
p.printIdentifier(p.renamer.NameForSymbol(meta.ExportsRef))
Expand Down Expand Up @@ -4929,6 +4932,7 @@ type Options struct {
LineOffsetTables []sourcemap.LineOffsetTable

ToCommonJSRef ast.Ref
ToCommonJSCachedRef ast.Ref
ToESMRef ast.Ref
RuntimeRequireRef ast.Ref
UnsupportedFeatures compat.JSFeature
Expand Down
11 changes: 9 additions & 2 deletions internal/linker/linker.go
Original file line number Diff line number Diff line change
Expand Up @@ -1979,8 +1979,11 @@ func (c *linkerContext) scanImportsAndExports() {
c.graph.GenerateRuntimeSymbolImportAndUse(sourceIndex, uint32(partIndex), "__toESM", toESMUses)

// If there's a CommonJS require of an ES6 module, then we're going to need the
// "__toCommonJS" symbol from the runtime to wrap the exports object
c.graph.GenerateRuntimeSymbolImportAndUse(sourceIndex, uint32(partIndex), "__toCommonJS", toCommonJSUses)
// "__toCommonJSCached" symbol from the runtime to wrap the exports object.
// The cached variant is used here (rather than "__toCommonJS") so that
// repeated require() of the same ESM module returns the same wrapper,
// matching Node.js CJS cache semantics.
c.graph.GenerateRuntimeSymbolImportAndUse(sourceIndex, uint32(partIndex), "__toCommonJSCached", toCommonJSUses)

// If there are unbundled calls to "require()" and we're not generating
// code for node, then substitute a "__require" wrapper for "require".
Expand Down Expand Up @@ -4659,6 +4662,7 @@ func (c *linkerContext) generateCodeForFileInChunkJS(
waitGroup *sync.WaitGroup,
partRange partRange,
toCommonJSRef ast.Ref,
toCommonJSCachedRef ast.Ref,
toESMRef ast.Ref,
runtimeRequireRef ast.Ref,
result *compileResultJS,
Expand Down Expand Up @@ -4957,6 +4961,7 @@ func (c *linkerContext) generateCodeForFileInChunkJS(
LineLimit: c.options.LineLimit,
ASCIIOnly: c.options.ASCIIOnly,
ToCommonJSRef: toCommonJSRef,
ToCommonJSCachedRef: toCommonJSCachedRef,
ToESMRef: toESMRef,
RuntimeRequireRef: runtimeRequireRef,
TSEnums: c.graph.TSEnums,
Expand Down Expand Up @@ -5577,6 +5582,7 @@ func (c *linkerContext) generateChunkJS(chunkIndex int, chunkWaitGroup *sync.Wai
compileResults := make([]compileResultJS, 0, len(chunkRepr.partsInChunkInOrder))
runtimeMembers := c.graph.Files[runtime.SourceIndex].InputFile.Repr.(*graph.JSRepr).AST.ModuleScope.Members
toCommonJSRef := ast.FollowSymbols(c.graph.Symbols, runtimeMembers["__toCommonJS"].Ref)
toCommonJSCachedRef := ast.FollowSymbols(c.graph.Symbols, runtimeMembers["__toCommonJSCached"].Ref)
toESMRef := ast.FollowSymbols(c.graph.Symbols, runtimeMembers["__toESM"].Ref)
runtimeRequireRef := ast.FollowSymbols(c.graph.Symbols, runtimeMembers["__require"].Ref)
r := c.renameSymbolsInChunk(chunk, chunkRepr.filesInChunkInOrder, timer)
Expand Down Expand Up @@ -5609,6 +5615,7 @@ func (c *linkerContext) generateChunkJS(chunkIndex int, chunkWaitGroup *sync.Wai
&waitGroup,
partRange,
toCommonJSRef,
toCommonJSCachedRef,
toESMRef,
runtimeRequireRef,
compileResult,
Expand Down
11 changes: 11 additions & 0 deletions internal/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,17 @@ func Source(unsupportedJSFeatures compat.JSFeature) logger.Source {
// to "true", which overwrites any existing export named "__esModule".
export var __toCommonJS = mod => __copyProps(__defProp({}, '__esModule', { value: true }), mod)

// Same conversion as __toCommonJS but memoized. Used for inline
// "require()" of a bundled ESM module, where the call is emitted at
// every require site; without the cache, two require() calls for the
// same module would return distinct objects, breaking Node.js CJS
// cache semantics. Falls back to no cache when WeakMap is unavailable.
export var __toCommonJSCached = /* @__PURE__ */ (cache => (mod, result) => (
cache && (result = cache.get(mod)) || (
result = __toCommonJS(mod), cache && cache.set(mod, result)),
result
))(typeof WeakMap !== 'undefined' ? new WeakMap : 0)

// For TypeScript experimental decorators
// - kind === undefined: class
// - kind === 1: method, parameter
Expand Down