Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(linker): add missing esm flag #1338

Merged
merged 6 commits into from Jun 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 24 additions & 0 deletions CHANGELOG.md
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
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
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
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
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
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
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
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