Visitar URL original
fix: name an anonymous export default class before its static initializers by alexander-akait · Pull Request #21910 · webpack/webpack · GitHub
Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/012-anonymous-default-class-name.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"webpack": patch
---

Name an anonymous `export default class` before its static initializers run.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -843,7 +843,7 @@

- Fix snapshot validity check for context dependencies in watch mode by treating watchpack's existence-only entries (`{}`) as cache misses. (by [@alexander-akait](https://github.com/alexander-akait) in [#20916](https://github.com/webpack/webpack/pull/20916))

- Support no-expression template literals in computed member access (e.g. `` import.meta[`url`] ``). (by [@alexander-akait](https://github.com/alexander-akait) in [#20889](https://github.com/webpack/webpack/pull/20889))
- Support no-expression template literals in computed member access (e.g. ``import.meta[`url`]``). (by [@alexander-akait](https://github.com/alexander-akait) in [#20889](https://github.com/webpack/webpack/pull/20889))

- Improve tree-shaking in `isPure`: handle more expression types (`ArrayExpression`, `ObjectExpression`, `NewExpression`, `ChainExpression`, `UnaryExpression` (safe operators), `MetaProperty`, `TaggedTemplateExpression`, `BinaryExpression` (strict equality)), prevent `/*#__PURE__*/` comments from leaking across `ObjectExpression` properties, and detect PURE comments inside `TemplateLiteral` interpolations. (by [@alexander-akait](https://github.com/alexander-akait) in [#20723](https://github.com/webpack/webpack/pull/20723))

Expand Down
11 changes: 8 additions & 3 deletions lib/dependencies/HarmonyExportDependencyParserPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,19 @@ module.exports = class HarmonyExportDependencyParserPlugin {
: undefined,
constValue
);
const isAnonymousClass =
(node.type === "ClassDeclaration" || node.type === "ClassExpression") &&
!node.id;
dep.isAnonymousDefault =
this.options.anonymousDefaultExportName !== false &&
(node.type === "ArrowFunctionExpression" ||
isAnonymousClass ||
((node.type === "FunctionDeclaration" ||
node.type === "FunctionExpression" ||
node.type === "ClassDeclaration" ||
node.type === "ClassExpression") &&
node.type === "FunctionExpression") &&
!node.id));
// A class observes its own name from a static initializer, which runs
// before the name could be patched afterwards, so it is named in place.
dep.isAnonymousDefaultClass = dep.isAnonymousDefault && isAnonymousClass;
dep.setLocWithIndex(parser.getLocation(statement), -1);
parser.state.current.addDependency(dep);
getInnerGraphUtils(parser.state.compilation).addVariableUsage(
Expand Down
29 changes: 25 additions & 4 deletions lib/dependencies/HarmonyExportExpressionDependency.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ const DEFAULT_EXPORT_NAME = "default";
/** @import { InlinedValue } from "../optimize/InlineExports" */
/** @import { Range } from "../javascript/JavascriptParser" */
/** @typedef {string | { id?: string | undefined, range: Range, prefix: string, suffix: string }} DeclarationId */
/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[Range, Range, string, DeclarationId | undefined, boolean]>} ObjectDeserializerContext */
/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[Range, Range, string, DeclarationId | undefined, boolean]>} ObjectSerializerContext */
/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[Range, Range, string, DeclarationId | undefined, boolean, boolean]>} ObjectDeserializerContext */
/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[Range, Range, string, DeclarationId | undefined, boolean, boolean]>} ObjectSerializerContext */
/** @import { ExportMap } from "./HarmonyExportInitFragment" */
/**
* @import {
Expand All @@ -54,6 +54,8 @@ class HarmonyExportExpressionDependency extends NullDependency {
this.constValue = constValue;
/** @type {boolean} */
this.isAnonymousDefault = false;
/** @type {boolean} */
this.isAnonymousDefaultClass = false;
}

get type() {
Expand Down Expand Up @@ -126,6 +128,7 @@ class HarmonyExportExpressionDependency extends NullDependency {
.write(this.prefix)
.write(this.declarationId)
.write(this.isAnonymousDefault)
.write(this.isAnonymousDefaultClass)
.write(this.constValue);
super.serialize(context);
}
Expand All @@ -145,8 +148,10 @@ class HarmonyExportExpressionDependency extends NullDependency {
const c4 = c3.rest;
this.isAnonymousDefault = c4.read();
const c5 = c4.rest;
this.constValue = c5.read();
super.deserialize(c5.rest);
this.isAnonymousDefaultClass = c5.read();
const c6 = c5.rest;
this.constValue = c6.read();
super.deserialize(c6.rest);
}
}

Expand Down Expand Up @@ -308,6 +313,22 @@ HarmonyExportExpressionDependency.Template = class HarmonyExportDependencyTempla
}

if (dep.range) {
// `export default class {}` names the class "default", and a static
// initializer reads that name before any later fix-up could run, so
// the class is created as a property of that name instead.
if (dep.isAnonymousDefaultClass) {
source.replace(
dep.rangeStatement[0],
dep.range[0] - 1,
`${content}{ ${DEFAULT_EXPORT_NAME}: (${dep.prefix}`
);
source.replace(
dep.range[1],
dep.rangeStatement[1] - 0.5,
`) }.${DEFAULT_EXPORT_NAME};`
);
return;
}
source.replace(
dep.rangeStatement[0],
dep.range[0] - 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export let nameDuringDefinition;

export default class {
static observed = (nameDuringDefinition = this.name);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Thing, { nameDuringDefinition } from "./Thing";

it("should name an anonymous default class before its static initializers run", () => {
// A static initializer reads the name during ClassDefinitionEvaluation, so a
// fix-up applied after the class expression is too late to be observed.
expect(nameDuringDefinition).toBe("default");
expect(Thing.name).toBe("default");
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"use strict";

// The fixture observes the name from a class static field, which Node 10
// cannot parse when the harness executes the bundle.
module.exports = function filter() {
const major = Number(process.versions.node.split(".")[0]);
return major >= 12;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use strict";

/** @type {import("../../../../").Configuration} */
module.exports = {
mode: "development",
devtool: false
};
3 changes: 0 additions & 3 deletions test/test262.spectest.js
Original file line number Diff line number Diff line change
Expand Up @@ -920,9 +920,6 @@ const knownBugs = [
// Potential improvement for enumerate
"module-code/namespace/internals/enumerate-binding-uninit.js",

// Replacing `export default` will remove `default` name by spec, need to `static name = "default";` if doesn't exist
"expressions/class/elements/class-name-static-initializer-default-export.js",

// Tests use `$262.evalScript`/`Object.preventExtensions(this)` to declare
// or collide global bindings; webpack wraps each module so `this` is not
// the realm's global object and there is no Script Record context.
Expand Down
Loading