Skip to content

Commit bcc77fb

Browse files
committed
fix #4100: invalid identifiers in node annotation
1 parent 37cb6a2 commit bcc77fb

File tree

5 files changed

+87
-3
lines changed

5 files changed

+87
-3
lines changed

CHANGELOG.md

+34
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,39 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
* Fix node-specific annotations for string literal export names ([#4100](https://github.com/evanw/esbuild/issues/4100))
6+
7+
When node instantiates a CommonJS module, it scans the AST to look for names to expose via ESM named exports. This is a heuristic that looks for certain patterns such as `exports.NAME = ...` or `module.exports = { ... }`. This behavior is used by esbuild to "annotate" CommonJS code that was converted from ESM with the original ESM export names. For example, when converting the file `export let foo, bar` from ESM to CommonJS, esbuild appends this to the end of the file:
8+
9+
```js
10+
// Annotate the CommonJS export names for ESM import in node:
11+
0 && (module.exports = {
12+
bar,
13+
foo
14+
});
15+
```
16+
17+
However, this feature previously didn't work correctly for export names that are not valid identifiers, which can be constructed using string literal export names. The generated code contained a syntax error. That problem is fixed in this release:
18+
19+
```js
20+
// Original code
21+
let foo
22+
export { foo as "foo!" }
23+
24+
// Old output (with --format=cjs --platform=node)
25+
...
26+
0 && (module.exports = {
27+
"foo!"
28+
});
29+
30+
// New output (with --format=cjs --platform=node)
31+
...
32+
0 && (module.exports = {
33+
"foo!": null
34+
});
35+
```
36+
337
## 0.25.1
438
539
* Fix incorrect paths in inline source maps ([#4070](https://github.com/evanw/esbuild/issues/4070), [#4075](https://github.com/evanw/esbuild/issues/4075), [#4105](https://github.com/evanw/esbuild/issues/4105))

internal/bundler_tests/bundler_default_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -1698,6 +1698,27 @@ func TestNodeAnnotationFalsePositiveIssue3544(t *testing.T) {
16981698
})
16991699
}
17001700

1701+
// https://github.com/evanw/esbuild/issues/4100
1702+
func TestNodeAnnotationInvalidIdentifierIssue4100(t *testing.T) {
1703+
default_suite.expectBundled(t, bundled{
1704+
files: map[string]string{
1705+
"/entry.mjs": `
1706+
let foo, bar, baz
1707+
export {
1708+
foo, bar as if, baz as "..."
1709+
}
1710+
`,
1711+
},
1712+
entryPaths: []string{"/entry.mjs"},
1713+
options: config.Options{
1714+
Mode: config.ModeBundle,
1715+
OutputFormat: config.FormatCommonJS,
1716+
AbsOutputFile: "/out.js",
1717+
Platform: config.PlatformNode,
1718+
},
1719+
})
1720+
}
1721+
17011722
func TestMinifiedBundleES6(t *testing.T) {
17021723
default_suite.expectBundled(t, bundled{
17031724
files: map[string]string{

internal/bundler_tests/snapshots/snapshots_default.txt

+21
Original file line numberDiff line numberDiff line change
@@ -5431,6 +5431,27 @@ function confuseNode(exports2) {
54315431
confuseNode
54325432
});
54335433

5434+
================================================================================
5435+
TestNodeAnnotationInvalidIdentifierIssue4100
5436+
---------- /out.js ----------
5437+
// entry.mjs
5438+
var entry_exports = {};
5439+
__export(entry_exports, {
5440+
"...": () => baz,
5441+
foo: () => foo,
5442+
if: () => bar
5443+
});
5444+
module.exports = __toCommonJS(entry_exports);
5445+
var foo;
5446+
var bar;
5447+
var baz;
5448+
// Annotate the CommonJS export names for ESM import in node:
5449+
0 && (module.exports = {
5450+
"...": null,
5451+
foo,
5452+
if: null
5453+
});
5454+
54345455
================================================================================
54355456
TestNodeModules
54365457
---------- /Users/user/project/out.js ----------

internal/linker/linker.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -5078,7 +5078,7 @@ func (c *linkerContext) generateEntryPointTailJS(
50785078

50795079
// "{if: null}"
50805080
var valueOrNil js_ast.Expr
5081-
if _, ok := js_lexer.Keywords[export]; ok {
5081+
if _, ok := js_lexer.Keywords[export]; ok || !js_ast.IsIdentifier(export) {
50825082
// Make sure keywords don't cause a syntax error. This has to map to
50835083
// "null" instead of something shorter like "0" because the library
50845084
// "cjs-module-lexer" only supports identifiers in this position, and

scripts/end-to-end-tests.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -2197,14 +2197,18 @@ tests.push(
21972197

21982198
// Test external ES6 export
21992199
tests.push(
2200-
test(['--bundle', 'foo.js', '--outfile=out.js', '--format=cjs'], {
2200+
test(['--bundle', 'foo.js', '--outfile=out.js', '--format=cjs', '--platform=node'], {
22012201
'foo.js': `export const foo = 123`,
22022202
'node.js': `const out = require('./out'); if (!out.__esModule || out.foo !== 123) throw 'fail'`,
22032203
}),
2204-
test(['--bundle', 'foo.js', '--outfile=out.js', '--format=cjs'], {
2204+
test(['--bundle', 'foo.js', '--outfile=out.js', '--format=cjs', '--platform=node'], {
22052205
'foo.js': `export default 123`,
22062206
'node.js': `const out = require('./out'); if (!out.__esModule || out.default !== 123) throw 'fail'`,
22072207
}),
2208+
test(['--bundle', 'foo.js', '--outfile=out.js', '--format=cjs', '--platform=node'], {
2209+
'foo.js': `const something = 123; export { something as 'some name' }`,
2210+
'node.js': `const out = require('./out'); if (!out.__esModule || out['some name'] !== 123) throw 'fail'`,
2211+
}),
22082212
test(['--bundle', 'foo.js', '--outfile=out.js', '--format=esm'], {
22092213
'foo.js': `export const foo = 123`,
22102214
'node.js': `import {foo} from './out.js'; if (foo !== 123) throw 'fail'`,
@@ -2213,6 +2217,10 @@ tests.push(
22132217
'foo.js': `export default 123`,
22142218
'node.js': `import out from './out.js'; if (out !== 123) throw 'fail'`,
22152219
}),
2220+
test(['--bundle', 'foo.js', '--outfile=out.js', '--format=esm'], {
2221+
'foo.js': `const something = 123; export { something as 'some name' }`,
2222+
'node.js': `import { 'some name' as out } from './out.js'; if (out !== 123) throw 'fail'`,
2223+
}),
22162224
test(['--bundle', 'foo.js', '--outfile=out.js', '--format=cjs', '--platform=node'], {
22172225
'foo.js': `
22182226
export function confuseNode(exports) {

0 commit comments

Comments
 (0)