Skip to content

Commit fbcf4bc

Browse files
fix(javascript): wrap an inlined entry that returns at its top level in an IIFE
A script parse accepts a top-level `return`, as CommonJS does, but an inlined entry rendered without a wrapper (`output.iife: false`, module output, or the `avoidEntryIife` rename) put it outside any function, and the bundle did not parse. test262's top-level `return` cases now pass minified too, so they leave MINIFIED_FAILURES. Claude-Session: https://claude.ai/code/session_019cRZoHW2M4sCbrLFgrAc6b
1 parent 65e57e5 commit fbcf4bc

9 files changed

Lines changed: 96 additions & 24 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"webpack": patch
3+
---
4+
5+
Wrap an inlined entry module that returns at its top level in an IIFE.

‎lib/javascript/JavascriptModulesPlugin.js‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,24 @@ const renameInlinedModuleVariable = (
430430

431431
const PLUGIN_NAME = "JavascriptModulesPlugin";
432432

433+
/**
434+
* Marks a module whose top level holds a `return`, which a script parse allows
435+
* as CommonJS does. Rendered outside a function, that statement does not parse.
436+
* @param {JavascriptParser} parser the parser
437+
* @returns {void}
438+
*/
439+
const recordTopLevelReturn = (parser) => {
440+
parser.hooks.statement.tap(PLUGIN_NAME, (statement) => {
441+
if (
442+
statement.type === "ReturnStatement" &&
443+
parser.scope.topLevelScope === true
444+
) {
445+
/** @type {BuildInfo} */
446+
(parser.state.module.buildInfo).topLevelReturn = true;
447+
}
448+
});
449+
};
450+
433451
// Drops the redundant string/buffer copy of a source, keeping its map: a
434452
// parsed map is far more expensive to rebuild than a `toString`.
435453
const CLEAR_SOURCE_COPIES = { maps: false, source: true };
@@ -519,6 +537,12 @@ class JavascriptModulesPlugin {
519537
}
520538
}
521539
});
540+
// Only a module parse rejects a `return` outside a function.
541+
if (type !== JAVASCRIPT_MODULE_TYPE_ESM) {
542+
normalModuleFactory.hooks.parser
543+
.for(type)
544+
.tap(PLUGIN_NAME, recordTopLevelReturn);
545+
}
522546
normalModuleFactory.hooks.createGenerator
523547
.for(type)
524548
.tap(PLUGIN_NAME, () => new JavascriptGenerator());
@@ -1441,7 +1465,9 @@ class JavascriptModulesPlugin {
14411465
? "it needs to be isolated against other modules in the chunk."
14421466
: exports && !webpackExports
14431467
? `it uses a non-standard name for the exports (${m.exportsArgument}).`
1444-
: hooks.embedInRuntimeBailout.call(m, renderContext);
1468+
: /** @type {BuildInfo} */ (m.buildInfo).topLevelReturn
1469+
? "it returns at its top level."
1470+
: hooks.embedInRuntimeBailout.call(m, renderContext);
14451471

14461472
if (iife) {
14471473
inlinedInIIFE = true;

‎lib/module/Module.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ const {
213213
* @property {boolean=} isCircular true when the module is in a synchronous circular dependency chain
214214
* @property {number=} circularKind CircularKind bit flags from CircularModulesPlugin; unset when the plugin did not run
215215
* @property {boolean=} usesTopLevelAwaitForOf module uses top-level `for await…of` or `await using`, which can't be lowered to a generator
216+
* @property {boolean=} topLevelReturn module returns at its top level, as a CommonJS module may, so it can only be rendered inside a function
216217
* @property {BuildDiagnostics=} diagnostics what the build recorded for stats and the performance hints to report
217218
*/
218219

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
let reached = false;
2+
3+
it("should stop the entry at its top-level return", () => {
4+
expect(reached).toBe(false);
5+
});
6+
7+
if (typeof it === "function") return;
8+
9+
reached = true;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"use strict";
2+
3+
module.exports = {
4+
findBundle() {
5+
return ["script.js", "module.mjs", "test.js"];
6+
}
7+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
const vm = require("vm");
4+
5+
it("should wrap an entry returning at its top level in an IIFE", () => {
6+
for (const file of ["script.js", "module.mjs"]) {
7+
const source = fs.readFileSync(path.join(__dirname, file), "utf-8");
8+
expect(source).toContain(
9+
"This entry needs to be wrapped in an IIFE because it returns at its top level."
10+
);
11+
}
12+
const script = fs.readFileSync(path.join(__dirname, "script.js"), "utf-8");
13+
expect(() => new vm.Script(script)).not.toThrow();
14+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"use strict";
2+
3+
/** @type {import("../../../../").Configuration[]} */
4+
module.exports = [
5+
{
6+
name: "script",
7+
output: {
8+
iife: false,
9+
filename: "script.js"
10+
}
11+
},
12+
{
13+
name: "module",
14+
output: {
15+
module: true,
16+
filename: "module.mjs"
17+
},
18+
target: "es2020"
19+
},
20+
{
21+
name: "test-output",
22+
entry: "./test.js",
23+
output: {
24+
filename: "test.js"
25+
}
26+
}
27+
];

‎test/specCases/test262.spectest.js‎

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1507,30 +1507,8 @@ const MINIFIED_FAILURES = [
15071507
"terser writes a program that no longer parses",
15081508
minifiers: ["terser", "printer"],
15091509
tests: [
1510-
"global-code/return.js (sloppy)",
1511-
"global-code/return.js (strict)",
15121510
"statements/class/elements/fields-hash-constructor-is-a-valid-name.js (sloppy)",
1513-
"statements/class/elements/fields-hash-constructor-is-a-valid-name.js (strict)",
1514-
"statements/return/S12.9_A1_T1.js (sloppy)",
1515-
"statements/return/S12.9_A1_T1.js (strict)",
1516-
"statements/return/S12.9_A1_T10.js (sloppy)",
1517-
"statements/return/S12.9_A1_T10.js (strict)",
1518-
"statements/return/S12.9_A1_T2.js (sloppy)",
1519-
"statements/return/S12.9_A1_T2.js (strict)",
1520-
"statements/return/S12.9_A1_T3.js (sloppy)",
1521-
"statements/return/S12.9_A1_T3.js (strict)",
1522-
"statements/return/S12.9_A1_T4.js (sloppy)",
1523-
"statements/return/S12.9_A1_T4.js (strict)",
1524-
"statements/return/S12.9_A1_T5.js (sloppy)",
1525-
"statements/return/S12.9_A1_T5.js (strict)",
1526-
"statements/return/S12.9_A1_T6.js (sloppy)",
1527-
"statements/return/S12.9_A1_T6.js (strict)",
1528-
"statements/return/S12.9_A1_T7.js (sloppy)",
1529-
"statements/return/S12.9_A1_T7.js (strict)",
1530-
"statements/return/S12.9_A1_T8.js (sloppy)",
1531-
"statements/return/S12.9_A1_T8.js (strict)",
1532-
"statements/return/S12.9_A1_T9.js (sloppy)",
1533-
"statements/return/S12.9_A1_T9.js (strict)"
1511+
"statements/class/elements/fields-hash-constructor-is-a-valid-name.js (strict)"
15341512
]
15351513
},
15361514
{

‎types.d.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16370,6 +16370,11 @@ declare interface KnownBuildInfo {
1637016370
*/
1637116371
usesTopLevelAwaitForOf?: boolean;
1637216372

16373+
/**
16374+
* module returns at its top level, as a CommonJS module may, so it can only be rendered inside a function
16375+
*/
16376+
topLevelReturn?: boolean;
16377+
1637316378
/**
1637416379
* what the build recorded for stats and the performance hints to report
1637516380
*/

0 commit comments

Comments
 (0)