Skip to content

Commit bf96964

Browse files
committed
fix: anonymous default export name fix-up in ES5 environment
When `output.environment.const` is false and the default export slot is used, the harmony default export template assigns directly to `__webpack_exports__["default"]` without declaring any local `__WEBPACK_DEFAULT_EXPORT__` binding. The anonymous-default `.name` fix-up introduced in #20773 still referenced that undeclared binding, causing a `ReferenceError` at module load time. Also replaces `Reflect.*` with `Object.*` so the emitted code does not depend on ES2015 `Reflect` in ES5 target runtimes, and guards against an undefined descriptor so Node 10 (and other legacy V8 versions that do not set an own `name` property on anonymous class expressions assigned to a MemberExpression) does not throw. Adds regression tests covering anonymous arrow / async arrow / class / class-extends default exports under ES5 environment, plus an anonymous function default export under module concatenation. Fixes #20793
1 parent e370b76 commit bf96964

19 files changed

Lines changed: 170 additions & 8 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+
Fix two ES5-environment regressions in the anonymous default export `.name` fix-up: the generated code referenced an undeclared `__WEBPACK_DEFAULT_EXPORT__` binding causing `ReferenceError`, and used `Reflect.defineProperty` which is not available in pre-ES2015 runtimes. The fix-up now references the real assignment target and uses `Object.defineProperty` / `Object.getOwnPropertyDescriptor`.

lib/Module.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,11 @@ const makeSerializable = require("./util/makeSerializable");
120120
* @property {[{ shareScope: string, initStage: number, init: string }]} share-init share-init for modules federation
121121
*/
122122

123-
/* eslint-disable jsdoc/type-formatting */
124123
/**
125124
* @template {string} K
126125
* @typedef {K extends keyof AllCodeGenerationSchemas ? AllCodeGenerationSchemas[K] : EXPECTED_ANY} CodeGenValue
127126
*/
128-
/* eslint-enable jsdoc/type-formatting */
129127

130-
/* eslint-disable jsdoc/require-template */
131128
/**
132129
* @typedef {object} CodeGenMapOverloads
133130
* @property {<K extends string>(key: K) => CodeGenValue<K> | undefined} get

lib/dependencies/HarmonyExportExpressionDependency.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ HarmonyExportExpressionDependency.Template = class HarmonyExportDependencyTempla
163163
// see test/test262-cases/test/language/module-code/instn-named-bndng-dflt-fun-anon.js cspell:disable-line
164164
initFragments.push(
165165
new InitFragment(
166-
`Reflect.defineProperty(${name}, "name", { value: "default", configurable: true });\n`,
166+
`Object.defineProperty(${name}, "name", { value: "default", configurable: true });\n`,
167167
InitFragment.STAGE_HARMONY_EXPORTS,
168168
2
169169
)
@@ -172,7 +172,7 @@ HarmonyExportExpressionDependency.Template = class HarmonyExportDependencyTempla
172172
} else {
173173
/** @type {string} */
174174
let content;
175-
const name = ConcatenationScope.DEFAULT_EXPORT;
175+
let name = ConcatenationScope.DEFAULT_EXPORT;
176176
if (runtimeTemplate.supportsConst()) {
177177
content = `/* harmony default export */ const ${name} = `;
178178
if (concatenationScope) {
@@ -201,9 +201,13 @@ HarmonyExportExpressionDependency.Template = class HarmonyExportDependencyTempla
201201
if (used) {
202202
runtimeRequirements.add(RuntimeGlobals.exports);
203203
// This is a little bit incorrect as TDZ is not correct, but we can't use const.
204-
content = `/* harmony default export */ ${exportsName}${propertyAccess(
204+
// No local `__WEBPACK_DEFAULT_EXPORT__` binding is created in this path,
205+
// so the anonymous-default `.name` fix-up below must reference the actual
206+
// assignment target instead. See issue #20793.
207+
name = `${exportsName}${propertyAccess(
205208
typeof used === "string" ? [used] : used
206-
)} = `;
209+
)}`;
210+
content = `/* harmony default export */ ${name} = `;
207211
} else {
208212
content = `/* unused harmony default export */ var ${name} = `;
209213
}
@@ -221,7 +225,7 @@ HarmonyExportExpressionDependency.Template = class HarmonyExportDependencyTempla
221225
source.replace(
222226
dep.range[1],
223227
dep.rangeStatement[1] - 0.5,
224-
`);\nReflect.getOwnPropertyDescriptor(${name}, "name").writable || Reflect.defineProperty(${name}, "name", { value: "default", configurable: true });`
228+
`);\n(Object.getOwnPropertyDescriptor(${name}, "name") || {}).writable || Object.defineProperty(${name}, "name", { value: "default", configurable: true });`
225229
);
226230
} else {
227231
source.replace(dep.range[1], dep.rangeStatement[1] - 0.5, ");");
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import inner from "./inner";
2+
3+
it("should handle anonymous default function declaration under module concatenation", () => {
4+
// When ConcatenationScope renames `__WEBPACK_DEFAULT_EXPORT__` to a
5+
// module-scoped symbol, any Reflect.defineProperty InitFragment added
6+
// for the anonymous default fix-up must reference the renamed symbol.
7+
expect(inner()).toBe(42);
8+
expect(inner.name).toBe("default");
9+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function () {
2+
return 42;
3+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"use strict";
2+
3+
/** @type {import("../../../../").Configuration} */
4+
module.exports = {
5+
mode: "production",
6+
devtool: false,
7+
optimization: {
8+
concatenateModules: true,
9+
minimize: false
10+
}
11+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import load from "./loader";
2+
3+
it("should handle anonymous async arrow default export in ES5 env", async () => {
4+
// `export default async (v) => v * 2` — ArrowFunctionExpression (async),
5+
// matches isAnonymousDefault. Ensure the Reflect fix-up references the
6+
// real assignment target, not an undeclared `__WEBPACK_DEFAULT_EXPORT__`.
7+
const result = await load(21);
8+
expect(result).toBe(42);
9+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default async (/** @type {number} */ value) => value * 2;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"use strict";
2+
3+
/** @type {import("../../../../").Configuration} */
4+
module.exports = {
5+
mode: "development",
6+
devtool: false,
7+
output: {
8+
environment: {
9+
module: false,
10+
const: false,
11+
arrowFunction: false,
12+
bigIntLiteral: false,
13+
destructuring: false,
14+
dynamicImport: false,
15+
forOf: false
16+
}
17+
}
18+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export class Base {
2+
kind() {
3+
return "base";
4+
}
5+
}

0 commit comments

Comments
 (0)