Skip to content

Commit

Permalink
fix(linker): add missing esm flag (#1338)
Browse files Browse the repository at this point in the history
  • Loading branch information
lbwa authored Jun 8, 2021
1 parent 6dc45ba commit a5f1387
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 42 deletions.
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,30 @@

Due to an oversight, the `--metafile` setting didn't work when `--watch` was also specified. This only affected the command-line interface. With this release, the `--metafile` setting should now work in this case.

* Add a hidden `__esModule` property to modules in ESM format ([#1338](https://github.com/evanw/esbuild/pull/1338))

Module namespace objects from ESM files will now have a hidden `__esModule` property. This improves compatibility with code that has been converted from ESM syntax to CommonJS by Babel or TypeScript. For example:

```js
// Input TypeScript code
import x from "y"
console.log(x)

// Output JavaScript code from the TypeScript compiler
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const y_1 = __importDefault(require("y"));
console.log(y_1.default);
```

If the object returned by `require("y")` doesn't have an `__esModule` property, then `y_1` will be the object `{ "default": require("y") }`. If the file `"y"` is in ESM format and has a default export of, say, the value `null`, that means `y_1` will now be `{ "default": { "default": null } }` and you will need to use `y_1.default.default` to access the default value. Adding an automatically-generated `__esModule` property when converting files in ESM format to CommonJS is required to make this code work correctly (i.e. for the value to be accessible via just `y_1.default` instead).

With this release, code in ESM format will now have an automatically-generated `__esModule` property to satisfy this convention. The property is non-enumerable so it shouldn't show up when iterating over the properties of the object. As a result, the export name `__esModule` is now reserved for use with esbuild. It's now an error to create an export with the name `__esModule`.

This fix was contributed by [@lbwa](https://github.com/lbwa).

## 0.12.6

* Improve template literal lowering transformation conformance ([#1327](https://github.com/evanw/esbuild/issues/1327))
Expand Down
25 changes: 20 additions & 5 deletions internal/bundler/linker.go
Original file line number Diff line number Diff line change
Expand Up @@ -1740,10 +1740,13 @@ func (c *linkerContext) createExportsForFile(sourceIndex uint32) {
}
}

// Prefix this part with "var exports = {}" if this isn't a CommonJS module
declaredSymbols := []js_ast.DeclaredSymbol{}
var nsExportStmts []js_ast.Stmt
if repr.AST.ExportsKind != js_ast.ExportsCommonJS && (!file.IsEntryPoint() || c.options.OutputFormat != config.FormatCommonJS) {

// Prefix this part with "var exports = {}" if this isn't a CommonJS module
needsExportsVariable := repr.AST.ExportsKind != js_ast.ExportsCommonJS &&
(!file.IsEntryPoint() || c.options.OutputFormat != config.FormatCommonJS)
if needsExportsVariable {
nsExportStmts = append(nsExportStmts, js_ast.Stmt{Data: &js_ast.SLocal{Decls: []js_ast.Decl{{
Binding: js_ast.Binding{Data: &js_ast.BIdentifier{Ref: repr.AST.ExportsRef}},
ValueOrNil: js_ast.Expr{Data: &js_ast.EObject{}},
Expand All @@ -1758,8 +1761,20 @@ func (c *linkerContext) createExportsForFile(sourceIndex uint32) {
// "__markAsModule" which sets the "__esModule" property to true. This must
// be done before any to "require()" or circular imports of multiple modules
// that have been each converted from ESM to CommonJS may not work correctly.
if repr.AST.ExportKeyword.Len > 0 && (repr.AST.ExportsKind == js_ast.ExportsCommonJS ||
(file.IsEntryPoint() && c.options.OutputFormat == config.FormatCommonJS)) {
needsMarkAsModule :=
(repr.AST.ExportKeyword.Len > 0 && (repr.AST.ExportsKind == js_ast.ExportsCommonJS ||
(file.IsEntryPoint() && c.options.OutputFormat == config.FormatCommonJS))) ||
needsExportsVariable

// Avoid calling "__markAsModule" if we call "__export" since the function
// "__export" already calls "__markAsModule". This is an optimization to
// reduce generated code size.
needsExportCall := len(properties) > 0
if needsMarkAsModule && needsExportCall {
needsMarkAsModule = false
}

if needsMarkAsModule {
runtimeRepr := c.graph.Files[runtime.SourceIndex].InputFile.Repr.(*graph.JSRepr)
markAsModuleRef := runtimeRepr.AST.ModuleScope.Members["__markAsModule"].Ref
nsExportStmts = append(nsExportStmts, js_ast.Stmt{Data: &js_ast.SExpr{Value: js_ast.Expr{Data: &js_ast.ECall{
Expand All @@ -1783,7 +1798,7 @@ func (c *linkerContext) createExportsForFile(sourceIndex uint32) {

// "__export(exports, { foo: () => foo })"
exportRef := js_ast.InvalidRef
if len(properties) > 0 {
if needsExportCall {
runtimeRepr := c.graph.Files[runtime.SourceIndex].InputFile.Repr.(*graph.JSRepr)
exportRef = runtimeRepr.AST.ModuleScope.Members["__export"].Ref
nsExportStmts = append(nsExportStmts, js_ast.Stmt{Data: &js_ast.SExpr{Value: js_ast.Expr{Data: &js_ast.ECall{
Expand Down
26 changes: 15 additions & 11 deletions internal/bundler/snapshots/snapshots_default.txt
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,7 @@ TestEmptyExportClauseBundleAsCommonJSIssue910
---------- /out.js ----------
// types.mjs
var types_exports = {};
__markAsModule(types_exports);
var init_types = __esm({
"types.mjs"() {
}
Expand Down Expand Up @@ -864,6 +865,7 @@ var init_d = __esm({

// e.js
var e_exports = {};
__markAsModule(e_exports);
import * as x_star from "x";
var init_e = __esm({
"e.js"() {
Expand Down Expand Up @@ -1813,21 +1815,21 @@ import("foo");import(foo());
TestMinifiedExportsAndModuleFormatCommonJS
---------- /out.js ----------
// foo/test.js
var o = {};
s(o, {
foo: () => p
var t = {};
f(t, {
foo: () => l
});
var p = 123;
var l = 123;

// bar/test.js
var t = {};
s(t, {
bar: () => l
var r = {};
f(r, {
bar: () => m
});
var l = 123;
var m = 123;

// entry.js
console.log(exports, module.exports, o, t);
console.log(exports, module.exports, t, r);

================================================================================
TestMinifyArguments
Expand Down Expand Up @@ -2093,7 +2095,6 @@ export {
TestReExportDefaultExternalCommonJS
---------- /out.js ----------
// entry.js
__markAsModule(exports);
__export(exports, {
bar: () => import_bar.default,
foo: () => import_foo.default
Expand Down Expand Up @@ -2138,7 +2139,6 @@ export { default as bar } from "./bar";
================================================================================
TestReExportDefaultNoBundleCommonJS
---------- /out.js ----------
__markAsModule(exports);
__export(exports, {
bar: () => import_bar.default,
foo: () => import_foo.default
Expand Down Expand Up @@ -3130,6 +3130,7 @@ TestTopLevelAwaitAllowedImportWithoutSplitting
---------- /out.js ----------
// c.js
var c_exports = {};
__markAsModule(c_exports);
var init_c = __esm({
async "c.js"() {
await 0;
Expand All @@ -3138,6 +3139,7 @@ var init_c = __esm({

// b.js
var b_exports = {};
__markAsModule(b_exports);
var init_b = __esm({
async "b.js"() {
await init_c();
Expand All @@ -3146,6 +3148,7 @@ var init_b = __esm({

// a.js
var a_exports = {};
__markAsModule(a_exports);
var init_a = __esm({
async "a.js"() {
await init_b();
Expand All @@ -3154,6 +3157,7 @@ var init_a = __esm({

// entry.js
var entry_exports = {};
__markAsModule(entry_exports);
var init_entry = __esm({
async "entry.js"() {
init_a();
Expand Down
13 changes: 2 additions & 11 deletions internal/bundler/snapshots/snapshots_importstar.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ var require_foo = __commonJS({
});

// entry.js
__markAsModule(exports);
__export(exports, {
ns: () => ns
});
Expand All @@ -25,7 +24,6 @@ var require_foo = __commonJS({
});

// entry.js
__markAsModule(exports);
__export(exports, {
bar: () => import_foo.bar
});
Expand All @@ -42,7 +40,6 @@ var require_foo = __commonJS({
});

// entry.js
__markAsModule(exports);
__export(exports, {
y: () => import_foo.x
});
Expand All @@ -54,7 +51,6 @@ var import_foo = __toModule(require_foo());
TestExportSelfAndImportSelfCommonJS
---------- /out.js ----------
// entry.js
__markAsModule(exports);
__export(exports, {
foo: () => foo
});
Expand All @@ -65,7 +61,6 @@ console.log(exports);
TestExportSelfAndRequireSelfCommonJS
---------- /out.js ----------
// entry.js
__markAsModule(exports);
__export(exports, {
foo: () => foo
});
Expand All @@ -82,7 +77,6 @@ init_entry();
TestExportSelfAsNamespaceCommonJS
---------- /out.js ----------
// entry.js
__markAsModule(exports);
__export(exports, {
foo: () => foo,
ns: () => exports
Expand All @@ -108,7 +102,6 @@ export {
TestExportSelfCommonJS
---------- /out.js ----------
// entry.js
__markAsModule(exports);
__export(exports, {
foo: () => foo
});
Expand Down Expand Up @@ -158,7 +151,6 @@ var someName = (() => {
TestExportStarDefaultExportCommonJS
---------- /out.js ----------
// entry.js
__markAsModule(exports);
__export(exports, {
foo: () => foo
});
Expand Down Expand Up @@ -267,7 +259,6 @@ var require_foo = __commonJS({
});

// entry.js
__markAsModule(exports);
__export(exports, {
ns: () => ns
});
Expand Down Expand Up @@ -780,7 +771,6 @@ export {
================================================================================
TestReExportStarAsCommonJSNoBundle
---------- /out.js ----------
__markAsModule(exports);
__export(exports, {
out: () => out
});
Expand All @@ -798,7 +788,6 @@ export {
TestReExportStarAsExternalCommonJS
---------- /out.js ----------
// entry.js
__markAsModule(exports);
__export(exports, {
out: () => out
});
Expand Down Expand Up @@ -868,6 +857,7 @@ TestReExportStarExternalIIFE
var mod = (() => {
// entry.js
var entry_exports = {};
__markAsModule(entry_exports);
__reExport(entry_exports, __toModule(__require("foo")));
return entry_exports;
})();
Expand All @@ -877,6 +867,7 @@ TestReExportStarIIFENoBundle
---------- /out.js ----------
var mod = (() => {
var entry_exports = {};
__markAsModule(entry_exports);
__reExport(entry_exports, __toModule(require("foo")));
return entry_exports;
})();
Expand Down
28 changes: 20 additions & 8 deletions internal/bundler/snapshots/snapshots_splitting.txt
Original file line number Diff line number Diff line change
Expand Up @@ -259,10 +259,16 @@ export {
================================================================================
TestSplittingDynamicCommonJSIntoES6
---------- /out/entry.js ----------
import "./chunk-U6GWLSPU.js";

// entry.js
import("./foo-W3YX6HCT.js").then(({ default: { bar } }) => console.log(bar));
import("./foo-XBEX5OV6.js").then(({ default: { bar } }) => console.log(bar));

---------- /out/foo-XBEX5OV6.js ----------
import {
__commonJS
} from "./chunk-U6GWLSPU.js";

---------- /out/foo-W3YX6HCT.js ----------
// foo.js
var require_foo = __commonJS({
"foo.js"(exports) {
Expand All @@ -271,6 +277,11 @@ var require_foo = __commonJS({
});
export default require_foo();

---------- /out/chunk-U6GWLSPU.js ----------
export {
__commonJS
};

================================================================================
TestSplittingDynamicES6IntoES6
---------- /out/entry.js ----------
Expand Down Expand Up @@ -317,7 +328,7 @@ TestSplittingHybridESMAndCJSIssue617
import {
foo,
init_a
} from "./chunk-R3U6QWBM.js";
} from "./chunk-NCWNCRTK.js";
init_a();
export {
foo
Expand All @@ -327,15 +338,15 @@ export {
import {
a_exports,
init_a
} from "./chunk-R3U6QWBM.js";
} from "./chunk-NCWNCRTK.js";

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

---------- /out/chunk-R3U6QWBM.js ----------
---------- /out/chunk-NCWNCRTK.js ----------
// a.js
var a_exports = {};
__export(a_exports, {
Expand Down Expand Up @@ -388,22 +399,23 @@ TestSplittingMissingLazyExport
---------- /out/a.js ----------
import {
foo
} from "./chunk-QVTGQSXT.js";
} from "./chunk-KFPHQBDL.js";

// a.js
console.log(foo());

---------- /out/b.js ----------
import {
bar
} from "./chunk-QVTGQSXT.js";
} from "./chunk-KFPHQBDL.js";

// b.js
console.log(bar());

---------- /out/chunk-QVTGQSXT.js ----------
---------- /out/chunk-KFPHQBDL.js ----------
// empty.js
var empty_exports = {};
__markAsModule(empty_exports);

// common.js
function foo() {
Expand Down
1 change: 1 addition & 0 deletions internal/bundler/snapshots/snapshots_ts.txt
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ TestTSExportMissingES6
---------- /out.js ----------
// foo.ts
var foo_exports = {};
__markAsModule(foo_exports);

// entry.js
console.log(foo_exports);
Expand Down
3 changes: 3 additions & 0 deletions internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -12586,6 +12586,9 @@ func (p *parser) recordExport(loc logger.Loc, alias string, ref js_ast.Ref) {
fmt.Sprintf("Multiple exports with the same name %q", alias),
[]logger.MsgData{logger.RangeData(&p.tracker, js_lexer.RangeOfIdentifier(p.source, name.AliasLoc),
fmt.Sprintf("%q was originally exported here", alias))})
} else if alias == "__esModule" {
p.log.AddRangeError(&p.tracker, js_lexer.RangeOfIdentifier(p.source, loc),
"The export name \"__esModule\" is reserved and cannot be used (it's needed as an export marker when converting ES module syntax to CommonJS)")
} else {
p.namedExports[alias] = js_ast.NamedExport{AliasLoc: loc, Ref: ref}
}
Expand Down
7 changes: 7 additions & 0 deletions internal/js_parser/js_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2484,6 +2484,13 @@ func TestExport(t *testing.T) {
"<stdin>: error: This export alias is invalid because it contains the unpaired Unicode surrogate U+DC00\n")
expectParseErrorTarget(t, 2020, "export * as '' from 'foo'",
"<stdin>: error: Using a string as a module namespace identifier name is not supported in the configured target environment\n")

// Exports with the name "__esModule" are forbidden
esModuleError := "<stdin>: error: The export name \"__esModule\" is reserved and cannot be used " +
"(it's needed as an export marker when converting ES module syntax to CommonJS)\n"
expectParseError(t, "export var __esModule", esModuleError)
expectParseError(t, "export {__esModule}; var __esModule", esModuleError)
expectParseError(t, "export {__esModule} from 'foo'", esModuleError)
}

func TestExportDuplicates(t *testing.T) {
Expand Down
Loading

0 comments on commit a5f1387

Please sign in to comment.