Skip to content

Commit ded4ce5

Browse files
feat(javascript): honor pure_funcs, drop_console, global_defs and top_retain when minifying
The one pass now reads terser's compress options that name calls, globals and names, which it accepted and ignored: - `pure_funcs` marks the calls whose callee is written as named pure; - `drop_console` drops a global `console` method's call with its arguments, every method or the ones listed; - `global_defs` replaces a global nothing sets with its primitive value, so `if (DEBUG)` folds; - `drop_debugger` drops `debugger` statements unless turned off; - `top_retain` keeps the top-level names it lists, matches or answers; - `keep_infinity` prints `Infinity` as written. The swc and oxc cases the output now matches are retired. Claude-Session: https://claude.ai/code/session_01Tgxgyb4N35ndP6cC4KcrXC
1 parent 353202a commit ded4ce5

6 files changed

Lines changed: 259 additions & 29 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"webpack": minor
3+
---
4+
5+
Honor `pure_funcs`, `drop_console`, `global_defs` and `top_retain` in minify.

‎lib/javascript/syntax-printer.js‎

Lines changed: 175 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,55 @@ const mapToObject = (map) => {
314314
const keepName = (setting, name) =>
315315
setting === true || (setting instanceof RegExp && setting.test(name));
316316

317+
/**
318+
* terser's `top_retain`, read into what answers whether it keeps a name: a
319+
* list of names, one string of them split by commas, a pattern, or a test.
320+
* @param {CompressOptions["top_retain"] | undefined} setting the option
321+
* @returns {((definition: NamedDefinition) => boolean) | null} the test, null where it keeps none
322+
*/
323+
const topRetainOf = (setting) => {
324+
if (!setting) return null;
325+
if (typeof setting === "function") return setting;
326+
if (setting instanceof RegExp) {
327+
return (definition) => {
328+
setting.lastIndex = 0;
329+
return setting.test(definition.name);
330+
};
331+
}
332+
const names = new Set(
333+
typeof setting === "string"
334+
? setting.split(/,/).map((name) => name.trim())
335+
: setting
336+
);
337+
return (definition) => names.has(definition.name);
338+
};
339+
340+
/**
341+
* terser's `global_defs`, the globals it replaces with a value, kept where the
342+
* value is a primitive one a literal prints; a node or code, terser's `@`
343+
* form, is not read.
344+
* @param {CompressOptions["global_defs"] | undefined} setting the option
345+
* @returns {Map<string, string | number | boolean | null> | null} the values by name, null where none
346+
*/
347+
const globalDefsOf = (setting) => {
348+
if (!setting) return null;
349+
/** @type {Map<string, string | number | boolean | null>} */
350+
const values = new Map();
351+
for (const name of Object.keys(setting)) {
352+
const value = setting[name];
353+
if (
354+
name[0] !== "@" &&
355+
(value === null ||
356+
typeof value === "string" ||
357+
typeof value === "number" ||
358+
typeof value === "boolean")
359+
) {
360+
values.set(name, value);
361+
}
362+
}
363+
return values.size === 0 ? null : values;
364+
};
365+
317366
/**
318367
* Where mangled names come from: the name for each number, and, for a source
319368
* ordering its names by the characters printed, what counts them.
@@ -5601,6 +5650,21 @@ const createNodePrinter = (modules) => {
56015650
// Whether converting what may be an object runs code, terser's
56025651
// `pure_conversions` turned off.
56035652
let comparesConvert = false;
5653+
// Whether a `debugger` statement goes, and a global `Infinity` prints as written.
5654+
let dropsDebugger = false;
5655+
let keepsInfinity = false;
5656+
// The calls `pure_funcs` names, by how their callee is written.
5657+
/** @type {Set<string> | null} */
5658+
let pureFunctions = null;
5659+
// The `console` methods whose calls go, true for every one.
5660+
/** @type {Set<string> | true | null} */
5661+
let droppedConsole = null;
5662+
// The globals `global_defs` replaces, by name, each with a primitive value.
5663+
/** @type {Map<string, string | number | boolean | null> | null} */
5664+
let globalDefs = null;
5665+
// What answers whether `top_retain` keeps a top-level name.
5666+
/** @type {((definition: MinifierVariable) => boolean) | null} */
5667+
let retainsTop = null;
56045668
// Whether a computed key of a constant prints as the name it converts to,
56055669
// terser's `computed_props`.
56065670
let liftsKeys = false;
@@ -7933,7 +7997,8 @@ const createNodePrinter = (modules) => {
79337997
definition.identifiers.length <= 1 &&
79347998
definition.export === 0 &&
79357999
!definition.undeclared &&
7936-
(!definition.global || dropsToplevel) &&
8000+
(!definition.global ||
8001+
(dropsToplevel && (retainsTop === null || !retainsTop(definition)))) &&
79378002
!(/** @type {MinifierScope} */ (definition.scope).isPinned())
79388003
);
79398004
};
@@ -8108,6 +8173,68 @@ const createNodePrinter = (modules) => {
81088173
return true;
81098174
};
81108175

