Skip to content

Commit a599d45

Browse files
authored
Move module.exports assigment to the top (#2059)
1 parent 73a539d commit a599d45

File tree

8 files changed

+177
-57
lines changed

8 files changed

+177
-57
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@
88

99
Previously esbuild always created the forwarding `default` import in Babel mode, even if `module.exports` had no property called `default`. This was problematic because the getter named `default` still showed up as a property on the imported namespace object, and could potentially interfere with code that iterated over the properties of the imported namespace object. With this release the getter named `default` will now only be added in Babel mode if the `default` property exists at the time of the import.
1010

11+
* Fix a circular import edge case regarding ESM-to-CommonJS conversion ([#1894](https://github.com/evanw/esbuild/issues/1894), [#2059](https://github.com/evanw/esbuild/pull/2059))
12+
13+
This fixes a regression that was introduced in version 0.14.5 of esbuild. Ever since that version, esbuild now creates two separate export objects when you convert an ES module file into a CommonJS module: one for ES modules and one for CommonJS modules. The one for CommonJS modules is written to `module.exports` and exported from the file, and the one for ES modules is internal and can be accessed by bundling code that imports the entry point (for example, the entry point might import itself to be able to inspect its own exports).
14+
15+
The reason for these two separate export objects is that CommonJS modules are supposed to see a special export called `__esModule` which indicates that the module used to be an ES module, while ES modules are not supposed to see any automatically-added export named `__esModule`. This matters for real-world code both because people sometimes iterate over the properties of ES module export namespace objects and because some people write ES module code containing their own exports named `__esModule` that they expect other ES module code to be able to read.
16+
17+
However, this change to split exports into two separate objects broke ES module re-exports in the edge case where the imported module is involved in an import cycle. This happened because the CommonJS `module.exports` object was no longer mutated as exports were added. Instead it was being initialized at the end of the generated file after the import statements to other modules (which are converted into `require()` calls). This release changes `module.exports` initialization to happen earlier in the file and then double-writes further exports to both the ES module and CommonJS module export objects.
18+
19+
This fix was contributed by [@indutny](https://github.com/indutny).
20+
1121
## 0.14.26
1222

1323
* Fix a tree shaking regression regarding `var` declarations ([#2080](https://github.com/evanw/esbuild/issues/2080), [#2085](https://github.com/evanw/esbuild/pull/2085), [#2098](https://github.com/evanw/esbuild/issues/2098), [#2099](https://github.com/evanw/esbuild/issues/2099))

internal/bundler/bundler_importstar_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1805,3 +1805,33 @@ entry-nope.js: WARNING: Import "nope" will always be undefined because the file
18051805
`,
18061806
})
18071807
}
1808+
1809+
// Failure case due to a bug in https://github.com/evanw/esbuild/pull/2059
1810+
func TestReExportStarEntryPointAndInnerFile(t *testing.T) {
1811+
importstar_suite.expectBundled(t, bundled{
1812+
files: map[string]string{
1813+
"/entry.js": `
1814+
export * from 'a'
1815+
import * as inner from './inner.js'
1816+
export { inner }
1817+
`,
1818+
"/inner.js": `
1819+
export * from 'b'
1820+
`,
1821+
},
1822+
entryPaths: []string{"/entry.js"},
1823+
options: config.Options{
1824+
Mode: config.ModeBundle,
1825+
AbsOutputDir: "/out",
1826+
OutputFormat: config.FormatCommonJS,
1827+
ExternalSettings: config.ExternalSettings{
1828+
PreResolve: config.ExternalMatchers{
1829+
Exact: map[string]bool{
1830+
"a": true,
1831+
"b": true,
1832+
},
1833+
},
1834+
},
1835+
},
1836+
})
1837+
}

internal/bundler/linker.go

Lines changed: 59 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2024,6 +2024,31 @@ func (c *linkerContext) createExportsForFile(sourceIndex uint32) {
20242024
repr.AST.UsesExportsRef = true
20252025
}
20262026

2027+
// Decorate "module.exports" with the "__esModule" flag to indicate that
2028+
// we used to be an ES module. This is done by wrapping the exports object
2029+
// instead of by mutating the exports object because other modules in the
2030+
// bundle (including the entry point module) may do "import * as" to get
2031+
// access to the exports object and should NOT see the "__esModule" flag.
2032+
if repr.Meta.ForceIncludeExportsForEntryPoint &&
2033+
c.options.OutputFormat == config.FormatCommonJS {
2034+
2035+
runtimeRepr := c.graph.Files[runtime.SourceIndex].InputFile.Repr.(*graph.JSRepr)
2036+
toCommonJSRef := runtimeRepr.AST.NamedExports["__toCommonJS"].Ref
2037+
2038+
// "module.exports = __toCommonJS(exports);"
2039+
nsExportStmts = append(nsExportStmts, js_ast.AssignStmt(
2040+
js_ast.Expr{Data: &js_ast.EDot{
2041+
Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: c.unboundModuleRef}},
2042+
Name: "exports",
2043+
}},
2044+
2045+
js_ast.Expr{Data: &js_ast.ECall{
2046+
Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: toCommonJSRef}},
2047+
Args: []js_ast.Expr{{Data: &js_ast.EIdentifier{Ref: repr.AST.ExportsRef}}},
2048+
}},
2049+
))
2050+
}
2051+
20272052
// No need to generate a part if it'll be empty
20282053
if len(nsExportStmts) > 0 {
20292054
// Initialize the part that was allocated for us earlier. The information
@@ -3492,6 +3517,21 @@ func (c *linkerContext) convertStmtsForChunk(sourceIndex uint32, stmtList *stmtL
34923517
repr := file.InputFile.Repr.(*graph.JSRepr)
34933518
shouldExtractESMStmtsForWrap := repr.Meta.Wrap != graph.WrapNone
34943519

3520+
// If this file is a CommonJS entry point, double-write re-exports to the
3521+
// external CommonJS "module.exports" object in addition to our internal ESM
3522+
// export namespace object. The difference between these two objects is that
3523+
// our internal one must not have the "__esModule" marker while the external
3524+
// one must have the "__esModule" marker. This is done because an ES module
3525+
// importing itself should not see the "__esModule" marker but a CommonJS module
3526+
// importing us should see the "__esModule" marker.
3527+
var moduleExportsForReExportOrNil js_ast.Expr
3528+
if c.options.OutputFormat == config.FormatCommonJS && file.IsEntryPoint() {
3529+
moduleExportsForReExportOrNil = js_ast.Expr{Data: &js_ast.EDot{
3530+
Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: c.unboundModuleRef}},
3531+
Name: "exports",
3532+
}}
3533+
}
3534+
34953535
for _, stmt := range partStmts {
34963536
switch s := stmt.Data.(type) {
34973537
case *js_ast.SImport:
@@ -3547,16 +3587,20 @@ func (c *linkerContext) convertStmtsForChunk(sourceIndex uint32, stmtList *stmtL
35473587
ImportRecordIndex: s.ImportRecordIndex,
35483588
}
35493589

3550-
// Prefix this module with "__reExport(exports, ns)"
3590+
// Prefix this module with "__reExport(exports, ns, module.exports)"
35513591
exportStarRef := c.graph.Files[runtime.SourceIndex].InputFile.Repr.(*graph.JSRepr).AST.ModuleScope.Members["__reExport"].Ref
3592+
args := []js_ast.Expr{
3593+
{Loc: stmt.Loc, Data: &js_ast.EIdentifier{Ref: repr.AST.ExportsRef}},
3594+
{Loc: stmt.Loc, Data: &js_ast.EIdentifier{Ref: s.NamespaceRef}},
3595+
}
3596+
if moduleExportsForReExportOrNil.Data != nil {
3597+
args = append(args, moduleExportsForReExportOrNil)
3598+
}
35523599
stmtList.insideWrapperPrefix = append(stmtList.insideWrapperPrefix, js_ast.Stmt{
35533600
Loc: stmt.Loc,
35543601
Data: &js_ast.SExpr{Value: js_ast.Expr{Loc: stmt.Loc, Data: &js_ast.ECall{
35553602
Target: js_ast.Expr{Loc: stmt.Loc, Data: &js_ast.EIdentifier{Ref: exportStarRef}},
3556-
Args: []js_ast.Expr{
3557-
{Loc: stmt.Loc, Data: &js_ast.EIdentifier{Ref: repr.AST.ExportsRef}},
3558-
{Loc: stmt.Loc, Data: &js_ast.EIdentifier{Ref: s.NamespaceRef}},
3559-
},
3603+
Args: args,
35603604
}}},
35613605
})
35623606

@@ -3579,25 +3623,29 @@ func (c *linkerContext) convertStmtsForChunk(sourceIndex uint32, stmtList *stmtL
35793623
var target js_ast.E
35803624
if record.SourceIndex.IsValid() {
35813625
if otherRepr := c.graph.Files[record.SourceIndex.GetIndex()].InputFile.Repr.(*graph.JSRepr); otherRepr.AST.ExportsKind == js_ast.ExportsESMWithDynamicFallback {
3582-
// Prefix this module with "__reExport(exports, otherExports)"
3626+
// Prefix this module with "__reExport(exports, otherExports, module.exports)"
35833627
target = &js_ast.EIdentifier{Ref: otherRepr.AST.ExportsRef}
35843628
}
35853629
}
35863630
if target == nil {
3587-
// Prefix this module with "__reExport(exports, require(path))"
3631+
// Prefix this module with "__reExport(exports, require(path), module.exports)"
35883632
target = &js_ast.ERequireString{
35893633
ImportRecordIndex: s.ImportRecordIndex,
35903634
}
35913635
}
35923636
exportStarRef := c.graph.Files[runtime.SourceIndex].InputFile.Repr.(*graph.JSRepr).AST.ModuleScope.Members["__reExport"].Ref
3637+
args := []js_ast.Expr{
3638+
{Loc: stmt.Loc, Data: &js_ast.EIdentifier{Ref: repr.AST.ExportsRef}},
3639+
{Loc: record.Range.Loc, Data: target},
3640+
}
3641+
if moduleExportsForReExportOrNil.Data != nil {
3642+
args = append(args, moduleExportsForReExportOrNil)
3643+
}
35933644
stmtList.insideWrapperPrefix = append(stmtList.insideWrapperPrefix, js_ast.Stmt{
35943645
Loc: stmt.Loc,
35953646
Data: &js_ast.SExpr{Value: js_ast.Expr{Loc: stmt.Loc, Data: &js_ast.ECall{
35963647
Target: js_ast.Expr{Loc: stmt.Loc, Data: &js_ast.EIdentifier{Ref: exportStarRef}},
3597-
Args: []js_ast.Expr{
3598-
{Loc: stmt.Loc, Data: &js_ast.EIdentifier{Ref: repr.AST.ExportsRef}},
3599-
{Loc: record.Range.Loc, Data: target},
3600-
},
3648+
Args: args,
36013649
}}},
36023650
})
36033651
}
@@ -4097,25 +4145,6 @@ func (c *linkerContext) generateEntryPointTailJS(
40974145
Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: repr.AST.WrapperRef}},
40984146
}}}})
40994147
}
4100-
4101-
// Decorate "module.exports" with the "__esModule" flag to indicate that
4102-
// we used to be an ES module. This is done by wrapping the exports object
4103-
// instead of by mutating the exports object because other modules in the
4104-
// bundle (including the entry point module) may do "import * as" to get
4105-
// access to the exports object and should NOT see the "__esModule" flag.
4106-
if repr.Meta.ForceIncludeExportsForEntryPoint {
4107-
// "module.exports = __toCommonJS(exports);"
4108-
stmts = append(stmts, js_ast.AssignStmt(
4109-
js_ast.Expr{Data: &js_ast.EDot{
4110-
Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: c.unboundModuleRef}},
4111-
Name: "exports",
4112-
}},
4113-
js_ast.Expr{Data: &js_ast.ECall{
4114-
Target: js_ast.Expr{Data: &js_ast.EIdentifier{Ref: toCommonJSRef}},
4115-
Args: []js_ast.Expr{{Data: &js_ast.EIdentifier{Ref: repr.AST.ExportsRef}}},
4116-
}},
4117-
))
4118-
}
41194148
}
41204149

41214150
// If we are generating CommonJS for node, encode the known export names in

internal/bundler/snapshots/snapshots_default.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -857,8 +857,8 @@ TestExportWildcardFSNodeCommonJS
857857
---------- /out.js ----------
858858
// entry.js
859859
var entry_exports = {};
860-
__reExport(entry_exports, require("fs"));
861860
module.exports = __toCommonJS(entry_exports);
861+
__reExport(entry_exports, require("fs"), module.exports);
862862

863863
================================================================================
864864
TestExportWildcardFSNodeES6
@@ -2440,11 +2440,11 @@ __export(entry_exports, {
24402440
bar: () => import_bar.default,
24412441
foo: () => import_foo.default
24422442
});
2443+
module.exports = __toCommonJS(entry_exports);
24432444
var import_foo = __toESM(require("foo"));
24442445

24452446
// bar.js
24462447
var import_bar = __toESM(require("bar"));
2447-
module.exports = __toCommonJS(entry_exports);
24482448

24492449
================================================================================
24502450
TestReExportDefaultExternalES6
@@ -2486,9 +2486,9 @@ __export(entry_exports, {
24862486
bar: () => import_bar.default,
24872487
foo: () => import_foo.default
24882488
});
2489+
module.exports = __toCommonJS(entry_exports);
24892490
var import_foo = __toESM(require("./foo"));
24902491
var import_bar = __toESM(require("./bar"));
2491-
module.exports = __toCommonJS(entry_exports);
24922492

24932493
================================================================================
24942494
TestReExportDefaultNoBundleES6

internal/bundler/snapshots/snapshots_importstar.txt

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ var entry_exports = {};
1212
__export(entry_exports, {
1313
ns: () => ns
1414
});
15-
var ns = __toESM(require_foo());
1615
module.exports = __toCommonJS(entry_exports);
16+
var ns = __toESM(require_foo());
1717

1818
================================================================================
1919
TestExportOtherCommonJS
@@ -30,8 +30,8 @@ var entry_exports = {};
3030
__export(entry_exports, {
3131
bar: () => import_foo.bar
3232
});
33-
var import_foo = __toESM(require_foo());
3433
module.exports = __toCommonJS(entry_exports);
34+
var import_foo = __toESM(require_foo());
3535

3636
================================================================================
3737
TestExportOtherNestedCommonJS
@@ -48,10 +48,10 @@ var entry_exports = {};
4848
__export(entry_exports, {
4949
y: () => import_foo.x
5050
});
51+
module.exports = __toCommonJS(entry_exports);
5152

5253
// bar.js
5354
var import_foo = __toESM(require_foo());
54-
module.exports = __toCommonJS(entry_exports);
5555

5656
================================================================================
5757
TestExportSelfAndImportSelfCommonJS
@@ -61,9 +61,9 @@ var entry_exports = {};
6161
__export(entry_exports, {
6262
foo: () => foo
6363
});
64+
module.exports = __toCommonJS(entry_exports);
6465
var foo = 123;
6566
console.log(entry_exports);
66-
module.exports = __toCommonJS(entry_exports);
6767

6868
================================================================================
6969
TestExportSelfAndRequireSelfCommonJS
@@ -73,6 +73,7 @@ var entry_exports = {};
7373
__export(entry_exports, {
7474
foo: () => foo
7575
});
76+
module.exports = __toCommonJS(entry_exports);
7677
var foo;
7778
var init_entry = __esm({
7879
"entry.js"() {
@@ -81,7 +82,6 @@ var init_entry = __esm({
8182
}
8283
});
8384
init_entry();
84-
module.exports = __toCommonJS(entry_exports);
8585

8686
================================================================================
8787
TestExportSelfAsNamespaceCommonJS
@@ -92,8 +92,8 @@ __export(entry_exports, {
9292
foo: () => foo,
9393
ns: () => entry_exports
9494
});
95-
var foo = 123;
9695
module.exports = __toCommonJS(entry_exports);
96+
var foo = 123;
9797

9898
================================================================================
9999
TestExportSelfAsNamespaceES6
@@ -118,8 +118,8 @@ var entry_exports = {};
118118
__export(entry_exports, {
119119
foo: () => foo
120120
});
121-
var foo = 123;
122121
module.exports = __toCommonJS(entry_exports);
122+
var foo = 123;
123123

124124
================================================================================
125125
TestExportSelfCommonJSMinified
@@ -169,10 +169,10 @@ var entry_exports = {};
169169
__export(entry_exports, {
170170
foo: () => foo
171171
});
172+
module.exports = __toCommonJS(entry_exports);
172173

173174
// foo.js
174175
var foo = "foo";
175-
module.exports = __toCommonJS(entry_exports);
176176

177177
================================================================================
178178
TestImportDefaultNamespaceComboIssue446
@@ -275,8 +275,8 @@ var entry_exports = {};
275275
__export(entry_exports, {
276276
ns: () => ns
277277
});
278-
var ns = __toESM(require_foo());
279278
module.exports = __toCommonJS(entry_exports);
279+
var ns = __toESM(require_foo());
280280

281281
================================================================================
282282
TestImportExportSelfAsNamespaceES6
@@ -869,8 +869,8 @@ var entry_exports = {};
869869
__export(entry_exports, {
870870
out: () => out
871871
});
872-
var out = __toESM(require("foo"));
873872
module.exports = __toCommonJS(entry_exports);
873+
var out = __toESM(require("foo"));
874874

875875
================================================================================
876876
TestReExportStarAsES6NoBundle
@@ -888,8 +888,8 @@ var entry_exports = {};
888888
__export(entry_exports, {
889889
out: () => out
890890
});
891-
var out = __toESM(require("foo"));
892891
module.exports = __toCommonJS(entry_exports);
892+
var out = __toESM(require("foo"));
893893

894894
================================================================================
895895
TestReExportStarAsExternalES6
@@ -929,21 +929,36 @@ var mod = (() => {
929929
TestReExportStarCommonJSNoBundle
930930
---------- /out.js ----------
931931
var entry_exports = {};
932-
__reExport(entry_exports, require("foo"));
933932
module.exports = __toCommonJS(entry_exports);
933+
__reExport(entry_exports, require("foo"), module.exports);
934934

935935
================================================================================
936936
TestReExportStarES6NoBundle
937937
---------- /out.js ----------
938938
export * from "foo";
939939

940+
================================================================================
941+
TestReExportStarEntryPointAndInnerFile
942+
---------- /out/entry.js ----------
943+
// entry.js
944+
var entry_exports = {};
945+
__export(entry_exports, {
946+
inner: () => inner_exports
947+
});
948+
module.exports = __toCommonJS(entry_exports);
949+
__reExport(entry_exports, require("a"), module.exports);
950+
951+
// inner.js
952+
var inner_exports = {};
953+
__reExport(inner_exports, require("b"));
954+
940955
================================================================================
941956
TestReExportStarExternalCommonJS
942957
---------- /out.js ----------
943958
// entry.js
944959
var entry_exports = {};
945-
__reExport(entry_exports, require("foo"));
946960
module.exports = __toCommonJS(entry_exports);
961+
__reExport(entry_exports, require("foo"), module.exports);
947962

948963
================================================================================
949964
TestReExportStarExternalES6

0 commit comments

Comments
 (0)