add size optimization mode to `optimization.mangleExports`

This commit is contained in:
Tobias Koppers 2020-07-31 12:13:30 +02:00
parent dad6464696
commit 4c3216efad
11 changed files with 168 additions and 45 deletions

View File

@ -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.
*/

View File

@ -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");

View File

@ -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);
}
}
);

View File

@ -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.",

View File

@ -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 [

View File

@ -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";

View File

@ -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]
);
});

View File

@ -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";

View File

@ -0,0 +1,5 @@
module.exports = {
findBundle: function () {
return ["./deterministic.js", "./size.js"];
}
};

View File

@ -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")
})
]
}
};
];

4
types.d.ts vendored
View File

@ -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.