8176+
/**
8177+
* @param {number} node a name
8178+
* @returns {string | number | boolean | null | undefined} the value `global_defs` gives it, where it reads a global nothing assigns
8179+
*/
8180+
const globalDefOf = (node) => {
8181+
const name = tree.name(node);
8182+
const value = /** @type {Map<string, string | number | boolean | null>} */ (
8183+
globalDefs
8184+
).get(name);
8185+
return value !== undefined && isGlobalUndefined(node, name)
8186+
? value
8187+
: undefined;
8188+
};
8189+
8190+
/**
8191+
* A callee as `pure_funcs` names it: a name or a chain of names read off
8192+
* it with dots, as written.
8193+
* @param {number} node the callee, past parentheses
8194+
* @returns {string} its text, empty for any other callee
8195+
*/
8196+
const writtenCallee = (node) => {
8197+
const type = typeOf(node);
8198+
if (type === NodeType.Identifier) return tree.name(node);
8199+
if (
8200+
type !== NodeType.MemberExpression ||
8201+
tree.flag(Flag.computed, node) ||
8202+
tree.flag(Flag.optional, node)
8203+
) {
8204+
return "";
8205+
}
8206+
const object = writtenCallee(childOf(Field.object, node));
8207+
const property = childOf(Field.property, node);
8208+
return object === "" || typeOf(property) !== NodeType.Identifier
8209+
? ""
8210+
: `${object}.${tree.name(property)}`;
8211+
};
8212+
8213+
/**
8214+
* @param {number} node an expression, past parentheses
8215+
* @returns {boolean} whether it is a call of a `console` method `drop_console` drops, on the global `console`
8216+
*/
8217+
const callsDroppedConsole = (node) => {
8218+
if (droppedConsole === null || typeOf(node) !== NodeType.CallExpression) {
8219+
return false;
8220+
}
8221+
const callee = childOf(Field.callee, node);
8222+
if (
8223+
typeOf(callee) !== NodeType.MemberExpression ||
8224+
tree.flag(Flag.computed, callee)
8225+
) {
8226+
return false;
8227+
}
8228+
const object = childOf(Field.object, callee);
8229+
const property = childOf(Field.property, callee);
8230+
return (
8231+
typeOf(object) === NodeType.Identifier &&
8232+
isGlobalUndefined(object, "console") &&
8233+
typeOf(property) === NodeType.Identifier &&
8234+
(droppedConsole === true || droppedConsole.has(tree.name(property)))
8235+
);
8236+
};
8237+
81118238
/**
81128239
* @param {number} node a call or `new`
81138240
* @returns {boolean} whether a `#__PURE__` comment before it marks it pure, where `side_effects` reads them
@@ -8122,6 +8249,12 @@ const createNodePrinter = (modules) => {
81228249
return true;
81238250
}
81248251
if (constructsRegExp(node)) return true;
8252+
if (
8253+
pureFunctions !== null &&
8254+
pureFunctions.has(writtenCallee(childOf(Field.callee, node)))
8255+
) {
8256+
return true;
8257+
}
81258258
if (!readsPureAnnotations || commentTokens === null) return false;
81268259
const tables = output.tokenTables;
81278260
const table = /** @type {TokenTable} */ (tables.last);
@@ -8349,6 +8482,10 @@ const createNodePrinter = (modules) => {
83498482
case NodeType.Identifier: {
83508483
// Without the mangler's analysis nothing is known constant.
83518484
if (mangledNames === null) return undefined;
8485+
if (globalDefs !== null) {
8486+
const value = globalDefOf(node);
8487+
if (value !== undefined) return value;
8488+
}
83528489
const definition = mangledNames[node];
83538490
if (
83548491
!isMinifierVariable(definition) ||
@@ -9931,6 +10068,15 @@ const createNodePrinter = (modules) => {
993110068
}
993210069
} else if (statementType === NodeType.FunctionDeclaration) {
993310070
if (!hasEdgeComments(statement)) candidates.push(statement);
10071+
} else if (
10072+
(statementType === NodeType.DebuggerStatement && dropsDebugger) ||
10073+
(statementType === NodeType.ExpressionStatement &&
10074+
callsDroppedConsole(childOf(Field.expression, statement)))
10075+
) {
10076+
// terser's `drop_debugger` and `drop_console`: the call goes whole,
10077+
// what its arguments read and run with it.
10078+
dropped[statement] = 1;
10079+
findNode(statement, discountRead);
993410080
} else if (statementType === NodeType.IfStatement) {
993510081
if (neverRuns(statement)) {
993610082
dropped[statement] = 1;
@@ -13775,7 +13921,11 @@ const createNodePrinter = (modules) => {
1377513921
const printsInfinity = (node) => {
1377613922
let negative = false;
1377713923
if (typeOf(node) === NodeType.Identifier) {
13778-
if (keepsGlobalNames || !isGlobalUndefined(node, "Infinity")) {
13924+
if (
13925+
keepsGlobalNames ||
13926+
keepsInfinity ||
13927+
!isGlobalUndefined(node, "Infinity")
13928+
) {
1377913929
return false;
1378013930
}
1378113931
} else {
@@ -14416,7 +14566,8 @@ const createNodePrinter = (modules) => {
1441614566
output.semicolon();
1441714567
};
1441814568
generators[NodeType.DebuggerStatement] = () => {
14419-
output.print("debugger");
14569+
// One `drop_debugger` drops where no list holds it prints as `;`.
14570+
if (!dropsDebugger) output.print("debugger");
1442014571
output.semicolon();
1442114572
};
1442214573
generators[NodeType.LabeledStatement] = (node) => {
@@ -14868,6 +15019,13 @@ const createNodePrinter = (modules) => {
1486815019
const deleted =
1486915020
typeOf(parentAt(0)) === NodeType.UnaryExpression &&
1487015021
tree.textOf(Part.operator, parentAt(0)) === "delete";
15022+
if (compresses && !deleted && globalDefs !== null) {
15023+
const value = globalDefOf(node);
15024+
if (value !== undefined && readsAsValue(node, parentAt(0))) {
15025+
printValue(value, takesUnary(node));
15026+
return;
15027+
}
15028+
}
1487115029
if (compresses && !deleted && readsUndefined(node)) {
1487215030
// `void 0` is shorter, and no scope can rebind it.
1487315031
output.print("void 0");
@@ -16154,6 +16312,20 @@ const createNodePrinter = (modules) => {
1615416312
}
1615516313
dropsToplevel = Boolean(compress && (compress.toplevel || compress.module));
1615616314
keepsFunctionNames = Boolean(compress && compress.keep_fnames);
16315+
dropsDebugger = Boolean(compress && compress.drop_debugger !== false);
16316+
keepsInfinity = Boolean(compress && compress.keep_infinity);
16317+
pureFunctions =
16318+
compress &&
16319+
Array.isArray(compress.pure_funcs) &&
16320+
compress.pure_funcs.length > 0
16321+
? new Set(compress.pure_funcs)
16322+
: null;
16323+
droppedConsole =
16324+
!compress || !compress.drop_console
16325+
? null
16326+
: compress.drop_console === true || new Set(compress.drop_console);
16327+
retainsTop = compress ? topRetainOf(compress.top_retain) : null;
16328+
globalDefs = compress ? globalDefsOf(compress.global_defs) : null;
1615716329
keepsClassNames = Boolean(compress && compress.keep_classnames);
1615816330
privateNames = printOptions.names ? printOptions.names.privateNames : null;
1615916331
renamedProperties = printOptions.names

‎test/helpers/oxcSmaller.js‎

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -989,7 +989,6 @@ const OXC_SMALLER_BY_REASON = [
989989
"oxc minifier: ecmascript/may_have_side_effects_statements.rs:168",
990990
"oxc minifier: ecmascript/may_have_side_effects_statements.rs:169",
991991
"oxc minifier: ecmascript/may_have_side_effects_statements.rs:170",
992-
"oxc minifier: ecmascript/may_have_side_effects_statements.rs:173",
993992
"oxc minifier: ecmascript/may_have_side_effects_statements.rs:80",
994993
"oxc minifier: ecmascript/may_have_side_effects_statements.rs:81",
995994
"oxc minifier: ecmascript/may_have_side_effects_statements.rs:82",
@@ -1041,7 +1040,6 @@ const OXC_SMALLER_BY_REASON = [
10411040
"oxc minifier: peephole/collapse_variable_declarations.rs:97",
10421041
"oxc minifier: peephole/collapse_variable_declarations.rs:98",
10431042
"oxc minifier: peephole/collapse_variable_declarations.rs:99",
1044-
"oxc minifier: peephole/compression_pass.rs:10",
10451043
"oxc minifier: peephole/convert_to_dotted_properties.rs:216",
10461044
"oxc minifier: peephole/convert_to_dotted_properties.rs:217",
10471045
"oxc minifier: peephole/convert_to_dotted_properties.rs:218",
@@ -1356,7 +1354,6 @@ const OXC_SMALLER_BY_REASON = [
13561354
"oxc minifier: peephole/esbuild.rs:202",
13571355
"oxc minifier: peephole/esbuild.rs:2028",
13581356
"oxc minifier: peephole/esbuild.rs:206",
1359-
"oxc minifier: peephole/esbuild.rs:207",
13601357
"oxc minifier: peephole/esbuild.rs:211",
13611358
"oxc minifier: peephole/esbuild.rs:213",
13621359
"oxc minifier: peephole/esbuild.rs:214",
@@ -2348,10 +2345,8 @@ const OXC_SMALLER_BY_REASON = [
23482345
"oxc minifier: peephole/minimize_switch_statements.rs:64",
23492346
"oxc minifier: peephole/minimize_switch_statements.rs:7",
23502347
"oxc minifier: peephole/minimize_switch_statements.rs:8",
2351-
"oxc minifier: peephole/normalize.rs:100",
23522348
"oxc minifier: peephole/normalize.rs:101",
23532349
"oxc minifier: peephole/normalize.rs:107",
2354-
"oxc minifier: peephole/normalize.rs:108",
23552350
"oxc minifier: peephole/normalize.rs:121",
23562351
"oxc minifier: peephole/normalize.rs:122",
23572352
"oxc minifier: peephole/normalize.rs:124",
@@ -2485,16 +2480,11 @@ const OXC_SMALLER_BY_REASON = [
24852480
"oxc minifier: peephole/normalize.rs:353",
24862481
"oxc minifier: peephole/normalize.rs:363",
24872482
"oxc minifier: peephole/normalize.rs:45",
2488-
"oxc minifier: peephole/normalize.rs:82",
24892483
"oxc minifier: peephole/normalize.rs:83",
24902484
"oxc minifier: peephole/normalize.rs:88",
24912485
"oxc minifier: peephole/normalize.rs:93",
24922486
"oxc minifier: peephole/normalize.rs:94",
24932487
"oxc minifier: peephole/normalize.rs:95",
2494-
"oxc minifier: peephole/normalize.rs:96",
2495-
"oxc minifier: peephole/normalize.rs:97",
2496-
"oxc minifier: peephole/normalize.rs:98",
2497-
"oxc minifier: peephole/normalize.rs:99",
24982488
"oxc minifier: peephole/obscure_edge_cases.rs:127",
24992489
"oxc minifier: peephole/obscure_edge_cases.rs:128",
25002490
"oxc minifier: peephole/obscure_edge_cases.rs:129",
@@ -4379,7 +4369,6 @@ const OXC_SMALLER_BY_REASON = [
43794369
"terser compress: dead-code/throw_assignment",
43804370
"terser compress: dead-code/try_catch_finally",
43814371
"terser compress: debugger/drop_debugger",
4382-
"terser compress: debugger/keep_debugger",
43834372
"terser compress: destructuring/destructuring_assign_of_computed_key",
43844373
"terser compress: destructuring/destructuring_assign_of_numeric_key",
43854374
"terser compress: destructuring/destructuring_decl_of_computed_key",

‎test/helpers/swcSmaller.js‎

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,6 @@ const SWC_SMALLER_BY_REASON = [
562562
"swc minifier: fixture/issues/12177/input.js",
563563
"swc minifier: fixture/issues/12182/callee-safety/nested-values/input.js",
564564
"swc minifier: fixture/issues/12182/callee-safety/spread-overwrite/input.js",
565-
"swc minifier: fixture/issues/12184/computed-members/input.js",
566565
"swc minifier: fixture/issues/12185/input.js",
567566
"swc minifier: fixture/issues/12189/input.js",
568567
"swc minifier: fixture/issues/12190/input.js",
@@ -1033,7 +1032,6 @@ const SWC_SMALLER_BY_REASON = [
10331032
"terser compress: dead-code/issue_2383_3",
10341033
"terser compress: dead-code/throw_assignment",
10351034
"terser compress: dead-code/try_catch_finally",
1036-
"terser compress: debugger/drop_debugger",
10371035
"terser compress: destructuring/destructure_empty_array_3",
10381036
"terser compress: destructuring/destructuring_with_undefined_as_default_assignment",
10391037
"terser compress: destructuring/empty_object_destructuring_3",
@@ -1058,6 +1056,7 @@ const SWC_SMALLER_BY_REASON = [
10581056
"terser compress: drop-unused/drop_assign",
10591057
"terser compress: drop-unused/drop_fargs",
10601058
"terser compress: drop-unused/drop_toplevel_all",
1059+
"terser compress: drop-unused/drop_toplevel_all_retain",
10611060
"terser compress: drop-unused/drop_toplevel_retain",
10621061
"terser compress: drop-unused/drop_toplevel_retain_array",
10631062
"terser compress: drop-unused/drop_value",
@@ -1137,7 +1136,6 @@ const SWC_SMALLER_BY_REASON = [
11371136
"terser compress: global_defs/conditional_chains",
11381137
"terser compress: global_defs/issue_1801",
11391138
"terser compress: global_defs/issue_2167",
1140-
"terser compress: global_defs/keyword",
11411139
"terser compress: global_defs/mixed",
11421140
"terser compress: harmony/array_literal_with_spread_3a",
11431141
"terser compress: harmony/array_literal_with_spread_4a",
@@ -1171,8 +1169,6 @@ const SWC_SMALLER_BY_REASON = [
11711169
"terser compress: inline/inline_annotation_2",
11721170
"terser compress: issue-1052/defun_else_if_return",
11731171
"terser compress: issue-1052/defun_if_return",
1174-
"terser compress: issue-1212/issue_1212_debug_false",
1175-
"terser compress: issue-1212/issue_1212_debug_true",
11761172
"terser compress: issue-126/concatenate_rhs_strings",
11771173
"terser compress: issue-1275/string_plus_optimization",
11781174
"terser compress: issue-1447/else_with_empty_block",
@@ -1187,7 +1183,6 @@ const SWC_SMALLER_BY_REASON = [
11871183
"terser compress: issue-2001/export_mangle_3",
11881184
"terser compress: issue-2001/export_mangle_4",
11891185
"terser compress: issue-2001/export_toplevel_2",
1190-
"terser compress: issue-208/do_update_rhs",
11911186
"terser compress: issue-208/mixed",
11921187
"terser compress: issue-269/issue_269_1",
11931188
"terser compress: issue-269/strings_concat",
@@ -1251,10 +1246,8 @@ const SWC_SMALLER_BY_REASON = [
12511246
"terser compress: properties/evaluate_array_length",
12521247
"terser compress: properties/evaluate_string_length",
12531248
"terser compress: properties/lhs_prop_2",
1254-
"terser compress: pure_funcs/babel",
12551249
"terser compress: pure_funcs/issue_2705_1",
12561250
"terser compress: pure_funcs/issue_2705_6",
1257-
"terser compress: pure_funcs/issue_3065_2b",
12581251
"terser compress: pure_funcs/issue_3065_3",
12591252
"terser compress: pure_funcs/issue_3065_4",
12601253
"terser compress: pure_funcs/side_effects",
@@ -1383,7 +1376,6 @@ const SWC_SMALLER_BY_REASON = [
13831376
"terser compress: template-string/template_literal_plus",
13841377
"terser compress: template-string/template_literal_plus_grouping",
13851378
"terser compress: transform/booleans_evaluate",
1386-
"terser compress: transform/booleans_global_defs",
13871379
"terser compress: transform/condition_evaluate",
13881380
"terser compress: transform/while_if_break",
13891381
"terser compress: typeof/issue_1668",

0 commit comments

Comments
 (0)