Skip to content

Commit

Permalink
Merge pull request #16941 from webpack/feat/destructuring-assignment
Browse files Browse the repository at this point in the history
Add support for destructuring assignment in parser. Enables tree-shaking for destructuring assignment in simple use cases.
  • Loading branch information
TheLarkInn committed Apr 12, 2023
2 parents 254f8aa + 8c8a3a0 commit 4cacd7e
Show file tree
Hide file tree
Showing 14 changed files with 272 additions and 10 deletions.
24 changes: 18 additions & 6 deletions lib/DefinePlugin.js
Expand Up @@ -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<string>|undefined=} objKeys used keys
* @returns {string} code converted to string that evaluates
*/
const stringifyObj = (
Expand All @@ -126,7 +127,8 @@ const stringifyObj = (
valueCacheVersions,
key,
runtimeTemplate,
asiSafe
asiSafe,
objKeys
) => {
let code;
let arr = Array.isArray(obj);
Expand All @@ -137,7 +139,12 @@ const stringifyObj = (
)
.join(",")}]`;
} else {
code = `{${Object.keys(obj)
let keys = Object.keys(obj);
if (objKeys) {
if (objKeys.size === 0) keys = [];
else keys = keys.filter(k => objKeys.has(k));
}
code = `{${keys
.map(key => {
const code = obj[key];
return (
Expand Down Expand Up @@ -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<string>|undefined=} objKeys used keys
* @returns {string} code converted to string that evaluates
*/
const toCode = (
Expand All @@ -177,7 +185,8 @@ const toCode = (
valueCacheVersions,
key,
runtimeTemplate,
asiSafe
asiSafe,
objKeys
) => {
if (code === null) {
return "null";
Expand Down Expand Up @@ -211,7 +220,8 @@ const toCode = (
valueCacheVersions,
key,
runtimeTemplate,
asiSafe
asiSafe,
objKeys
);
}
if (typeof code === "bigint") {
Expand Down Expand Up @@ -426,7 +436,8 @@ class DefinePlugin {
compilation.valueCacheVersions,
originalKey,
runtimeTemplate,
!parser.isAsiPosition(expr.range[0])
!parser.isAsiPosition(expr.range[0]),
parser.destructuringAssignmentPropertiesFor(expr)
);
if (WEBPACK_REQUIRE_FUNCTION_REGEXP.test(strCode)) {
return toConstantDependency(parser, strCode, [
Expand Down Expand Up @@ -523,7 +534,8 @@ class DefinePlugin {
compilation.valueCacheVersions,
key,
runtimeTemplate,
!parser.isAsiPosition(expr.range[0])
!parser.isAsiPosition(expr.range[0]),
parser.destructuringAssignmentPropertiesFor(expr)
);

if (WEBPACK_REQUIRE_FUNCTION_REGEXP.test(strCode)) {
Expand Down
4 changes: 4 additions & 0 deletions lib/dependencies/HarmonyImportDependencyParserPlugin.js
Expand Up @@ -196,6 +196,8 @@ module.exports = class HarmonyImportDependencyParserPlugin {
exportPresenceMode,
settings.assertions
);
dep.referencedPropertiesInDestructuring =
parser.destructuringAssignmentPropertiesFor(expr);
dep.shorthand = parser.scope.inShorthand;
dep.directImport = true;
dep.asiSafe = !parser.isAsiPosition(expr.range[0]);
Expand Down Expand Up @@ -233,6 +235,8 @@ module.exports = class HarmonyImportDependencyParserPlugin {
exportPresenceMode,
settings.assertions
);
dep.referencedPropertiesInDestructuring =
parser.destructuringAssignmentPropertiesFor(expr);
dep.asiSafe = !parser.isAsiPosition(expr.range[0]);
dep.loc = expr.loc;
parser.state.module.addDependency(dep);
Expand Down
31 changes: 28 additions & 3 deletions lib/dependencies/HarmonyImportSpecifierDependency.js
Expand Up @@ -52,6 +52,8 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
this.asiSafe = undefined;
/** @type {Set<string> | boolean} */
this.usedByExports = undefined;
/** @type {Set<string>} */
this.referencedPropertiesInDestructuring = undefined;
}

// TODO webpack 6 remove
Expand Down Expand Up @@ -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._getReferencedExportsInDestructuring();
let namespaceObjectAsContext = this.namespaceObjectAsContext;
if (ids[0] === "default") {
const selfModule = moduleGraph.getParentModule(this);
Expand All @@ -134,7 +136,8 @@ 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._getReferencedExportsInDestructuring();
ids = ids.slice(1);
namespaceObjectAsContext = true;
break;
Expand All @@ -152,7 +155,27 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
ids = ids.slice(0, -1);
}

return [ids];
return this._getReferencedExportsInDestructuring(ids);
}

/**
* @param {string[]=} ids ids
* @returns {(string[] | ReferencedExport)[]} referenced exports
*/
_getReferencedExportsInDestructuring(ids) {
if (this.referencedPropertiesInDestructuring) {
/** @type {ReferencedExport[]} */
const refs = [];
for (const key of this.referencedPropertiesInDestructuring) {
refs.push({
name: ids ? ids.concat([key]) : [key],
canMangle: false
});
}
return refs;
} else {
return ids ? [ids] : Dependency.EXPORTS_OBJECT_REFERENCED;
}
}

/**
Expand Down Expand Up @@ -226,6 +249,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
write(this.shorthand);
write(this.asiSafe);
write(this.usedByExports);
write(this.referencedPropertiesInDestructuring);
super.serialize(context);
}

Expand All @@ -241,6 +265,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
this.shorthand = read();
this.asiSafe = read();
this.usedByExports = read();
this.referencedPropertiesInDestructuring = read();
super.deserialize(context);
}
}
Expand Down
88 changes: 88 additions & 0 deletions lib/javascript/JavascriptParser.js
Expand Up @@ -331,6 +331,8 @@ class JavascriptParser extends Parser {
/** @type {(StatementNode|ExpressionNode)[]} */
this.statementPath = undefined;
this.prevStatement = undefined;
/** @type {WeakMap<ExpressionNode, Set<string>>} */
this.destructuringAssignmentProperties = undefined;
this.currentTagData = undefined;
this._initializeEvaluating();
}
Expand Down Expand Up @@ -1411,6 +1413,15 @@ class JavascriptParser extends Parser {
});
}

/**
* @param {ExpressionNode} node node
* @returns {Set<string>|undefined} destructured identifiers
*/
destructuringAssignmentPropertiesFor(node) {
if (!this.destructuringAssignmentProperties) return undefined;
return this.destructuringAssignmentProperties.get(node);
}

getRenameIdentifier(expr) {
const result = this.evaluateExpression(expr);
if (result.isIdentifier()) {
Expand Down Expand Up @@ -1557,6 +1568,8 @@ class JavascriptParser extends Parser {
case "ClassDeclaration":
this.blockPreWalkClassDeclaration(statement);
break;
case "ExpressionStatement":
this.blockPreWalkExpressionStatement(statement);
}
this.prevStatement = this.statementPath.pop();
}
Expand Down Expand Up @@ -1890,6 +1903,37 @@ 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.destructuringAssignmentProperties
)
return;
const keys = this._preWalkObjectPattern(expression.left);
if (!keys) return;

// check multiple assignments
if (this.destructuringAssignmentProperties.has(expression)) {
const set = this.destructuringAssignmentProperties.get(expression);
this.destructuringAssignmentProperties.delete(expression);
for (const id of set) keys.add(id);
}

this.destructuringAssignmentProperties.set(expression.right, keys);

if (expression.right.type === "AssignmentExpression") {
this.preWalkAssignmentExpression(expression.right);
}
}

blockPreWalkImportDeclaration(statement) {
const source = statement.source.value;
this.hooks.import.call(statement, source);
Expand Down Expand Up @@ -2087,6 +2131,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);
Expand All @@ -2104,6 +2149,47 @@ class JavascriptParser extends Parser {
}
}

_preWalkObjectPattern(objectPattern) {
const ids = new Set();
const properties = objectPattern.properties;
for (let i = 0; i < properties.length; i++) {
const property = properties[i];
if (property.type !== "Property") return;
const key = property.key;
if (key.type === "Identifier") {
ids.add(key.name);
} else {
const id = this.evaluateExpression(key);
const str = id.asString();
if (str) {
ids.add(str);
} else {
// could not evaluate key
return;
}
}
}

return ids;
}

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, keys);

if (declarator.init.type === "AssignmentExpression") {
this.preWalkAssignmentExpression(declarator.init);
}
}

walkVariableDeclaration(statement) {
for (const declarator of statement.declarations) {
switch (declarator.type) {
Expand Down Expand Up @@ -3367,12 +3453,14 @@ class JavascriptParser extends Parser {
this.statementPath = [];
this.prevStatement = undefined;
if (this.hooks.program.call(ast, comments) === undefined) {
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.destructuringAssignmentProperties = undefined;
}
this.hooks.finish.call(ast, comments);
this.scope = oldScope;
Expand Down
@@ -0,0 +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,
c: __webpack_exports_info__.c.used
};
@@ -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
};
@@ -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
};
15 changes: 15 additions & 0 deletions 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
};

0 comments on commit 4cacd7e

Please sign in to comment.