Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: mangle with destructuring #18319

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 12 additions & 2 deletions lib/DefinePlugin.js
Expand Up @@ -27,6 +27,7 @@ const createHash = require("./util/createHash");
/** @typedef {import("./NormalModule")} NormalModule */
/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
/** @typedef {import("./javascript/JavascriptParser").DestructuringAssignmentProperty} DestructuringAssignmentProperty */
/** @typedef {import("./javascript/JavascriptParser").Range} Range */
/** @typedef {import("./logging/Logger").Logger} Logger */

Expand Down Expand Up @@ -114,6 +115,15 @@ class RuntimeValue {
}
}

/**
* @param {Set<DestructuringAssignmentProperty> | undefined} properties properties
* @returns {Set<string> | undefined} used keys
*/
function getObjKeys(properties) {
if (!properties) return undefined;
return new Set([...properties].map(p => p.id));
}

/**
* @param {any[]|{[k: string]: any}} obj obj
* @param {JavascriptParser} parser Parser
Expand Down Expand Up @@ -491,7 +501,7 @@ class DefinePlugin {
runtimeTemplate,
logger,
!parser.isAsiPosition(/** @type {Range} */ (expr.range)[0]),
parser.destructuringAssignmentPropertiesFor(expr)
getObjKeys(parser.destructuringAssignmentPropertiesFor(expr))
);

