mirror of https://github.com/webpack/webpack.git
add size optimization mode to `optimization.mangleExports`
This commit is contained in:
parent
dad6464696
commit
4c3216efad
|
@ -1292,9 +1292,9 @@ export interface Optimization {
|
|||
*/
|
||||
innerGraph?: boolean;
|
||||
/**
|
||||
* Rename exports when possible to generate shorter code (depends on optimization.usedExports and optimization.providedExports).
|
||||
* Rename exports when possible to generate shorter code (depends on optimization.usedExports and optimization.providedExports, true/"deterministic": generate short deterministic names optimized for caching, "size": generate the shortest possible names).
|
||||
*/
|
||||
mangleExports?: boolean;
|
||||
mangleExports?: ("size" | "deterministic") | boolean;
|
||||
/**
|
||||
* Reduce size of WASM by changing imports to shorter strings.
|
||||
*/
|
||||
|
|
|
@ -394,7 +394,9 @@ class WebpackOptionsApply extends OptionsApply {
|
|||
}
|
||||
if (options.optimization.mangleExports) {
|
||||
const MangleExportsPlugin = require("./optimize/MangleExportsPlugin");
|
||||
new MangleExportsPlugin().apply(compiler);
|
||||
new MangleExportsPlugin(
|
||||
options.optimization.mangleExports !== "size"
|
||||
).apply(compiler);
|
||||
}
|
||||
if (options.optimization.concatenateModules) {
|
||||
const ModuleConcatenationPlugin = require("./optimize/ModuleConcatenationPlugin");
|
||||
|
|
|
@ -16,6 +16,7 @@ const { compareSelect, compareStringsNumeric } = require("../util/comparators");
|
|||
|
||||
/** @typedef {import("../Compiler")} Compiler */
|
||||
/** @typedef {import("../ExportsInfo")} ExportsInfo */
|
||||
/** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */
|
||||
|
||||
const OBJECT = [];
|
||||
const ARRAY = [];
|
||||
|
@ -39,13 +40,15 @@ const canMangle = exportsInfo => {
|
|||
// Sort by name
|
||||
const comparator = compareSelect(e => e.name, compareStringsNumeric);
|
||||
/**
|
||||
* @param {boolean} deterministic use deterministic names
|
||||
* @param {ExportsInfo} exportsInfo exports info
|
||||
* @param {boolean} canBeArray can be exports info point to an array
|
||||
* @returns {void}
|
||||
*/
|
||||
const mangleExportsInfo = (exportsInfo, canBeArray) => {
|
||||
const mangleExportsInfo = (deterministic, exportsInfo, canBeArray) => {
|
||||
if (!canMangle(exportsInfo)) return;
|
||||
const usedNames = new Set();
|
||||
/** @type {ExportInfo[]} */
|
||||
const mangleableExports = [];
|
||||
const empty = canBeArray ? ARRAY : OBJECT;
|
||||
// Don't rename 1-2 char exports or exports that can't be mangled
|
||||
|
@ -55,7 +58,8 @@ const mangleExportsInfo = (exportsInfo, canBeArray) => {
|
|||
if (
|
||||
exportInfo.canMangle !== true ||
|
||||
(name.length === 1 && /^[a-zA-Z0-9_$]/.test(name)) ||
|
||||
(name.length === 2 &&
|
||||
(deterministic &&
|
||||
name.length === 2 &&
|
||||
/^[a-zA-Z_$][a-zA-Z0-9_$]|^[1-9][0-9]/.test(name)) ||
|
||||
(exportInfo.provided !== true && exportInfo.name in empty)
|
||||
) {
|
||||
|
@ -71,38 +75,70 @@ const mangleExportsInfo = (exportsInfo, canBeArray) => {
|
|||
used === UsageState.OnlyPropertiesUsed ||
|
||||
used === UsageState.Unused
|
||||
) {
|
||||
mangleExportsInfo(exportInfo.exportsInfo, true);
|
||||
mangleExportsInfo(deterministic, exportInfo.exportsInfo, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deterministic) {
|
||||
assignDeterministicIds(
|
||||
mangleableExports,
|
||||
e => e.name,
|
||||
comparator,
|
||||
(e, id) => {
|
||||
const name = numberToIdentifier(id);
|
||||
const size = usedNames.size;
|
||||
usedNames.add(name);
|
||||
if (size === usedNames.size) return false;
|
||||
e.setUsedName(name);
|
||||
return true;
|
||||
},
|
||||
[
|
||||
NUMBER_OF_IDENTIFIER_START_CHARS,
|
||||
NUMBER_OF_IDENTIFIER_START_CHARS *
|
||||
NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS
|
||||
],
|
||||
NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS,
|
||||
usedNames.size
|
||||
);
|
||||
} else {
|
||||
const usedExports = [];
|
||||
const unusedExports = [];
|
||||
for (const exportInfo of mangleableExports) {
|
||||
if (exportInfo.getUsed(undefined) === UsageState.Unused) {
|
||||
unusedExports.push(exportInfo);
|
||||
} else {
|
||||
usedExports.push(exportInfo);
|
||||
}
|
||||
}
|
||||
usedExports.sort(comparator);
|
||||
unusedExports.sort(comparator);
|
||||
let i = 0;
|
||||
for (const list of [usedExports, unusedExports]) {
|
||||
for (const exportInfo of list) {
|
||||
let name;
|
||||
do {
|
||||
name = numberToIdentifier(i++);
|
||||
} while (usedNames.has(name));
|
||||
exportInfo.setUsedName(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
assignDeterministicIds(
|
||||
mangleableExports,
|
||||
e => e.name,
|
||||
comparator,
|
||||
(e, id) => {
|
||||
const name = numberToIdentifier(id);
|
||||
const size = usedNames.size;
|
||||
usedNames.add(name);
|
||||
if (size === usedNames.size) return false;
|
||||
e.setUsedName(name);
|
||||
return true;
|
||||
},
|
||||
[
|
||||
NUMBER_OF_IDENTIFIER_START_CHARS,
|
||||
NUMBER_OF_IDENTIFIER_START_CHARS * NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS
|
||||
],
|
||||
NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS,
|
||||
usedNames.size
|
||||
);
|
||||
};
|
||||
|
||||
class MangleExportsPlugin {
|
||||
/**
|
||||
* @param {boolean} deterministic use deterministic names
|
||||
*/
|
||||
constructor(deterministic) {
|
||||
this._deterministic = deterministic;
|
||||
}
|
||||
/**
|
||||
* Apply the plugin
|
||||
* @param {Compiler} compiler the compiler instance
|
||||
* @returns {void}
|
||||
*/
|
||||
apply(compiler) {
|
||||
const { _deterministic: deterministic } = this;
|
||||
compiler.hooks.compilation.tap("MangleExportsPlugin", compilation => {
|
||||
const moduleGraph = compilation.moduleGraph;
|
||||
compilation.hooks.optimizeCodeGeneration.tap(
|
||||
|
@ -110,7 +146,7 @@ class MangleExportsPlugin {
|
|||
modules => {
|
||||
for (const module of modules) {
|
||||
const exportsInfo = moduleGraph.getExportsInfo(module);
|
||||
mangleExportsInfo(exportsInfo, false);
|
||||
mangleExportsInfo(deterministic, exportsInfo, false);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1109,8 +1109,15 @@
|
|||
"type": "boolean"
|
||||
},
|
||||
"mangleExports": {
|
||||
"description": "Rename exports when possible to generate shorter code (depends on optimization.usedExports and optimization.providedExports).",
|
||||
"type": "boolean"
|
||||
"description": "Rename exports when possible to generate shorter code (depends on optimization.usedExports and optimization.providedExports, true/\"deterministic\": generate short deterministic names optimized for caching, \"size\": generate the shortest possible names).",
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": ["size", "deterministic"]
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
}
|
||||
]
|
||||
},
|
||||
"mangleWasmImports": {
|
||||
"description": "Reduce size of WASM by changing imports to shorter strings.",
|
||||
|
|
|
@ -1292,15 +1292,25 @@ Object {
|
|||
"optimization-mangle-exports": Object {
|
||||
"configs": Array [
|
||||
Object {
|
||||
"description": "Rename exports when possible to generate shorter code (depends on optimization.usedExports and optimization.providedExports).",
|
||||
"description": "Rename exports when possible to generate shorter code (depends on optimization.usedExports and optimization.providedExports, true/\\"deterministic\\": generate short deterministic names optimized for caching, \\"size\\": generate the shortest possible names).",
|
||||
"multiple": false,
|
||||
"path": "optimization.mangleExports",
|
||||
"type": "enum",
|
||||
"values": Array [
|
||||
"size",
|
||||
"deterministic",
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"description": "Rename exports when possible to generate shorter code (depends on optimization.usedExports and optimization.providedExports, true/\\"deterministic\\": generate short deterministic names optimized for caching, \\"size\\": generate the shortest possible names).",
|
||||
"multiple": false,
|
||||
"path": "optimization.mangleExports",
|
||||
"type": "boolean",
|
||||
},
|
||||
],
|
||||
"description": "Rename exports when possible to generate shorter code (depends on optimization.usedExports and optimization.providedExports).",
|
||||
"description": "Rename exports when possible to generate shorter code (depends on optimization.usedExports and optimization.providedExports, true/\\"deterministic\\": generate short deterministic names optimized for caching, \\"size\\": generate the shortest possible names).",
|
||||
"multiple": false,
|
||||
"simpleType": "boolean",
|
||||
"simpleType": "string",
|
||||
},
|
||||
"optimization-mangle-wasm-imports": Object {
|
||||
"configs": Array [
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
exports.abc = "abc";
|
||||
exports.def = "def";
|
||||
exports.toString = () => "toString";
|
||||
exports.setToString = () => {
|
||||
exports.toString = () => "toString";
|
||||
};
|
||||
exports.moduleId = module.id;
|
||||
exports.a = "single char";
|
||||
exports["="] = "single char non-identifier";
|
||||
exports.$1 = "double char";
|
||||
exports.__1 = "3 chars";
|
||||
|
|
|
@ -1,24 +1,49 @@
|
|||
import { moduleId, toString, abc } from "./module";
|
||||
import { moduleId, setToString, toString, abc, a, $1, __1 } from "./module";
|
||||
const moduleId2 = require("./commonjs").moduleId;
|
||||
const toString2 = require("./commonjs").toString;
|
||||
const setToString2 = require("./commonjs").setToString;
|
||||
const abc2 = require("./commonjs").abc;
|
||||
const a2 = require("./commonjs").a;
|
||||
const equals2 = require("./commonjs")["="];
|
||||
const $12 = require("./commonjs").$1;
|
||||
const __12 = require("./commonjs").__1;
|
||||
|
||||
it("should mangle names and remove exports even with toString named export (ESM)", () => {
|
||||
expect(abc).toBe("abc");
|
||||
expect(toString).toBe(undefined);
|
||||
setToString();
|
||||
expect(toString()).toBe("toString");
|
||||
expect(a).toBe("single char");
|
||||
expect($1).toBe("double char");
|
||||
expect(__1).toBe("3 chars");
|
||||
expect(
|
||||
Object.keys(require.cache[moduleId].exports)
|
||||
.map(p => p.length)
|
||||
.sort()
|
||||
).toEqual([2, 2, 2]);
|
||||
).toEqual(
|
||||
OPTIMIZATION === "deterministic"
|
||||
? [1, 2, 2, 2, 2, 2, 2]
|
||||
: [1, 1, 1, 1, 1, 1, 1]
|
||||
);
|
||||
});
|
||||
|
||||
it("should mangle names and remove exports even with toString named export (CJS)", () => {
|
||||
expect(abc2).toBe("abc");
|
||||
expect(toString2()).toBe("toString");
|
||||
expect(toString2).toBe(Object.prototype.toString);
|
||||
setToString2();
|
||||
const toString3 = require("./commonjs").toString;
|
||||
expect(toString3()).toBe("toString");
|
||||
expect(a2).toBe("single char");
|
||||
expect(equals2).toBe("single char non-identifier");
|
||||
expect($12).toBe("double char");
|
||||
expect(__12).toBe("3 chars");
|
||||
expect(
|
||||
Object.keys(require.cache[moduleId2].exports)
|
||||
.map(p => p.length)
|
||||
.sort()
|
||||
).toEqual([2, 2, 8]);
|
||||
).toEqual(
|
||||
OPTIMIZATION === "deterministic"
|
||||
? [1, 2, 2, 2, 2, 2, 2, 8]
|
||||
: [1, 1, 1, 1, 1, 1, 1, 8]
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
export const abc = "abc";
|
||||
export const def = "def";
|
||||
export const toString = () => "toString";
|
||||
export let toString;
|
||||
export const setToString = () => {
|
||||
toString = () => "toString";
|
||||
};
|
||||
export const moduleId = module.id;
|
||||
export const a = "single char";
|
||||
export const $1 = "double char";
|
||||
export const __1 = "3 chars";
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
findBundle: function () {
|
||||
return ["./deterministic.js", "./size.js"];
|
||||
}
|
||||
};
|
|
@ -1,8 +1,34 @@
|
|||
const { DefinePlugin } = require("../../../../");
|
||||
/** @type {import("../../../../").Configuration} */
|
||||
module.exports = {
|
||||
optimization: {
|
||||
mangleExports: true,
|
||||
usedExports: true,
|
||||
providedExports: true
|
||||
module.exports = [
|
||||
{
|
||||
output: {
|
||||
filename: "deterministic.js"
|
||||
},
|
||||
optimization: {
|
||||
mangleExports: true,
|
||||
usedExports: true,
|
||||
providedExports: true
|
||||
},
|
||||
plugins: [
|
||||
new DefinePlugin({
|
||||
OPTIMIZATION: JSON.stringify("deterministic")
|
||||
})
|
||||
]
|
||||
},
|
||||
{
|
||||
output: {
|
||||
filename: "size.js"
|
||||
},
|
||||
optimization: {
|
||||
mangleExports: "size",
|
||||
usedExports: true,
|
||||
providedExports: true
|
||||
},
|
||||
plugins: [
|
||||
new DefinePlugin({
|
||||
OPTIMIZATION: JSON.stringify("size")
|
||||
})
|
||||
]
|
||||
}
|
||||
};
|
||||
];
|
||||
|
|
|
@ -5016,9 +5016,9 @@ declare interface Optimization {
|
|||
innerGraph?: boolean;
|
||||
|
||||
/**
|
||||
* Rename exports when possible to generate shorter code (depends on optimization.usedExports and optimization.providedExports).
|
||||
* Rename exports when possible to generate shorter code (depends on optimization.usedExports and optimization.providedExports, true/"deterministic": generate short deterministic names optimized for caching, "size": generate the shortest possible names).
|
||||
*/
|
||||
mangleExports?: boolean;
|
||||
mangleExports?: boolean | "deterministic" | "size";
|
||||
|
||||
/**
|
||||
* Reduce size of WASM by changing imports to shorter strings.
|
||||
|
|
Loading…
Reference in New Issue