UNPKG

5.15 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const { UsageState } = require("../ExportsInfo");
9const {
10 numberToIdentifier,
11 NUMBER_OF_IDENTIFIER_START_CHARS,
12 NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS
13} = require("../Template");
14const { assignDeterministicIds } = require("../ids/IdHelpers");
15const { compareSelect, compareStringsNumeric } = require("../util/comparators");
16
17/** @typedef {import("../Compiler")} Compiler */
18/** @typedef {import("../ExportsInfo")} ExportsInfo */
19/** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */
20
21/**
22 * @param {ExportsInfo} exportsInfo exports info
23 * @returns {boolean} mangle is possible
24 */
25const canMangle = exportsInfo => {
26 if (exportsInfo.otherExportsInfo.getUsed(undefined) !== UsageState.Unused)
27 return false;
28 let hasSomethingToMangle = false;
29 for (const exportInfo of exportsInfo.exports) {
30 if (exportInfo.canMangle === true) {
31 hasSomethingToMangle = true;
32 }
33 }
34 return hasSomethingToMangle;
35};
36
37// Sort by name
38const comparator = compareSelect(e => e.name, compareStringsNumeric);
39/**
40 * @param {boolean} deterministic use deterministic names
41 * @param {ExportsInfo} exportsInfo exports info
42 * @param {boolean} isNamespace is namespace object
43 * @returns {void}
44 */
45const mangleExportsInfo = (deterministic, exportsInfo, isNamespace) => {
46 if (!canMangle(exportsInfo)) return;
47 const usedNames = new Set();
48 /** @type {ExportInfo[]} */
49 const mangleableExports = [];
50
51 // Avoid to renamed exports that are not provided when
52 // 1. it's not a namespace export: non-provided exports can be found in prototype chain
53 // 2. there are other provided exports and deterministic mode is chosen:
54 // non-provided exports would break the determinism
55 let avoidMangleNonProvided = !isNamespace;
56 if (!avoidMangleNonProvided && deterministic) {
57 for (const exportInfo of exportsInfo.ownedExports) {
58 if (exportInfo.provided !== false) {
59 avoidMangleNonProvided = true;
60 break;
61 }
62 }
63 }
64 for (const exportInfo of exportsInfo.ownedExports) {
65 const name = exportInfo.name;
66 if (!exportInfo.hasUsedName()) {
67 if (
68 // Can the export be mangled?
69 exportInfo.canMangle !== true ||
70 // Never rename 1 char exports
71 (name.length === 1 && /^[a-zA-Z0-9_$]/.test(name)) ||
72 // Don't rename 2 char exports in deterministic mode
73 (deterministic &&
74 name.length === 2 &&
75 /^[a-zA-Z_$][a-zA-Z0-9_$]|^[1-9][0-9]/.test(name)) ||
76 // Don't rename exports that are not provided
77 (avoidMangleNonProvided && exportInfo.provided !== true)
78 ) {
79 exportInfo.setUsedName(name);
80 usedNames.add(name);
81 } else {
82 mangleableExports.push(exportInfo);
83 }
84 }
85 if (exportInfo.exportsInfoOwned) {
86 const used = exportInfo.getUsed(undefined);
87 if (
88 used === UsageState.OnlyPropertiesUsed ||
89 used === UsageState.Unused
90 ) {
91 mangleExportsInfo(deterministic, exportInfo.exportsInfo, false);
92 }
93 }
94 }
95 if (deterministic) {
96 assignDeterministicIds(
97 mangleableExports,
98 e => e.name,
99 comparator,
100 (e, id) => {
101 const name = numberToIdentifier(id);
102 const size = usedNames.size;
103 usedNames.add(name);
104 if (size === usedNames.size) return false;
105 e.setUsedName(name);
106 return true;
107 },
108 [
109 NUMBER_OF_IDENTIFIER_START_CHARS,
110 NUMBER_OF_IDENTIFIER_START_CHARS *
111 NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS
112 ],
113 NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS,
114 usedNames.size
115 );
116 } else {
117 const usedExports = [];
118 const unusedExports = [];
119 for (const exportInfo of mangleableExports) {
120 if (exportInfo.getUsed(undefined) === UsageState.Unused) {
121 unusedExports.push(exportInfo);
122 } else {
123 usedExports.push(exportInfo);
124 }
125 }
126 usedExports.sort(comparator);
127 unusedExports.sort(comparator);
128 let i = 0;
129 for (const list of [usedExports, unusedExports]) {
130 for (const exportInfo of list) {
131 let name;
132 do {
133 name = numberToIdentifier(i++);
134 } while (usedNames.has(name));
135 exportInfo.setUsedName(name);
136 }
137 }
138 }
139};
140
141class MangleExportsPlugin {
142 /**
143 * @param {boolean} deterministic use deterministic names
144 */
145 constructor(deterministic) {
146 this._deterministic = deterministic;
147 }
148 /**
149 * Apply the plugin
150 * @param {Compiler} compiler the compiler instance
151 * @returns {void}
152 */
153 apply(compiler) {
154 const { _deterministic: deterministic } = this;
155 compiler.hooks.compilation.tap("MangleExportsPlugin", compilation => {
156 const moduleGraph = compilation.moduleGraph;
157 compilation.hooks.optimizeCodeGeneration.tap(
158 "MangleExportsPlugin",
159 modules => {
160 if (compilation.moduleMemCaches) {
161 throw new Error(
162 "optimization.mangleExports can't be used with cacheUnaffected as export mangling is a global effect"
163 );
164 }
165 for (const module of modules) {
166 const isNamespace =
167 module.buildMeta && module.buildMeta.exportsType === "namespace";
168 const exportsInfo = moduleGraph.getExportsInfo(module);
169 mangleExportsInfo(deterministic, exportsInfo, isNamespace);
170 }
171 }
172 );
173 });
174 }
175}
176
177module.exports = MangleExportsPlugin;