From 9bb35fb998440e7e03eb28fbe538ded5db817d20 Mon Sep 17 00:00:00 2001 From: Ivan Kopeykin Date: Fri, 7 Apr 2023 22:29:32 +0300 Subject: [PATCH 1/7] support destructuring assignment in parser --- lib/DefinePlugin.js | 24 +++-- lib/javascript/JavascriptParser.js | 88 +++++++++++++++++++ .../plugins/define-plugin/index.js | 6 ++ .../plugins/define-plugin/webpack.config.js | 10 ++- types.d.ts | 5 ++ 5 files changed, 126 insertions(+), 7 deletions(-) diff --git a/lib/DefinePlugin.js b/lib/DefinePlugin.js index c7fb1d85c47..1db41f6cd7a 100644 --- a/lib/DefinePlugin.js +++ b/lib/DefinePlugin.js @@ -118,6 +118,7 @@ class RuntimeValue { * @param {string} key the defined key * @param {RuntimeTemplate} runtimeTemplate the runtime template * @param {boolean|undefined|null=} asiSafe asi safe (undefined: unknown, null: unneeded) + * @param {Set|undefined=} objKeys used keys * @returns {string} code converted to string that evaluates */ const stringifyObj = ( @@ -126,7 +127,8 @@ const stringifyObj = ( valueCacheVersions, key, runtimeTemplate, - asiSafe + asiSafe, + objKeys ) => { let code; let arr = Array.isArray(obj); @@ -137,7 +139,12 @@ const stringifyObj = ( ) .join(",")}]`; } else { - code = `{${Object.keys(obj) + let keys = Object.keys(obj); + if (objKeys) { + if (objKeys.size === 0) keys = []; + keys = keys.filter(k => objKeys.has(k)); + } + code = `{${keys .map(key => { const code = obj[key]; return ( @@ -169,6 +176,7 @@ const stringifyObj = ( * @param {string} key the defined key * @param {RuntimeTemplate} runtimeTemplate the runtime template * @param {boolean|undefined|null=} asiSafe asi safe (undefined: unknown, null: unneeded) + * @param {Set|undefined=} objKeys used keys * @returns {string} code converted to string that evaluates */ const toCode = ( @@ -177,7 +185,8 @@ const toCode = ( valueCacheVersions, key, runtimeTemplate, - asiSafe + asiSafe, + objKeys ) => { if (code === null) { return "null"; @@ -211,7 +220,8 @@ const toCode = ( valueCacheVersions, key, runtimeTemplate, - asiSafe + asiSafe, + objKeys ); } if (typeof code === "bigint") { @@ -426,7 +436,8 @@ class DefinePlugin { compilation.valueCacheVersions, originalKey, runtimeTemplate, - !parser.isAsiPosition(expr.range[0]) + !parser.isAsiPosition(expr.range[0]), + parser.destructuringAssignmentKeysFor(expr) ); if (WEBPACK_REQUIRE_FUNCTION_REGEXP.test(strCode)) { return toConstantDependency(parser, strCode, [ @@ -523,7 +534,8 @@ class DefinePlugin { compilation.valueCacheVersions, key, runtimeTemplate, - !parser.isAsiPosition(expr.range[0]) + !parser.isAsiPosition(expr.range[0]), + parser.destructuringAssignmentKeysFor(expr) ); if (WEBPACK_REQUIRE_FUNCTION_REGEXP.test(strCode)) { diff --git a/lib/javascript/JavascriptParser.js b/lib/javascript/JavascriptParser.js index 58bcc4a64b3..a33cd91e873 100644 --- a/lib/javascript/JavascriptParser.js +++ b/lib/javascript/JavascriptParser.js @@ -331,6 +331,8 @@ class JavascriptParser extends Parser { /** @type {(StatementNode|ExpressionNode)[]} */ this.statementPath = undefined; this.prevStatement = undefined; + /** @type {WeakMap>} */ + this.destructuringAssignmentKeys = undefined; this.currentTagData = undefined; this._initializeEvaluating(); } @@ -1411,6 +1413,15 @@ class JavascriptParser extends Parser { }); } + /** + * @param {ExpressionNode} node node + * @returns {Set|undefined} destructured identifiers + */ + destructuringAssignmentKeysFor(node) { + if (!this.destructuringAssignmentKeys) return undefined; + return this.destructuringAssignmentKeys.get(node); + } + getRenameIdentifier(expr) { const result = this.evaluateExpression(expr); if (result.isIdentifier()) { @@ -1557,6 +1568,8 @@ class JavascriptParser extends Parser { case "ClassDeclaration": this.blockPreWalkClassDeclaration(statement); break; + case "ExpressionStatement": + this.blockPreWalkExpressionStatement(statement); } this.prevStatement = this.statementPath.pop(); } @@ -1890,6 +1903,35 @@ class JavascriptParser extends Parser { this.scope.topLevelScope = wasTopLevel; } + blockPreWalkExpressionStatement(statement) { + const expression = statement.expression; + switch (expression.type) { + case "AssignmentExpression": + this.preWalkAssignmentExpression(expression); + } + } + + preWalkAssignmentExpression(expression) { + if ( + expression.left.type !== "ObjectPattern" || + !this.destructuringAssignmentKeys + ) + return; + const ids = this._preWalkObjectPattern(expression.left); + // check multiple assignments + if (this.destructuringAssignmentKeys.has(expression)) { + const set = this.destructuringAssignmentKeys.get(expression); + this.destructuringAssignmentKeys.delete(expression); + for (const id of set) ids.add(id); + } + + this.destructuringAssignmentKeys.set(expression.right, ids); + + if (expression.right.type === "AssignmentExpression") { + this.preWalkAssignmentExpression(expression.right); + } + } + blockPreWalkImportDeclaration(statement) { const source = statement.source.value; this.hooks.import.call(statement, source); @@ -2087,6 +2129,7 @@ class JavascriptParser extends Parser { for (const declarator of statement.declarations) { switch (declarator.type) { case "VariableDeclarator": { + this.preWalkVariableDeclarator(declarator); if (!this.hooks.preDeclarator.call(declarator, statement)) { this.enterPattern(declarator.id, (name, decl) => { let hook = hookMap.get(name); @@ -2104,6 +2147,49 @@ class JavascriptParser extends Parser { } } + _preWalkObjectPattern(objectPattern) { + const ids = new Set(); + const properties = objectPattern.properties; + for (let i = 0; i < properties.length; i++) { + const id = this.evaluateExpression(properties[i].key); + if (id.isIdentifier()) { + ids.add( + typeof id.identifier === "string" + ? id.identifier + : /** @type {string} should be type string here otherwise syntax error */ ( + id.identifier.freeName + ) + ); + } else { + const str = id.asString(); + if (str) { + ids.add(str); + } else { + // could not evaluate key + return; + } + } + } + + return ids; + } + + preWalkVariableDeclarator(declarator) { + if ( + declarator.id.type !== "ObjectPattern" || + !this.destructuringAssignmentKeys + ) + return; + this.destructuringAssignmentKeys.set( + declarator.init, + this._preWalkObjectPattern(declarator.id) + ); + + if (declarator.init.type === "AssignmentExpression") { + this.preWalkAssignmentExpression(declarator.init); + } + } + walkVariableDeclaration(statement) { for (const declarator of statement.declarations) { switch (declarator.type) { @@ -3367,12 +3453,14 @@ class JavascriptParser extends Parser { this.statementPath = []; this.prevStatement = undefined; if (this.hooks.program.call(ast, comments) === undefined) { + this.destructuringAssignmentKeys = new WeakMap(); this.detectMode(ast.body); this.preWalkStatements(ast.body); this.prevStatement = undefined; this.blockPreWalkStatements(ast.body); this.prevStatement = undefined; this.walkStatements(ast.body); + this.destructuringAssignmentKeys = undefined; } this.hooks.finish.call(ast, comments); this.scope = oldScope; diff --git a/test/configCases/plugins/define-plugin/index.js b/test/configCases/plugins/define-plugin/index.js index e3cde299308..2e103604e91 100644 --- a/test/configCases/plugins/define-plugin/index.js +++ b/test/configCases/plugins/define-plugin/index.js @@ -248,3 +248,9 @@ it("should expand properly", function() { expect(require("./dir/" + (tmp + A_DOT_J + tmp) + "s")).toBe(a); expect(require("./dir/" + (tmp + A_DOT_J) + tmp + "s")).toBe(a); }); + +it("destructuring assignment", () => { + const {used} = OBJECT2; + const {['used']: used2} = OBJECT2.sub; + expect(used).toBe(used2); +}); diff --git a/test/configCases/plugins/define-plugin/webpack.config.js b/test/configCases/plugins/define-plugin/webpack.config.js index 4f202b594c6..12810899a97 100644 --- a/test/configCases/plugins/define-plugin/webpack.config.js +++ b/test/configCases/plugins/define-plugin/webpack.config.js @@ -47,7 +47,15 @@ module.exports = { return module instanceof Module; } ), - A_DOT_J: '"a.j"' + A_DOT_J: '"a.j"', + OBJECT2: { + used: 1, + unused: "(() => throw new Error('unused property was rendered'))()", + sub: { + used: 1, + unused: "(() => throw new Error('unused property was rendered'))()" + } + } }) ] }; diff --git a/types.d.ts b/types.d.ts index b1ec17297e7..493be6e8955 100644 --- a/types.d.ts +++ b/types.d.ts @@ -5277,7 +5277,9 @@ declare class JavascriptParser extends Parser { | ForOfStatement )[]; prevStatement: any; + destructuringAssignmentKeys: WeakMap>; currentTagData: any; + destructuringAssignmentKeysFor(node: Expression): undefined | Set; getRenameIdentifier(expr?: any): undefined | string | VariableInfoInterface; walkClass(classy: ClassExpression | ClassDeclaration): void; preWalkStatements(statements?: any): void; @@ -5321,6 +5323,8 @@ declare class JavascriptParser extends Parser { walkForOfStatement(statement?: any): void; preWalkFunctionDeclaration(statement?: any): void; walkFunctionDeclaration(statement?: any): void; + blockPreWalkExpressionStatement(statement?: any): void; + preWalkAssignmentExpression(expression?: any): void; blockPreWalkImportDeclaration(statement?: any): void; enterDeclaration(declaration?: any, onIdent?: any): void; blockPreWalkExportNamedDeclaration(statement?: any): void; @@ -5330,6 +5334,7 @@ declare class JavascriptParser extends Parser { blockPreWalkExportAllDeclaration(statement?: any): void; preWalkVariableDeclaration(statement?: any): void; blockPreWalkVariableDeclaration(statement?: any): void; + preWalkVariableDeclarator(declarator?: any): void; walkVariableDeclaration(statement?: any): void; blockPreWalkClassDeclaration(statement?: any): void; walkClassDeclaration(statement?: any): void; From 12844b2d344de146512af9c9cd2a1e82e01776c6 Mon Sep 17 00:00:00 2001 From: Ivan Kopeykin Date: Sat, 8 Apr 2023 13:17:30 +0300 Subject: [PATCH 2/7] fix keys evaluation --- lib/javascript/JavascriptParser.js | 29 +++++++++---------- .../plugins/define-plugin/index.js | 3 +- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/javascript/JavascriptParser.js b/lib/javascript/JavascriptParser.js index a33cd91e873..c5c559243a7 100644 --- a/lib/javascript/JavascriptParser.js +++ b/lib/javascript/JavascriptParser.js @@ -1917,15 +1917,17 @@ class JavascriptParser extends Parser { !this.destructuringAssignmentKeys ) return; - const ids = this._preWalkObjectPattern(expression.left); + const keys = this._preWalkObjectPattern(expression.left); + if (!keys) return; + // check multiple assignments if (this.destructuringAssignmentKeys.has(expression)) { const set = this.destructuringAssignmentKeys.get(expression); this.destructuringAssignmentKeys.delete(expression); - for (const id of set) ids.add(id); + for (const id of set) keys.add(id); } - this.destructuringAssignmentKeys.set(expression.right, ids); + this.destructuringAssignmentKeys.set(expression.right, keys); if (expression.right.type === "AssignmentExpression") { this.preWalkAssignmentExpression(expression.right); @@ -2151,16 +2153,11 @@ class JavascriptParser extends Parser { const ids = new Set(); const properties = objectPattern.properties; for (let i = 0; i < properties.length; i++) { - const id = this.evaluateExpression(properties[i].key); - if (id.isIdentifier()) { - ids.add( - typeof id.identifier === "string" - ? id.identifier - : /** @type {string} should be type string here otherwise syntax error */ ( - id.identifier.freeName - ) - ); + const key = properties[i].key; + if (key.type === "Identifier") { + ids.add(key.name); } else { + const id = this.evaluateExpression(key); const str = id.asString(); if (str) { ids.add(str); @@ -2180,10 +2177,10 @@ class JavascriptParser extends Parser { !this.destructuringAssignmentKeys ) return; - this.destructuringAssignmentKeys.set( - declarator.init, - this._preWalkObjectPattern(declarator.id) - ); + const keys = this._preWalkObjectPattern(declarator.id); + + if (!keys) return; + this.destructuringAssignmentKeys.set(declarator.init, keys); if (declarator.init.type === "AssignmentExpression") { this.preWalkAssignmentExpression(declarator.init); diff --git a/test/configCases/plugins/define-plugin/index.js b/test/configCases/plugins/define-plugin/index.js index 2e103604e91..f79974071a2 100644 --- a/test/configCases/plugins/define-plugin/index.js +++ b/test/configCases/plugins/define-plugin/index.js @@ -251,6 +251,7 @@ it("should expand properly", function() { it("destructuring assignment", () => { const {used} = OBJECT2; - const {['used']: used2} = OBJECT2.sub; + const {['used']: used2, used: used3} = OBJECT2.sub; expect(used).toBe(used2); + expect(used).toBe(used3); }); From 89933e8a063e984536411f7b15b5dd84629ae85a Mon Sep 17 00:00:00 2001 From: Ivan Kopeykin Date: Sat, 8 Apr 2023 21:19:04 +0300 Subject: [PATCH 3/7] fix pre walking --- lib/javascript/JavascriptParser.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/javascript/JavascriptParser.js b/lib/javascript/JavascriptParser.js index c5c559243a7..22a98ee85ca 100644 --- a/lib/javascript/JavascriptParser.js +++ b/lib/javascript/JavascriptParser.js @@ -2173,6 +2173,7 @@ class JavascriptParser extends Parser { preWalkVariableDeclarator(declarator) { if ( + !declarator.init || declarator.id.type !== "ObjectPattern" || !this.destructuringAssignmentKeys ) From 397ce0c84e30381646dd596fe8fc7d3cb47e9217 Mon Sep 17 00:00:00 2001 From: Ivan Kopeykin Date: Sun, 9 Apr 2023 00:23:15 +0300 Subject: [PATCH 4/7] add import tree shaking support --- .../HarmonyImportDependencyParserPlugin.js | 4 +++ .../HarmonyImportSpecifierDependency.js | 30 +++++++++++++++++-- .../counter.js | 7 +++++ .../counter2.js | 7 +++++ .../harmony-destructuring-assignment/index.js | 27 +++++++++++++++++ .../reexport-namespace.js | 14 +++++++++ 6 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 test/cases/parsing/harmony-destructuring-assignment/counter.js create mode 100644 test/cases/parsing/harmony-destructuring-assignment/counter2.js create mode 100644 test/cases/parsing/harmony-destructuring-assignment/index.js create mode 100644 test/cases/parsing/harmony-destructuring-assignment/reexport-namespace.js diff --git a/lib/dependencies/HarmonyImportDependencyParserPlugin.js b/lib/dependencies/HarmonyImportDependencyParserPlugin.js index 9777333cc5d..2a2e9cf1002 100644 --- a/lib/dependencies/HarmonyImportDependencyParserPlugin.js +++ b/lib/dependencies/HarmonyImportDependencyParserPlugin.js @@ -187,6 +187,7 @@ module.exports = class HarmonyImportDependencyParserPlugin { .for(harmonySpecifierTag) .tap("HarmonyImportDependencyParserPlugin", expr => { const settings = /** @type {HarmonySettings} */ (parser.currentTagData); + const keys = parser.destructuringAssignmentKeysFor(expr); const dep = new HarmonyImportSpecifierDependency( settings.source, settings.sourceOrder, @@ -196,6 +197,7 @@ module.exports = class HarmonyImportDependencyParserPlugin { exportPresenceMode, settings.assertions ); + dep.referencedKeys = keys; dep.shorthand = parser.scope.inShorthand; dep.directImport = true; dep.asiSafe = !parser.isAsiPosition(expr.range[0]); @@ -223,6 +225,7 @@ module.exports = class HarmonyImportDependencyParserPlugin { members.length - nonOptionalMembers.length ) : expression; + const keys = parser.destructuringAssignmentKeysFor(expr); const ids = settings.ids.concat(nonOptionalMembers); const dep = new HarmonyImportSpecifierDependency( settings.source, @@ -233,6 +236,7 @@ module.exports = class HarmonyImportDependencyParserPlugin { exportPresenceMode, settings.assertions ); + dep.referencedKeys = keys; dep.asiSafe = !parser.isAsiPosition(expr.range[0]); dep.loc = expr.loc; parser.state.module.addDependency(dep); diff --git a/lib/dependencies/HarmonyImportSpecifierDependency.js b/lib/dependencies/HarmonyImportSpecifierDependency.js index 35354ca7bb9..dc3e092ff53 100644 --- a/lib/dependencies/HarmonyImportSpecifierDependency.js +++ b/lib/dependencies/HarmonyImportSpecifierDependency.js @@ -52,6 +52,8 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency { this.asiSafe = undefined; /** @type {Set | boolean} */ this.usedByExports = undefined; + /** @type {Set} */ + this.referencedKeys = undefined; } // TODO webpack 6 remove @@ -121,7 +123,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency { */ getReferencedExports(moduleGraph, runtime) { let ids = this.getIds(moduleGraph); - if (ids.length === 0) return Dependency.EXPORTS_OBJECT_REFERENCED; + if (ids.length === 0) return this._getReferencedKeysExports(); let namespaceObjectAsContext = this.namespaceObjectAsContext; if (ids[0] === "default") { const selfModule = moduleGraph.getParentModule(this); @@ -134,7 +136,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency { ) { case "default-only": case "default-with-named": - if (ids.length === 1) return Dependency.EXPORTS_OBJECT_REFERENCED; + if (ids.length === 1) return this._getReferencedKeysExports(); ids = ids.slice(1); namespaceObjectAsContext = true; break; @@ -152,7 +154,27 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency { ids = ids.slice(0, -1); } - return [ids]; + return this._getReferencedKeysExports(ids); + } + + /** + * @param {string[]=} ids ids + * @returns {(string[] | ReferencedExport)[]} referenced exports + */ + _getReferencedKeysExports(ids) { + if (this.referencedKeys) { + /** @type {ReferencedExport[]} */ + const refs = []; + for (const key of this.referencedKeys) { + refs.push({ + name: ids ? ids.concat([key]) : [key], + canMangle: false + }); + } + return refs; + } else { + return ids ? [ids] : Dependency.EXPORTS_OBJECT_REFERENCED; + } } /** @@ -226,6 +248,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency { write(this.shorthand); write(this.asiSafe); write(this.usedByExports); + write(this.referencedKeys); super.serialize(context); } @@ -241,6 +264,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency { this.shorthand = read(); this.asiSafe = read(); this.usedByExports = read(); + this.referencedKeys = read(); super.deserialize(context); } } diff --git a/test/cases/parsing/harmony-destructuring-assignment/counter.js b/test/cases/parsing/harmony-destructuring-assignment/counter.js new file mode 100644 index 00000000000..21dbf67c4b0 --- /dev/null +++ b/test/cases/parsing/harmony-destructuring-assignment/counter.js @@ -0,0 +1,7 @@ +export let counter = 0; +export const d = 1; + +export const exportsInfo = { + counter: __webpack_exports_info__.counter.used, + d: __webpack_exports_info__.d.used +}; diff --git a/test/cases/parsing/harmony-destructuring-assignment/counter2.js b/test/cases/parsing/harmony-destructuring-assignment/counter2.js new file mode 100644 index 00000000000..21dbf67c4b0 --- /dev/null +++ b/test/cases/parsing/harmony-destructuring-assignment/counter2.js @@ -0,0 +1,7 @@ +export let counter = 0; +export const d = 1; + +export const exportsInfo = { + counter: __webpack_exports_info__.counter.used, + d: __webpack_exports_info__.d.used +}; diff --git a/test/cases/parsing/harmony-destructuring-assignment/index.js b/test/cases/parsing/harmony-destructuring-assignment/index.js new file mode 100644 index 00000000000..ec67a32b1fa --- /dev/null +++ b/test/cases/parsing/harmony-destructuring-assignment/index.js @@ -0,0 +1,27 @@ +import * as C from "./reexport-namespace"; +import { counter } from "./reexport-namespace"; +import { exportsInfo } from "./counter"; +import { exportsInfo as exportsInfo2 } from "./counter2"; + +it("expect tree-shake unused exports #1", () => { + const { D } = C; + expect(D).toBe(1); + expect(C.exportsInfo.D).toBe(true); + expect(C.exportsInfo.E).toBe(false); +}); + +it("expect tree-shake unused exports #2", () => { + const { d } = C.counter; + const { ['d']: d1 } = counter; + expect(d).toBe(1); + expect(d1).toBe(1); + expect(exportsInfo.d).toBe(true); + expect(exportsInfo.counter).toBe(false); +}); + +it("expect no support of \"deep\" tree-shaking", () => { + const { counter2: { d } } = C; + expect(d).toBe(1); + expect(exportsInfo2.d).toBe(true); + expect(exportsInfo2.counter).toBe(true); +}); diff --git a/test/cases/parsing/harmony-destructuring-assignment/reexport-namespace.js b/test/cases/parsing/harmony-destructuring-assignment/reexport-namespace.js new file mode 100644 index 00000000000..4a41ad89f66 --- /dev/null +++ b/test/cases/parsing/harmony-destructuring-assignment/reexport-namespace.js @@ -0,0 +1,14 @@ +import * as counter from "./counter"; +export { counter }; +import * as counter2 from "./counter2"; +export { counter2 }; + +export const D = 1; +export const E = 1; + +export const exportsInfo = { + D: __webpack_exports_info__.D.used, + E: __webpack_exports_info__.E.used, + counter: __webpack_exports_info__.counter.used, + counter2: __webpack_exports_info__.counter2.used, +}; From bdbb78769f8feffee51a093e17db7c82f98ec68e Mon Sep 17 00:00:00 2001 From: Ivan Kopeykin Date: Sun, 9 Apr 2023 08:55:55 +0300 Subject: [PATCH 5/7] support rest element --- lib/javascript/JavascriptParser.js | 4 +++- .../harmony-destructuring-assignment/counter.js | 4 +++- .../harmony-destructuring-assignment/counter3.js | 7 +++++++ .../harmony-destructuring-assignment/index.js | 12 +++++++++++- .../harmony-destructuring-assignment/test.filter.js | 4 ++++ 5 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 test/cases/parsing/harmony-destructuring-assignment/counter3.js create mode 100644 test/cases/parsing/harmony-destructuring-assignment/test.filter.js diff --git a/lib/javascript/JavascriptParser.js b/lib/javascript/JavascriptParser.js index 22a98ee85ca..b9fa8a5d7b3 100644 --- a/lib/javascript/JavascriptParser.js +++ b/lib/javascript/JavascriptParser.js @@ -2153,7 +2153,9 @@ class JavascriptParser extends Parser { const ids = new Set(); const properties = objectPattern.properties; for (let i = 0; i < properties.length; i++) { - const key = properties[i].key; + const property = properties[i]; + if (property.type !== "Property") return; + const key = property.key; if (key.type === "Identifier") { ids.add(key.name); } else { diff --git a/test/cases/parsing/harmony-destructuring-assignment/counter.js b/test/cases/parsing/harmony-destructuring-assignment/counter.js index 21dbf67c4b0..a33b7727575 100644 --- a/test/cases/parsing/harmony-destructuring-assignment/counter.js +++ b/test/cases/parsing/harmony-destructuring-assignment/counter.js @@ -1,7 +1,9 @@ export let counter = 0; export const d = 1; +export const c = 1; export const exportsInfo = { counter: __webpack_exports_info__.counter.used, - d: __webpack_exports_info__.d.used + d: __webpack_exports_info__.d.used, + c: __webpack_exports_info__.c.used }; diff --git a/test/cases/parsing/harmony-destructuring-assignment/counter3.js b/test/cases/parsing/harmony-destructuring-assignment/counter3.js new file mode 100644 index 00000000000..21dbf67c4b0 --- /dev/null +++ b/test/cases/parsing/harmony-destructuring-assignment/counter3.js @@ -0,0 +1,7 @@ +export let counter = 0; +export const d = 1; + +export const exportsInfo = { + counter: __webpack_exports_info__.counter.used, + d: __webpack_exports_info__.d.used +}; diff --git a/test/cases/parsing/harmony-destructuring-assignment/index.js b/test/cases/parsing/harmony-destructuring-assignment/index.js index ec67a32b1fa..6e48bf7a453 100644 --- a/test/cases/parsing/harmony-destructuring-assignment/index.js +++ b/test/cases/parsing/harmony-destructuring-assignment/index.js @@ -2,6 +2,7 @@ import * as C from "./reexport-namespace"; import { counter } from "./reexport-namespace"; import { exportsInfo } from "./counter"; import { exportsInfo as exportsInfo2 } from "./counter2"; +import * as counter3 from "./counter3"; it("expect tree-shake unused exports #1", () => { const { D } = C; @@ -11,14 +12,23 @@ it("expect tree-shake unused exports #1", () => { }); it("expect tree-shake unused exports #2", () => { - const { d } = C.counter; + const { d, c } = C.counter; const { ['d']: d1 } = counter; expect(d).toBe(1); + expect(c).toBe(1); expect(d1).toBe(1); expect(exportsInfo.d).toBe(true); + expect(exportsInfo.c).toBe(true); expect(exportsInfo.counter).toBe(false); }); +it("expect tree-shake bailout when rest element is used", () => { + const { d, ...rest } = counter3; + expect(d).toBe(1); + expect(rest.exportsInfo.d).toBe(true); + expect(rest.exportsInfo.counter).toBe(true); +}); + it("expect no support of \"deep\" tree-shaking", () => { const { counter2: { d } } = C; expect(d).toBe(1); diff --git a/test/cases/parsing/harmony-destructuring-assignment/test.filter.js b/test/cases/parsing/harmony-destructuring-assignment/test.filter.js new file mode 100644 index 00000000000..181167c763e --- /dev/null +++ b/test/cases/parsing/harmony-destructuring-assignment/test.filter.js @@ -0,0 +1,4 @@ +module.exports = function(config) { + // This test can't run in development mode + return config.mode !== "development"; +}; From 45754f45d06f01773f2c3a499591250c9c863f0e Mon Sep 17 00:00:00 2001 From: Ivan Kopeykin Date: Sun, 9 Apr 2023 10:44:09 +0300 Subject: [PATCH 6/7] rename some properties, add more test cases --- lib/DefinePlugin.js | 6 ++--- .../HarmonyImportDependencyParserPlugin.js | 8 +++--- .../HarmonyImportSpecifierDependency.js | 17 ++++++------ lib/javascript/JavascriptParser.js | 26 +++++++++---------- .../counter4.js | 15 +++++++++++ .../harmony-destructuring-assignment/index.js | 18 +++++++++++++ types.d.ts | 6 +++-- 7 files changed, 66 insertions(+), 30 deletions(-) create mode 100644 test/cases/parsing/harmony-destructuring-assignment/counter4.js diff --git a/lib/DefinePlugin.js b/lib/DefinePlugin.js index 1db41f6cd7a..79de6c918ca 100644 --- a/lib/DefinePlugin.js +++ b/lib/DefinePlugin.js @@ -142,7 +142,7 @@ const stringifyObj = ( let keys = Object.keys(obj); if (objKeys) { if (objKeys.size === 0) keys = []; - keys = keys.filter(k => objKeys.has(k)); + else keys = keys.filter(k => objKeys.has(k)); } code = `{${keys .map(key => { @@ -437,7 +437,7 @@ class DefinePlugin { originalKey, runtimeTemplate, !parser.isAsiPosition(expr.range[0]), - parser.destructuringAssignmentKeysFor(expr) + parser.destructuringAssignmentPropertiesFor(expr) ); if (WEBPACK_REQUIRE_FUNCTION_REGEXP.test(strCode)) { return toConstantDependency(parser, strCode, [ @@ -535,7 +535,7 @@ class DefinePlugin { key, runtimeTemplate, !parser.isAsiPosition(expr.range[0]), - parser.destructuringAssignmentKeysFor(expr) + parser.destructuringAssignmentPropertiesFor(expr) ); if (WEBPACK_REQUIRE_FUNCTION_REGEXP.test(strCode)) { diff --git a/lib/dependencies/HarmonyImportDependencyParserPlugin.js b/lib/dependencies/HarmonyImportDependencyParserPlugin.js index 2a2e9cf1002..ba74c9bbcd6 100644 --- a/lib/dependencies/HarmonyImportDependencyParserPlugin.js +++ b/lib/dependencies/HarmonyImportDependencyParserPlugin.js @@ -187,7 +187,6 @@ module.exports = class HarmonyImportDependencyParserPlugin { .for(harmonySpecifierTag) .tap("HarmonyImportDependencyParserPlugin", expr => { const settings = /** @type {HarmonySettings} */ (parser.currentTagData); - const keys = parser.destructuringAssignmentKeysFor(expr); const dep = new HarmonyImportSpecifierDependency( settings.source, settings.sourceOrder, @@ -197,7 +196,8 @@ module.exports = class HarmonyImportDependencyParserPlugin { exportPresenceMode, settings.assertions ); - dep.referencedKeys = keys; + dep.referencedPropertiesInDestructuring = + parser.destructuringAssignmentPropertiesFor(expr); dep.shorthand = parser.scope.inShorthand; dep.directImport = true; dep.asiSafe = !parser.isAsiPosition(expr.range[0]); @@ -225,7 +225,6 @@ module.exports = class HarmonyImportDependencyParserPlugin { members.length - nonOptionalMembers.length ) : expression; - const keys = parser.destructuringAssignmentKeysFor(expr); const ids = settings.ids.concat(nonOptionalMembers); const dep = new HarmonyImportSpecifierDependency( settings.source, @@ -236,7 +235,8 @@ module.exports = class HarmonyImportDependencyParserPlugin { exportPresenceMode, settings.assertions ); - dep.referencedKeys = keys; + dep.referencedPropertiesInDestructuring = + parser.destructuringAssignmentPropertiesFor(expr); dep.asiSafe = !parser.isAsiPosition(expr.range[0]); dep.loc = expr.loc; parser.state.module.addDependency(dep); diff --git a/lib/dependencies/HarmonyImportSpecifierDependency.js b/lib/dependencies/HarmonyImportSpecifierDependency.js index dc3e092ff53..5d7604fa819 100644 --- a/lib/dependencies/HarmonyImportSpecifierDependency.js +++ b/lib/dependencies/HarmonyImportSpecifierDependency.js @@ -53,7 +53,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency { /** @type {Set | boolean} */ this.usedByExports = undefined; /** @type {Set} */ - this.referencedKeys = undefined; + this.referencedPropertiesInDestructuring = undefined; } // TODO webpack 6 remove @@ -123,7 +123,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency { */ getReferencedExports(moduleGraph, runtime) { let ids = this.getIds(moduleGraph); - if (ids.length === 0) return this._getReferencedKeysExports(); + if (ids.length === 0) return this._getReferencedExportsInDestructuring(); let namespaceObjectAsContext = this.namespaceObjectAsContext; if (ids[0] === "default") { const selfModule = moduleGraph.getParentModule(this); @@ -136,7 +136,8 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency { ) { case "default-only": case "default-with-named": - if (ids.length === 1) return this._getReferencedKeysExports(); + if (ids.length === 1) + return this._getReferencedExportsInDestructuring(); ids = ids.slice(1); namespaceObjectAsContext = true; break; @@ -154,18 +155,18 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency { ids = ids.slice(0, -1); } - return this._getReferencedKeysExports(ids); + return this._getReferencedExportsInDestructuring(ids); } /** * @param {string[]=} ids ids * @returns {(string[] | ReferencedExport)[]} referenced exports */ - _getReferencedKeysExports(ids) { - if (this.referencedKeys) { + _getReferencedExportsInDestructuring(ids) { + if (this.referencedPropertiesInDestructuring) { /** @type {ReferencedExport[]} */ const refs = []; - for (const key of this.referencedKeys) { + for (const key of this.referencedPropertiesInDestructuring) { refs.push({ name: ids ? ids.concat([key]) : [key], canMangle: false @@ -248,7 +249,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency { write(this.shorthand); write(this.asiSafe); write(this.usedByExports); - write(this.referencedKeys); + write(this.referencedPropertiesInDestructuring); super.serialize(context); } diff --git a/lib/javascript/JavascriptParser.js b/lib/javascript/JavascriptParser.js index b9fa8a5d7b3..6d9e9e79027 100644 --- a/lib/javascript/JavascriptParser.js +++ b/lib/javascript/JavascriptParser.js @@ -332,7 +332,7 @@ class JavascriptParser extends Parser { this.statementPath = undefined; this.prevStatement = undefined; /** @type {WeakMap>} */ - this.destructuringAssignmentKeys = undefined; + this.destructuringAssignmentProperties = undefined; this.currentTagData = undefined; this._initializeEvaluating(); } @@ -1417,9 +1417,9 @@ class JavascriptParser extends Parser { * @param {ExpressionNode} node node * @returns {Set|undefined} destructured identifiers */ - destructuringAssignmentKeysFor(node) { - if (!this.destructuringAssignmentKeys) return undefined; - return this.destructuringAssignmentKeys.get(node); + destructuringAssignmentPropertiesFor(node) { + if (!this.destructuringAssignmentProperties) return undefined; + return this.destructuringAssignmentProperties.get(node); } getRenameIdentifier(expr) { @@ -1914,20 +1914,20 @@ class JavascriptParser extends Parser { preWalkAssignmentExpression(expression) { if ( expression.left.type !== "ObjectPattern" || - !this.destructuringAssignmentKeys + !this.destructuringAssignmentProperties ) return; const keys = this._preWalkObjectPattern(expression.left); if (!keys) return; // check multiple assignments - if (this.destructuringAssignmentKeys.has(expression)) { - const set = this.destructuringAssignmentKeys.get(expression); - this.destructuringAssignmentKeys.delete(expression); + if (this.destructuringAssignmentProperties.has(expression)) { + const set = this.destructuringAssignmentProperties.get(expression); + this.destructuringAssignmentProperties.delete(expression); for (const id of set) keys.add(id); } - this.destructuringAssignmentKeys.set(expression.right, keys); + this.destructuringAssignmentProperties.set(expression.right, keys); if (expression.right.type === "AssignmentExpression") { this.preWalkAssignmentExpression(expression.right); @@ -2177,13 +2177,13 @@ class JavascriptParser extends Parser { if ( !declarator.init || declarator.id.type !== "ObjectPattern" || - !this.destructuringAssignmentKeys + !this.destructuringAssignmentProperties ) return; const keys = this._preWalkObjectPattern(declarator.id); if (!keys) return; - this.destructuringAssignmentKeys.set(declarator.init, keys); + this.destructuringAssignmentProperties.set(declarator.init, keys); if (declarator.init.type === "AssignmentExpression") { this.preWalkAssignmentExpression(declarator.init); @@ -3453,14 +3453,14 @@ class JavascriptParser extends Parser { this.statementPath = []; this.prevStatement = undefined; if (this.hooks.program.call(ast, comments) === undefined) { - this.destructuringAssignmentKeys = new WeakMap(); + this.destructuringAssignmentProperties = new WeakMap(); this.detectMode(ast.body); this.preWalkStatements(ast.body); this.prevStatement = undefined; this.blockPreWalkStatements(ast.body); this.prevStatement = undefined; this.walkStatements(ast.body); - this.destructuringAssignmentKeys = undefined; + this.destructuringAssignmentProperties = undefined; } this.hooks.finish.call(ast, comments); this.scope = oldScope; diff --git a/test/cases/parsing/harmony-destructuring-assignment/counter4.js b/test/cases/parsing/harmony-destructuring-assignment/counter4.js new file mode 100644 index 00000000000..43eff0ee0a3 --- /dev/null +++ b/test/cases/parsing/harmony-destructuring-assignment/counter4.js @@ -0,0 +1,15 @@ +export let counter = 0; +export const d = 1; +export const c = 1; +export const e = 1; +export const f = 1; +export const g = 1; + +export const exportsInfo = { + counter: __webpack_exports_info__.counter.used, + d: __webpack_exports_info__.d.used, + c: __webpack_exports_info__.c.used, + e: __webpack_exports_info__.e.used, + f: __webpack_exports_info__.f.used, + g: __webpack_exports_info__.g.used +}; diff --git a/test/cases/parsing/harmony-destructuring-assignment/index.js b/test/cases/parsing/harmony-destructuring-assignment/index.js index 6e48bf7a453..42e573e3900 100644 --- a/test/cases/parsing/harmony-destructuring-assignment/index.js +++ b/test/cases/parsing/harmony-destructuring-assignment/index.js @@ -3,6 +3,7 @@ import { counter } from "./reexport-namespace"; import { exportsInfo } from "./counter"; import { exportsInfo as exportsInfo2 } from "./counter2"; import * as counter3 from "./counter3"; +import * as counter4 from "./counter4"; it("expect tree-shake unused exports #1", () => { const { D } = C; @@ -22,6 +23,23 @@ it("expect tree-shake unused exports #2", () => { expect(exportsInfo.counter).toBe(false); }); +it("expect multiple assignment work correctly", () => { + const { e, d: d1 } = counter4; + let c1; + const { f, d: d2 } = { c: c1 } = counter4; + expect(c1).toBe(1); + expect(d1).toBe(1); + expect(d2).toBe(1); + expect(e).toBe(1); + expect(f).toBe(1); + expect(counter4.exportsInfo.c).toBe(true); + expect(counter4.exportsInfo.d).toBe(true); + expect(counter4.exportsInfo.e).toBe(true); + expect(counter4.exportsInfo.f).toBe(true); + expect(counter4.exportsInfo.g).toBe(false); + expect(counter4.exportsInfo.counter).toBe(false); +}); + it("expect tree-shake bailout when rest element is used", () => { const { d, ...rest } = counter3; expect(d).toBe(1); diff --git a/types.d.ts b/types.d.ts index 493be6e8955..deccf6949fd 100644 --- a/types.d.ts +++ b/types.d.ts @@ -5277,9 +5277,11 @@ declare class JavascriptParser extends Parser { | ForOfStatement )[]; prevStatement: any; - destructuringAssignmentKeys: WeakMap>; + destructuringAssignmentProperties: WeakMap>; currentTagData: any; - destructuringAssignmentKeysFor(node: Expression): undefined | Set; + destructuringAssignmentPropertiesFor( + node: Expression + ): undefined | Set; getRenameIdentifier(expr?: any): undefined | string | VariableInfoInterface; walkClass(classy: ClassExpression | ClassDeclaration): void; preWalkStatements(statements?: any): void; From 8c8a3a0a99e494e266aa740d4ee49fb71b344a42 Mon Sep 17 00:00:00 2001 From: Ivan Kopeykin Date: Sun, 9 Apr 2023 11:33:49 +0300 Subject: [PATCH 7/7] fix caching --- lib/dependencies/HarmonyImportSpecifierDependency.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dependencies/HarmonyImportSpecifierDependency.js b/lib/dependencies/HarmonyImportSpecifierDependency.js index 5d7604fa819..6115614bd07 100644 --- a/lib/dependencies/HarmonyImportSpecifierDependency.js +++ b/lib/dependencies/HarmonyImportSpecifierDependency.js @@ -265,7 +265,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency { this.shorthand = read(); this.asiSafe = read(); this.usedByExports = read(); - this.referencedKeys = read(); + this.referencedPropertiesInDestructuring = read(); super.deserialize(context); } }