Skip to content

Commit e1b7050

Browse files
committed
fix #3319: missing symbol usage in glob transform
1 parent 461ca73 commit e1b7050

File tree

5 files changed

+248
-54
lines changed

5 files changed

+248
-54
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
* Fix glob imports in TypeScript files ([#3319](https://github.com/evanw/esbuild/issues/3319))
6+
7+
This release fixes a problem where bundling a TypeScript file containing a glob import could emit a call to a helper function that doesn't exist. The problem happened because esbuild's TypeScript transformation removes unused imports (which is required for correctness, as they may be type-only imports) and esbuild's glob import transformation wasn't correctly marking the imported helper function as used. This wasn't caught earlier because most of esbuild's glob import tests were written in JavaScript, not in TypeScript.
8+
59
* Fix a panic when transforming optional chaining with `define` ([#3551](https://github.com/evanw/esbuild/issues/3551), [#3554](https://github.com/evanw/esbuild/pull/3554))
610

711
This release fixes a case where esbuild could crash with a panic, which was triggered by using `define` to replace an expression containing an optional chain. Here is an example:

internal/bundler_tests/bundler_glob_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,33 @@ func TestGlobBasicNoSplitting(t *testing.T) {
3737
})
3838
}
3939

40+
func TestTSGlobBasicNoSplitting(t *testing.T) {
41+
glob_suite.expectBundled(t, bundled{
42+
files: map[string]string{
43+
"/entry.ts": `
44+
const ab = Math.random() < 0.5 ? 'a.ts' : 'b.ts'
45+
console.log({
46+
concat: {
47+
require: require('./src/' + ab),
48+
import: import('./src/' + ab),
49+
},
50+
template: {
51+
require: require(` + "`./src/${ab}`" + `),
52+
import: import(` + "`./src/${ab}`" + `),
53+
},
54+
})
55+
`,
56+
"/src/a.ts": `module.exports = 'a'`,
57+
"/src/b.ts": `module.exports = 'b'`,
58+
},
59+
entryPaths: []string{"/entry.ts"},
60+
options: config.Options{
61+
Mode: config.ModeBundle,
62+
AbsOutputFile: "/out.js",
63+
},
64+
})
65+
}
66+
4067
func TestGlobBasicSplitting(t *testing.T) {
4168
glob_suite.expectBundled(t, bundled{
4269
files: map[string]string{
@@ -65,6 +92,34 @@ func TestGlobBasicSplitting(t *testing.T) {
6592
})
6693
}
6794

95+
func TestTSGlobBasicSplitting(t *testing.T) {
96+
glob_suite.expectBundled(t, bundled{
97+
files: map[string]string{
98+
"/entry.ts": `
99+
const ab = Math.random() < 0.5 ? 'a.ts' : 'b.ts'
100+
console.log({
101+
concat: {
102+
require: require('./src/' + ab),
103+
import: import('./src/' + ab),
104+
},
105+
template: {
106+
require: require(` + "`./src/${ab}`" + `),
107+
import: import(` + "`./src/${ab}`" + `),
108+
},
109+
})
110+
`,
111+
"/src/a.ts": `module.exports = 'a'`,
112+
"/src/b.ts": `module.exports = 'b'`,
113+
},
114+
entryPaths: []string{"/entry.ts"},
115+
options: config.Options{
116+
Mode: config.ModeBundle,
117+
AbsOutputDir: "/out",
118+
CodeSplitting: true,
119+
},
120+
})
121+
}
122+
68123
func TestGlobDirDoesNotExist(t *testing.T) {
69124
glob_suite.expectBundled(t, bundled{
70125
files: map[string]string{

internal/bundler_tests/snapshots/snapshots_glob.txt

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,3 +258,135 @@ console.log({
258258
import: globImport_src_js(`./src/${ab}.js`)
259259
}
260260
});
261+
262+
================================================================================
263+
TestTSGlobBasicNoSplitting
264+
---------- /out.js ----------
265+
// src/a.ts
266+
var require_a = __commonJS({
267+
"src/a.ts"(exports, module) {
268+
module.exports = "a";
269+
}
270+
});
271+
272+
// src/b.ts
273+
var require_b = __commonJS({
274+
"src/b.ts"(exports, module) {
275+
module.exports = "b";
276+
}
277+
});
278+
279+
// require("./src/**/*") in entry.ts
280+
var globRequire_src = __glob({
281+
"./src/a.ts": () => require_a(),
282+
"./src/b.ts": () => require_b()
283+
});
284+
285+
// import("./src/**/*") in entry.ts
286+
var globImport_src = __glob({
287+
"./src/a.ts": () => Promise.resolve().then(() => __toESM(require_a())),
288+
"./src/b.ts": () => Promise.resolve().then(() => __toESM(require_b()))
289+
});
290+
291+
// entry.ts
292+
var ab = Math.random() < 0.5 ? "a.ts" : "b.ts";
293+
console.log({
294+
concat: {
295+
require: globRequire_src("./src/" + ab),
296+
import: globImport_src("./src/" + ab)
297+
},
298+
template: {
299+
require: globRequire_src(`./src/${ab}`),
300+
import: globImport_src(`./src/${ab}`)
301+
}
302+
});
303+
304+
================================================================================
305+
TestTSGlobBasicSplitting
306+
---------- /out/entry.js ----------
307+
import {
308+
require_a
309+
} from "./chunk-NW3BCH2J.js";
310+
import {
311+
require_b
312+
} from "./chunk-LX6LSYGO.js";
313+
import {
314+
__glob
315+
} from "./chunk-NI2FVOS3.js";
316+
317+
// require("./src/**/*") in entry.ts
318+
var globRequire_src = __glob({
319+
"./src/a.ts": () => require_a(),
320+
"./src/b.ts": () => require_b()
321+
});
322+
323+
// import("./src/**/*") in entry.ts
324+
var globImport_src = __glob({
325+
"./src/a.ts": () => import("./a-CIKLEGQA.js"),
326+
"./src/b.ts": () => import("./b-UWTMLRMB.js")
327+
});
328+
329+
// entry.ts
330+
var ab = Math.random() < 0.5 ? "a.ts" : "b.ts";
331+
console.log({
332+
concat: {
333+
require: globRequire_src("./src/" + ab),
334+
import: globImport_src("./src/" + ab)
335+
},
336+
template: {
337+
require: globRequire_src(`./src/${ab}`),
338+
import: globImport_src(`./src/${ab}`)
339+
}
340+
});
341+
342+
---------- /out/a-CIKLEGQA.js ----------
343+
import {
344+
require_a
345+
} from "./chunk-NW3BCH2J.js";
346+
import "./chunk-NI2FVOS3.js";
347+
export default require_a();
348+
349+
---------- /out/chunk-NW3BCH2J.js ----------
350+
import {
351+
__commonJS
352+
} from "./chunk-NI2FVOS3.js";
353+
354+
// src/a.ts
355+
var require_a = __commonJS({
356+
"src/a.ts"(exports, module) {
357+
module.exports = "a";
358+
}
359+
});
360+
361+
export {
362+
require_a
363+
};
364+
365+
---------- /out/b-UWTMLRMB.js ----------
366+
import {
367+
require_b
368+
} from "./chunk-LX6LSYGO.js";
369+
import "./chunk-NI2FVOS3.js";
370+
export default require_b();
371+
372+
---------- /out/chunk-LX6LSYGO.js ----------
373+
import {
374+
__commonJS
375+
} from "./chunk-NI2FVOS3.js";
376+
377+
// src/b.ts
378+
var require_b = __commonJS({
379+
"src/b.ts"(exports, module) {
380+
module.exports = "b";
381+
}
382+
});
383+
384+
export {
385+
require_b
386+
};
387+
388+
---------- /out/chunk-NI2FVOS3.js ----------
389+
export {
390+
__glob,
391+
__commonJS
392+
};

internal/js_parser/js_parser.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15762,6 +15762,7 @@ outer:
1576215762
})
1576315763
}
1576415764

15765+
p.recordUsage(ref)
1576515766
return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ECall{
1576615767
Target: js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EIdentifier{Ref: ref}},
1576715768
Args: []js_ast.Expr{expr},

scripts/end-to-end-tests.js

Lines changed: 56 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -8214,66 +8214,68 @@ if (process.platform === 'darwin' || process.platform === 'win32') {
82148214
}
82158215

82168216
// Test glob import behavior
8217-
tests.push(
8218-
test(['./src/*.ts', '--outdir=out', '--bundle', '--format=cjs'], {
8219-
'node.js': `
8220-
if (require('./out/a.js') !== 10) throw 'fail: a'
8221-
if (require('./out/b.js') !== 11) throw 'fail: b'
8222-
if (require('./out/c.js') !== 12) throw 'fail: c'
8223-
`,
8224-
'src/a.ts': `module.exports = 10 as number`,
8225-
'src/b.ts': `module.exports = 11 as number`,
8226-
'src/c.ts': `module.exports = 12 as number`,
8227-
}),
8228-
test(['in.js', '--outfile=node.js', '--bundle'], {
8229-
'in.js': `
8230-
for (let i = 0; i < 3; i++) {
8231-
const value = require('./' + i + '.js')
8232-
if (value !== i + 10) throw 'fail: ' + i
8233-
}
8234-
`,
8235-
'0.js': `module.exports = 10`,
8236-
'1.js': `module.exports = 11`,
8237-
'2.js': `module.exports = 12`,
8238-
}),
8239-
test(['in.js', '--outfile=node.js', '--bundle'], {
8240-
'in.js': `
8241-
for (let i = 0; i < 3; i++) {
8242-
const value = require(\`./\${i}.js\`)
8243-
if (value !== i + 10) throw 'fail: ' + i
8244-
}
8245-
`,
8246-
'0.js': `module.exports = 10`,
8247-
'1.js': `module.exports = 11`,
8248-
'2.js': `module.exports = 12`,
8249-
}),
8250-
test(['in.js', '--outfile=node.js', '--bundle'], {
8251-
'in.js': `
8252-
export let async = async () => {
8217+
for (const ext of ['.js', '.ts']) {
8218+
tests.push(
8219+
test(['./src/*' + ext, '--outdir=out', '--bundle', '--format=cjs'], {
8220+
'node.js': `
8221+
if (require('./out/a.js') !== 10) throw 'fail: a'
8222+
if (require('./out/b.js') !== 11) throw 'fail: b'
8223+
if (require('./out/c.js') !== 12) throw 'fail: c'
8224+
`,
8225+
['src/a' + ext]: `module.exports = 10`,
8226+
['src/b' + ext]: `module.exports = 11`,
8227+
['src/c' + ext]: `module.exports = 12`,
8228+
}),
8229+
test(['in' + ext, '--outfile=node.js', '--bundle'], {
8230+
['in' + ext]: `
82538231
for (let i = 0; i < 3; i++) {
8254-
const { default: value } = await import('./' + i + '.js')
8232+
const value = require('./' + i + '${ext}')
82558233
if (value !== i + 10) throw 'fail: ' + i
82568234
}
8257-
}
8258-
`,
8259-
'0.js': `export default 10`,
8260-
'1.js': `export default 11`,
8261-
'2.js': `export default 12`,
8262-
}, { async: true }),
8263-
test(['in.js', '--outfile=node.js', '--bundle'], {
8264-
'in.js': `
8265-
export let async = async () => {
8235+
`,
8236+
['0' + ext]: `module.exports = 10`,
8237+
['1' + ext]: `module.exports = 11`,
8238+
['2' + ext]: `module.exports = 12`,
8239+
}),
8240+
test(['in' + ext, '--outfile=node.js', '--bundle'], {
8241+
['in' + ext]: `
82668242
for (let i = 0; i < 3; i++) {
8267-
const { default: value } = await import(\`./\${i}.js\`)
8243+
const value = require(\`./\${i}${ext}\`)
82688244
if (value !== i + 10) throw 'fail: ' + i
82698245
}
8270-
}
8271-
`,
8272-
'0.js': `export default 10`,
8273-
'1.js': `export default 11`,
8274-
'2.js': `export default 12`,
8275-
}, { async: true }),
8276-
)
8246+
`,
8247+
['0' + ext]: `module.exports = 10`,
8248+
['1' + ext]: `module.exports = 11`,
8249+
['2' + ext]: `module.exports = 12`,
8250+
}),
8251+
test(['in' + ext, '--outfile=node.js', '--bundle'], {
8252+
['in' + ext]: `
8253+
export let async = async () => {
8254+
for (let i = 0; i < 3; i++) {
8255+
const { default: value } = await import('./' + i + '${ext}')
8256+
if (value !== i + 10) throw 'fail: ' + i
8257+
}
8258+
}
8259+
`,
8260+
['0' + ext]: `export default 10`,
8261+
['1' + ext]: `export default 11`,
8262+
['2' + ext]: `export default 12`,
8263+
}, { async: true }),
8264+
test(['in' + ext, '--outfile=node.js', '--bundle'], {
8265+
['in' + ext]: `
8266+
export let async = async () => {
8267+
for (let i = 0; i < 3; i++) {
8268+
const { default: value } = await import(\`./\${i}${ext}\`)
8269+
if (value !== i + 10) throw 'fail: ' + i
8270+
}
8271+
}
8272+
`,
8273+
['0' + ext]: `export default 10`,
8274+
['1' + ext]: `export default 11`,
8275+
['2' + ext]: `export default 12`,
8276+
}, { async: true }),
8277+
)
8278+
}
82778279

82788280
// Test "using" declarations
82798281
for (const flags of [[], '--supported:async-await=false']) {

0 commit comments

Comments
 (0)