if (parser.scope.inShorthand) {
Expand Down Expand Up @@ -597,7 +607,7 @@ class DefinePlugin {
runtimeTemplate,
logger,
!parser.isAsiPosition(/** @type {Range} */ (expr.range)[0]),
parser.destructuringAssignmentPropertiesFor(expr)
getObjKeys(parser.destructuringAssignmentPropertiesFor(expr))
);

if (parser.scope.inShorthand) {
Expand Down
151 changes: 151 additions & 0 deletions lib/dependencies/DestructuringAssignmentPropertyKeyDependency.js
@@ -0,0 +1,151 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Gengkun He @ahabhgk
*/

"use strict";

const Template = require("../Template");
const makeSerializable = require("../util/makeSerializable");
const NullDependency = require("./NullDependency");

/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
/** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../javascript/JavascriptParser").DestructuringAssignmentProperty} DestructuringAssignmentProperty */
/** @typedef {import("../javascript/JavascriptParser").Range} Range */
/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
/** @typedef {import("./HarmonyImportSpecifierDependency")} HarmonyImportSpecifierDependency */

/**
* @param {DestructuringAssignmentProperty} property property
* @param {((property: DestructuringAssignmentProperty) => void) | undefined=} onProperty on property
* @param {((property: DestructuringAssignmentProperty) => void) | undefined=} onClean on property end
* @param {(() => void) | undefined=} onTerminate on a terminal property
*/
function traversePropertyInDestructuring(
property,
onProperty,
onClean,
onTerminate
) {
onProperty && onProperty(property);
if (!property.pattern) {
onTerminate && onTerminate();
} else {
for (const p of property.pattern) {
traversePropertyInDestructuring(p, onProperty, onClean, onTerminate);
}
}
onClean && onClean(property);
}

/**
* @param {Set<DestructuringAssignmentProperty>} properties properties
* @returns {string[][]} all ids
*/
function getAllIdsInDestructuring(properties) {
/** @type {string[][]} */
const allIds = [];
for (const p of properties) {
const ids = [];
traversePropertyInDestructuring(
p,
p => ids.push(p.id),
p => ids.pop(),
() => allIds.push([...ids])
);
}
return allIds;
}

class DestructuringAssignmentPropertyKeyDependency extends NullDependency {
/**
* @param {string[]} ids ids
* @param {HarmonyImportSpecifierDependency} specifier import specifier
* @param {Range} range range of the property key id
* @param {boolean | string} shorthand destructuring property is in shorthand
*/
constructor(ids, specifier, range, shorthand) {
super();
this.ids = ids;
this.specifier = specifier;
this.range = range;
this.shorthand = shorthand;
}

get type() {
return "destructuring assignment property id";
}

/**
* @param {ObjectSerializerContext} context context
*/
serialize(context) {
const { write } = context;
write(this.ids);
write(this.specifier);
write(this.range);
write(this.shorthand);
super.serialize(context);
}

/**
* @param {ObjectDeserializerContext} context context
*/
deserialize(context) {
const { read } = context;
this.ids = read();
this.specifier = read();
this.range = read();
this.shorthand = read();
super.deserialize(context);
}
}

DestructuringAssignmentPropertyKeyDependency.Template = class DestructuringAssignmentPropertyIdDependencyTemplate extends (
NullDependency.Template
) {
/**
* @param {Dependency} dependency the dependency for which the template should be applied
* @param {ReplaceSource} source the current replace source which can be modified
* @param {DependencyTemplateContext} templateContext the context object
* @returns {void}
*/
apply(dependency, source, { moduleGraph, runtime }) {
const dep = /** @type {DestructuringAssignmentPropertyKeyDependency} */ (
dependency
);
const { ids, specifier, shorthand } = dep;
const specifierIds = specifier.ids.slice(
specifier.ids[0] === "default" ? 1 : 0
);
const module = moduleGraph.getModule(specifier);
const used = moduleGraph
.getExportsInfo(module)
.getUsedName(specifierIds.concat(ids), runtime);
if (!used) return;
const newName = used[used.length - 1];
const name = ids[ids.length - 1];
if (newName === name) return;

const comment = Template.toNormalComment(name) + " ";
const key = comment + JSON.stringify(newName);
source.replace(
dep.range[0],
dep.range[1] - 1,
shorthand ? `${key}: ${name}` : `${key}`
);
}
};

makeSerializable(
DestructuringAssignmentPropertyKeyDependency,
"webpack/lib/dependencies/DestructuringAssignmentPropertyKeyDependency"
);

module.exports = DestructuringAssignmentPropertyKeyDependency;
module.exports.traversePropertyInDestructuring =
traversePropertyInDestructuring;
module.exports.getAllIdsInDestructuring = getAllIdsInDestructuring;
46 changes: 46 additions & 0 deletions lib/dependencies/HarmonyImportDependencyParserPlugin.js
Expand Up @@ -8,6 +8,7 @@
const HotModuleReplacementPlugin = require("../HotModuleReplacementPlugin");
const InnerGraph = require("../optimize/InnerGraph");
const ConstDependency = require("./ConstDependency");
const DestructuringAssignmentPropertyKeyDependency = require("./DestructuringAssignmentPropertyKeyDependency");
const HarmonyAcceptDependency = require("./HarmonyAcceptDependency");
const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency");
const HarmonyEvaluatedImportSpecifierDependency = require("./HarmonyEvaluatedImportSpecifierDependency");
Expand All @@ -29,6 +30,7 @@ const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDepend
/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */
/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
/** @typedef {import("../javascript/JavascriptParser").DestructuringAssignmentProperty} DestructuringAssignmentProperty */
/** @typedef {import("../javascript/JavascriptParser").Range} Range */
/** @typedef {import("../optimize/InnerGraph").InnerGraph} InnerGraph */
/** @typedef {import("../optimize/InnerGraph").TopLevelSymbol} TopLevelSymbol */
Expand Down Expand Up @@ -92,6 +94,36 @@ function getAttributes(node) {
return result;
}

/**
* @param {Set<DestructuringAssignmentProperty>} properties properties
* @param {HarmonyImportSpecifierDependency} specifier import specifier dependency
* @param {JavascriptParser} parser javascript parser
*/
function replaceDestructuringAssignmentPropertiesKeys(
properties,
specifier,
parser
) {
for (const p of properties) {
const ids = [];
DestructuringAssignmentPropertyKeyDependency.traversePropertyInDestructuring(
p,
p => {
ids.push(p.id);
if (!p.range) return;
const dep = new DestructuringAssignmentPropertyKeyDependency(
[...ids],
specifier,
p.range,
p.shorthand
);
parser.state.module.addPresentationalDependency(dep);
},
() => ids.pop()
);
}
}

module.exports = class HarmonyImportDependencyParserPlugin {
/**
* @param {JavascriptParserOptions} options options
Expand Down Expand Up @@ -234,6 +266,13 @@ module.exports = class HarmonyImportDependencyParserPlugin {
);
dep.referencedPropertiesInDestructuring =
parser.destructuringAssignmentPropertiesFor(expr);
if (dep.referencedPropertiesInDestructuring) {
replaceDestructuringAssignmentPropertiesKeys(
dep.referencedPropertiesInDestructuring,
dep,
parser
);
}
dep.shorthand = parser.scope.inShorthand;
dep.directImport = true;
dep.asiSafe = !parser.isAsiPosition(
Expand Down Expand Up @@ -281,6 +320,13 @@ module.exports = class HarmonyImportDependencyParserPlugin {
);
dep.referencedPropertiesInDestructuring =
parser.destructuringAssignmentPropertiesFor(expr);
if (dep.referencedPropertiesInDestructuring) {
replaceDestructuringAssignmentPropertiesKeys(
dep.referencedPropertiesInDestructuring,
dep,
parser
);
}
dep.asiSafe = !parser.isAsiPosition(
/** @type {Range} */ (expr.range)[0]
);
Expand Down
32 changes: 14 additions & 18 deletions lib/dependencies/HarmonyImportSpecifierDependency.js
Expand Up @@ -12,6 +12,9 @@ const {
const { getTrimmedIdsAndRange } = require("../util/chainedImports");
const makeSerializable = require("../util/makeSerializable");
const propertyAccess = require("../util/propertyAccess");
const {
getAllIdsInDestructuring
} = require("./DestructuringAssignmentPropertyKeyDependency");
const HarmonyImportDependency = require("./HarmonyImportDependency");

/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
Expand All @@ -28,6 +31,7 @@ const HarmonyImportDependency = require("./HarmonyImportDependency");
/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */
/** @typedef {import("../WebpackError")} WebpackError */
/** @typedef {import("../javascript/JavascriptParser").Attributes} Attributes */
/** @typedef {import("../javascript/JavascriptParser").DestructuringAssignmentProperty} DestructuringAssignmentProperty */
/** @typedef {import("../javascript/JavascriptParser").Range} Range */
/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
Expand Down Expand Up @@ -73,7 +77,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
this.asiSafe = undefined;
/** @type {Set<string> | boolean | undefined} */
this.usedByExports = undefined;
/** @type {Set<string> | undefined} */
/** @type {Set<DestructuringAssignmentProperty> | undefined} */
this.referencedPropertiesInDestructuring = undefined;
}

Expand Down Expand Up @@ -144,8 +148,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
*/
getReferencedExports(moduleGraph, runtime) {
let ids = this.getIds(moduleGraph);
if (ids.length === 0)
return this._getReferencedExportsInDestructuring(moduleGraph);
if (ids.length === 0) return this._getReferencedExportsInDestructuring();
let namespaceObjectAsContext = this.namespaceObjectAsContext;
if (ids[0] === "default") {
const selfModule = moduleGraph.getParentModule(this);
Expand All @@ -162,7 +165,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
case "default-only":
case "default-with-named":
if (ids.length === 1)
return this._getReferencedExportsInDestructuring(moduleGraph);
return this._getReferencedExportsInDestructuring();
ids = ids.slice(1);
namespaceObjectAsContext = true;
break;
Expand All @@ -180,30 +183,23 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
ids = ids.slice(0, -1);
}

return this._getReferencedExportsInDestructuring(moduleGraph, ids);
return this._getReferencedExportsInDestructuring(ids);
}

/**
* @param {ModuleGraph} moduleGraph module graph
* @param {string[]=} ids ids
* @returns {(string[] | ReferencedExport)[]} referenced exports
*/
_getReferencedExportsInDestructuring(moduleGraph, ids) {
_getReferencedExportsInDestructuring(ids) {
if (this.referencedPropertiesInDestructuring) {
/** @type {ReferencedExport[]} */
const refs = [];
const importedModule = moduleGraph.getModule(this);
const canMangle =
Array.isArray(ids) &&
ids.length > 0 &&
!moduleGraph
.getExportsInfo(importedModule)
.getExportInfo(ids[0])
.isReexport();
for (const key of this.referencedPropertiesInDestructuring) {
for (const idsInDestructuring of getAllIdsInDestructuring(
this.referencedPropertiesInDestructuring
)) {
refs.push({
name: ids ? ids.concat([key]) : [key],
canMangle
name: ids ? ids.concat(idsInDestructuring) : idsInDestructuring,
canMangle: true
});
}
return refs;
Expand Down
10 changes: 10 additions & 0 deletions lib/dependencies/HarmonyModulesPlugin.js
Expand Up @@ -20,6 +20,7 @@ const {
JAVASCRIPT_MODULE_TYPE_AUTO,
JAVASCRIPT_MODULE_TYPE_ESM
} = require("../ModuleTypeConstants");
const DestructuringAssignmentPropertyKeyDependency = require("./DestructuringAssignmentPropertyKeyDependency");
const HarmonyDetectionParserPlugin = require("./HarmonyDetectionParserPlugin");
const HarmonyExportDependencyParserPlugin = require("./HarmonyExportDependencyParserPlugin");
const HarmonyImportDependencyParserPlugin = require("./HarmonyImportDependencyParserPlugin");
Expand Down Expand Up @@ -120,6 +121,15 @@ class HarmonyModulesPlugin {
new HarmonyAcceptImportDependency.Template()
);

compilation.dependencyFactories.set(
DestructuringAssignmentPropertyKeyDependency,
normalModuleFactory
);
compilation.dependencyTemplates.set(
DestructuringAssignmentPropertyKeyDependency,
new DestructuringAssignmentPropertyKeyDependency.Template()
);

/**
* @param {Parser} parser parser parser
* @param {JavascriptParserOptions} parserOptions parserOptions
Expand Down
2 changes: 1 addition & 1 deletion lib/dependencies/ImportMetaPlugin.js
Expand Up @@ -131,7 +131,7 @@ class ImportMetaPlugin {
}

let str = "";
for (const prop of referencedPropertiesInDestructuring) {
for (const { id: prop } of referencedPropertiesInDestructuring) {
switch (prop) {
case "url":
str += `url: ${importMetaUrl()},`;
Expand Down