Merge branch 'main' into fix/esm-library-dynamic-import-new-url

This commit is contained in:
Ryuya 2025-08-17 12:08:43 -07:00
commit 90d15f96ee
19 changed files with 194 additions and 68 deletions

View File

@ -173,7 +173,8 @@ jobs:
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
# TODO: Remove version override when https://github.com/nodejs/node/issues/59480 is fixed
node-version: ${{ matrix.node-version == '24.x' && '24.5.0' || matrix.node-version }}
architecture: ${{ steps.calculate_architecture.outputs.result }}
cache: "yarn"
# Install old `jest` version and deps for legacy node versions

View File

@ -59,28 +59,28 @@ module.exports = (env = "development") => ({
```
asset output.js 3.61 MiB [emitted] (name: main)
chunk (runtime: main) output.js (main) 2.24 MiB (javascript) 1.29 KiB (runtime) [entry]
chunk (runtime: main) output.js (main) 2.25 MiB (javascript) 1.29 KiB (runtime) [entry]
> ./example.js main
cached modules 2.24 MiB (javascript) 1.29 KiB (runtime) [cached] 1516 modules
cached modules 2.25 MiB (javascript) 1.29 KiB (runtime) [cached] 1523 modules
webpack X.X.X compiled successfully
```
## Production mode
```
asset output.js 548 KiB [emitted] [minimized] [big] (name: main) 1 related asset
asset output.js 549 KiB [emitted] [minimized] [big] (name: main) 1 related asset
chunk (runtime: main) output.js (main) 2.19 MiB (javascript) 1.29 KiB (runtime) [entry]
> ./example.js main
cached modules 2.19 MiB (javascript) 1.29 KiB (runtime) [cached] 893 modules
cached modules 2.19 MiB (javascript) 1.29 KiB (runtime) [cached] 900 modules
WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets:
output.js (548 KiB)
output.js (549 KiB)
WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
main (548 KiB)
main (549 KiB)
output.js
WARNING in webpack performance recommendations:

View File

@ -539,7 +539,10 @@ const msg = "from virtual module with custom scheme";
/******/ // "1" is the signal for "already loaded"
/******/ if(!installedChunks[chunkId]) {
/******/ if(true) { // all chunks have JS
/******/ installChunk(require("./" + __webpack_require__.u(chunkId)));
/******/ var installedChunk = require("./" + __webpack_require__.u(chunkId));
/******/ if (!installedChunks[chunkId]) {
/******/ installChunk(installedChunk);
/******/ }
/******/ } else installedChunks[chunkId] = 1;
/******/ }
/******/ };
@ -572,13 +575,13 @@ const msg = "from virtual module with custom scheme";
## Unoptimized
```
asset output.js 16.3 KiB [emitted] (name: main)
asset output.js 16.4 KiB [emitted] (name: main)
asset 2.output.js 815 bytes [emitted]
asset 1.output.js 814 bytes [emitted]
chunk (runtime: main) output.js (main) 1.46 KiB (javascript) 4.13 KiB (runtime) [entry] [rendered]
chunk (runtime: main) output.js (main) 1.46 KiB (javascript) 4.21 KiB (runtime) [entry] [rendered]
> ./example.js main
dependent modules 514 bytes [dependent] 8 modules
runtime modules 4.13 KiB 7 modules
runtime modules 4.21 KiB 7 modules
./example.js 977 bytes [built] [code generated]
[no exports]
[used exports unknown]
@ -601,7 +604,7 @@ webpack X.X.X compiled successfully
## Production mode
```
asset output.js 2.5 KiB [emitted] [minimized] (name: main)
asset output.js 2.52 KiB [emitted] [minimized] (name: main)
asset 263.output.js 121 bytes [emitted] [minimized]
asset 722.output.js 121 bytes [emitted] [minimized]
chunk (runtime: main) 263.output.js 20 bytes [rendered]
@ -614,10 +617,10 @@ chunk (runtime: main) 722.output.js 20 bytes [rendered]
./routes/b.js 20 bytes [built] [code generated]
[exports: default]
import() ./routes/b.js virtual:routes 2:9-32
chunk (runtime: main) output.js (main) 1.46 KiB (javascript) 4.13 KiB (runtime) [entry] [rendered]
chunk (runtime: main) output.js (main) 1.46 KiB (javascript) 4.21 KiB (runtime) [entry] [rendered]
> ./example.js main
dependent modules 514 bytes [dependent] 8 modules
runtime modules 4.13 KiB 7 modules
runtime modules 4.21 KiB 7 modules
./example.js 977 bytes [built] [code generated]
[no exports]
[no exports used]

View File

@ -180,7 +180,7 @@ class ConstPlugin {
? statement.alternate
: statement.consequent;
if (branchToRemove) {
this.eliminateUnusedStatement(parser, branchToRemove);
this.eliminateUnusedStatement(parser, branchToRemove, true);
}
return bool;
}
@ -193,7 +193,7 @@ class ConstPlugin {
) {
return;
}
this.eliminateUnusedStatement(parser, statement);
this.eliminateUnusedStatement(parser, statement, false);
return true;
});
parser.hooks.expressionConditionalOperator.tap(
@ -509,9 +509,10 @@ class ConstPlugin {
* Eliminate an unused statement.
* @param {JavascriptParser} parser the parser
* @param {Statement} statement the statement to remove
* @param {boolean} alwaysInBlock whether to always generate curly brackets
* @returns {void}
*/
eliminateUnusedStatement(parser, statement) {
eliminateUnusedStatement(parser, statement, alwaysInBlock) {
// Before removing the unused branch, the hoisted declarations
// must be collected.
//
@ -545,8 +546,14 @@ class ConstPlugin {
const declarations = parser.scope.isStrict
? getHoistedDeclarations(statement, false)
: getHoistedDeclarations(statement, true);
const replacement =
declarations.length > 0 ? `{ var ${declarations.join(", ")}; }` : "{}";
const inBlock = alwaysInBlock || statement.type === "BlockStatement";
let replacement = inBlock ? "{" : "";
replacement +=
declarations.length > 0 ? ` var ${declarations.join(", ")}; ` : "";
replacement += inBlock ? "}" : "";
const dep = new ConstDependency(
`// removed by dead control flow\n${replacement}`,
/** @type {Range} */ (statement.range)

View File

@ -489,6 +489,13 @@ class DefinePlugin {
if (nested && !hooked.has(nested)) {
// only detect the same nested key once
hooked.add(nested);
parser.hooks.collectDestructuringAssignmentProperties.tap(
PLUGIN_NAME,
(expr) => {
const nameInfo = parser.getNameForExpression(expr);
if (nameInfo && nameInfo.name === nested) return true;
}
);
parser.hooks.expression.for(nested).tap(
{
name: PLUGIN_NAME,
@ -687,6 +694,13 @@ class DefinePlugin {
PLUGIN_NAME,
withValueDependency(key, evaluateToString("object"))
);
parser.hooks.collectDestructuringAssignmentProperties.tap(
PLUGIN_NAME,
(expr) => {
const nameInfo = parser.getNameForExpression(expr);
if (nameInfo && nameInfo.name === key) return true;
}
);
parser.hooks.expression.for(key).tap(PLUGIN_NAME, (expr) => {
addValueDependency(key);
let strCode = stringifyObj(

View File

@ -62,9 +62,12 @@ class AwaitDependenciesInitFragment extends InitFragment {
this.dependencies.size === 1 ||
!runtimeTemplate.supportsDestructuring()
) {
templateInput.push(
"var __webpack_async_dependencies_result__ = (__webpack_async_dependencies__.then ? (await __webpack_async_dependencies__)() : __webpack_async_dependencies__);"
);
for (const [index, importVar] of importVars.entries()) {
templateInput.push(
`${importVar} = (__webpack_async_dependencies__.then ? (await __webpack_async_dependencies__)() : __webpack_async_dependencies__)[${index}];`
`${importVar} = __webpack_async_dependencies_result__[${index}];`
);
}
} else {

View File

@ -8,7 +8,10 @@
const CommentCompilationWarning = require("../CommentCompilationWarning");
const HotModuleReplacementPlugin = require("../HotModuleReplacementPlugin");
const WebpackError = require("../WebpackError");
const { getImportAttributes } = require("../javascript/JavascriptParser");
const {
VariableInfo,
getImportAttributes
} = require("../javascript/JavascriptParser");
const InnerGraph = require("../optimize/InnerGraph");
const ConstDependency = require("./ConstDependency");
const HarmonyAcceptDependency = require("./HarmonyAcceptDependency");
@ -211,6 +214,20 @@ module.exports = class HarmonyImportDependencyParserPlugin {
InnerGraph.onUsage(parser.state, (e) => (dep.usedByExports = e));
return true;
});
parser.hooks.collectDestructuringAssignmentProperties.tap(
PLUGIN_NAME,
(expr) => {
const nameInfo = parser.getNameForExpression(expr);
if (
nameInfo &&
nameInfo.rootInfo instanceof VariableInfo &&
nameInfo.rootInfo.name &&
parser.getTagData(nameInfo.rootInfo.name, harmonySpecifierTag)
) {
return true;
}
}
);
parser.hooks.expression
.for(harmonySpecifierTag)
.tap(PLUGIN_NAME, (expr) => {

View File

@ -96,6 +96,12 @@ class ImportMetaPlugin {
PLUGIN_NAME,
toConstantDependency(parser, JSON.stringify("object"))
);
parser.hooks.collectDestructuringAssignmentProperties.tap(
PLUGIN_NAME,
(expr) => {
if (expr.type === "MetaProperty") return true;
}
);
parser.hooks.expression
.for("import.meta")
.tap(PLUGIN_NAME, (metaProperty) => {

View File

@ -46,6 +46,12 @@ class ImportParserPlugin {
*/
const exportsFromEnumerable = (enumerable) =>
Array.from(enumerable, (e) => [e]);
parser.hooks.collectDestructuringAssignmentProperties.tap(
PLUGIN_NAME,
(expr) => {
if (expr.type === "ImportExpression") return true;
}
);
parser.hooks.importCall.tap(PLUGIN_NAME, (expr) => {
const param = parser.evaluateExpression(expr.source);

View File

@ -522,6 +522,10 @@ class JavascriptParser extends Parser {
varDeclarationVar: new HookMap(() => new SyncBailHook(["declaration"])),
/** @type {HookMap<SyncBailHook<[Identifier], boolean | void>>} */
pattern: new HookMap(() => new SyncBailHook(["pattern"])),
/** @type {SyncBailHook<[Expression], boolean | void>} */
collectDestructuringAssignmentProperties: new SyncBailHook([
"expression"
]),
/** @type {HookMap<SyncBailHook<[Expression], boolean | void>>} */
canRename: new HookMap(() => new SyncBailHook(["initExpression"])),
/** @type {HookMap<SyncBailHook<[Expression], boolean | void>>} */
@ -2607,34 +2611,48 @@ class JavascriptParser extends Parser {
* @param {AssignmentExpression} expression assignment expression
*/
preWalkAssignmentExpression(expression) {
this.enterDestructuringAssignment(expression.left, expression.right);
}
/**
* @param {Pattern} pattern pattern
* @param {Expression} expression assignment expression
* @returns {Expression | undefined} destructuring expression
*/
enterDestructuringAssignment(pattern, expression) {
if (
expression.left.type !== "ObjectPattern" ||
pattern.type !== "ObjectPattern" ||
!this.destructuringAssignmentProperties
) {
return;
}
const keys = this._preWalkObjectPattern(expression.left);
const expr =
expression.type === "AwaitExpression" ? expression.argument : expression;
const destructuring =
expr.type === "AssignmentExpression"
? this.enterDestructuringAssignment(expr.left, expr.right)
: this.hooks.collectDestructuringAssignmentProperties.call(expr)
? expr
: undefined;
if (destructuring) {
const keys = this._preWalkObjectPattern(pattern);
if (!keys) return;
// check multiple assignments
if (this.destructuringAssignmentProperties.has(expression)) {
if (this.destructuringAssignmentProperties.has(destructuring)) {
const set =
/** @type {Set<DestructuringAssignmentProperty>} */
(this.destructuringAssignmentProperties.get(expression));
this.destructuringAssignmentProperties.delete(expression);
for (const id of set) keys.add(id);
(this.destructuringAssignmentProperties.get(destructuring));
for (const id of keys) set.add(id);
} else {
this.destructuringAssignmentProperties.set(destructuring, keys);
}
}
this.destructuringAssignmentProperties.set(
expression.right.type === "AwaitExpression"
? expression.right.argument
: expression.right,
keys
);
if (expression.right.type === "AssignmentExpression") {
this.preWalkAssignmentExpression(expression.right);
}
return destructuring;
}
/**
@ -2995,25 +3013,8 @@ class JavascriptParser extends Parser {
* @param {VariableDeclarator} declarator variable declarator
*/
preWalkVariableDeclarator(declarator) {
if (
!declarator.init ||
declarator.id.type !== "ObjectPattern" ||
!this.destructuringAssignmentProperties
) {
return;
}
const keys = this._preWalkObjectPattern(declarator.id);
if (!keys) return;
this.destructuringAssignmentProperties.set(
declarator.init.type === "AwaitExpression"
? declarator.init.argument
: declarator.init,
keys
);
if (declarator.init.type === "AssignmentExpression") {
this.preWalkAssignmentExpression(declarator.init);
if (declarator.init) {
this.enterDestructuringAssignment(declarator.id, declarator.init);
}
}
@ -5179,7 +5180,7 @@ class JavascriptParser extends Parser {
}
/**
* @param {MemberExpression} expression an expression
* @param {Expression} expression an expression
* @returns {{ name: string, rootInfo: ExportedVariableInfo, getMembers: () => string[]} | undefined} name info
*/
getNameForExpression(expression) {

View File

@ -48,10 +48,16 @@ it("should add warning on direct import.meta usage", () => {
expect(Object.keys(import.meta)).toHaveLength(0);
});
it("should support destructuring assignment", () => {
it("should support destructuring assignment", async () => {
let version, url2, c;
({ webpack: version } = { url: url2 } = { c } = import.meta);
expect(version).toBeTypeOf("number");
expect(url2).toBe(url);
expect(c).toBe(undefined);
let version2, url3, d;
({ webpack: version2 } = await ({ url: url3 } = ({ d } = await import.meta)));
expect(version2).toBeTypeOf("number");
expect(url3).toBe(url);
expect(d).toBe(undefined);
});

View File

@ -0,0 +1,2 @@
await 1;
export const a = "a"

View File

@ -0,0 +1,2 @@
await 1;
export const b = "b"

View File

@ -0,0 +1,4 @@
import {a} from "./a";
import {b} from "./b";
export const c = a + b

View File

@ -0,0 +1,3 @@
import {c} from "./c";
export const d = c

View File

@ -0,0 +1,5 @@
it("should work", () => {
return import("./d").then(d => {
expect(d.d).toBe("ab");
});
});

View File

@ -0,0 +1,10 @@
"use strict";
/** @type {import("../../../../").Configuration} */
module.exports = {
output: {
environment: {
destructuring: false
}
}
};

View File

@ -402,7 +402,7 @@ class TestRunner {
);
const esmCache = new Map();
const { category, name, round } = this.testMeta;
const esmIdentifier = `${category.name}-${name}-${round || 0}`;
const esmIdentifier = `${category}-${name}-${round || 0}`;
let esmContext = null;
return (moduleInfo, context) => {
const asModule = require("../helpers/asModule");

46
types.d.ts vendored
View File

@ -663,12 +663,12 @@ declare abstract class BasicEvaluatedExpression {
| MethodDefinition
| PropertyDefinition
| VariableDeclarator
| SwitchCase
| CatchClause
| ObjectPattern
| ArrayPattern
| RestElement
| AssignmentPattern
| SwitchCase
| CatchClause
| Property
| AssignmentProperty
| ClassBody
@ -894,12 +894,12 @@ declare abstract class BasicEvaluatedExpression {
| MethodDefinition
| PropertyDefinition
| VariableDeclarator
| SwitchCase
| CatchClause
| ObjectPattern
| ArrayPattern
| RestElement
| AssignmentPattern
| SwitchCase
| CatchClause
| Property
| AssignmentProperty
| ClassBody
@ -6855,6 +6855,10 @@ declare class JavascriptParser extends ParserClass {
varDeclarationUsing: HookMap<SyncBailHook<[Identifier], boolean | void>>;
varDeclarationVar: HookMap<SyncBailHook<[Identifier], boolean | void>>;
pattern: HookMap<SyncBailHook<[Identifier], boolean | void>>;
collectDestructuringAssignmentProperties: SyncBailHook<
[Expression],
boolean | void
>;
canRename: HookMap<SyncBailHook<[Expression], boolean | void>>;
rename: HookMap<SyncBailHook<[Expression], boolean | void>>;
assign: HookMap<SyncBailHook<[AssignmentExpression], boolean | void>>;
@ -7329,6 +7333,38 @@ declare class JavascriptParser extends ParserClass {
): void;
blockPreWalkExpressionStatement(statement: ExpressionStatement): void;
preWalkAssignmentExpression(expression: AssignmentExpression): void;
enterDestructuringAssignment(
pattern: Pattern,
expression: Expression
):
| undefined
| ImportExpressionImport
| UnaryExpression
| ArrayExpression
| ArrowFunctionExpression
| AssignmentExpression
| AwaitExpression
| BinaryExpression
| SimpleCallExpression
| NewExpression
| ChainExpression
| ClassExpression
| ConditionalExpression
| FunctionExpression
| Identifier
| SimpleLiteral
| RegExpLiteral
| BigIntLiteral
| LogicalExpression
| MemberExpression
| MetaProperty
| ObjectExpression
| SequenceExpression
| TaggedTemplateExpression
| TemplateLiteral
| ThisExpression
| UpdateExpression
| YieldExpression;
modulePreWalkImportDeclaration(
statement: ImportDeclarationJavascriptParser
): void;
@ -7874,7 +7910,7 @@ declare class JavascriptParser extends ParserClass {
allowedTypes: number
): undefined | CallExpressionInfo | ExpressionExpressionInfo;
getNameForExpression(
expression: MemberExpression
expression: Expression
):
| undefined
| {