UNPKG

11.8 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 Dependency = require("./Dependency");
9const { UsageState } = require("./ExportsInfo");
10const { STAGE_DEFAULT } = require("./OptimizationStages");
11const TupleQueue = require("./util/TupleQueue");
12const {
13 getEntryRuntime,
14 mergeRuntime,
15 mergeRuntimeOwned
16} = require("./util/runtime");
17
18/** @typedef {import("./Chunk")} Chunk */
19/** @typedef {import("./ChunkGroup")} ChunkGroup */
20/** @typedef {import("./Compiler")} Compiler */
21/** @typedef {import("./DependenciesBlock")} DependenciesBlock */
22/** @typedef {import("./Dependency")} Dependency */
23/** @typedef {import("./Dependency").ReferencedExport} ReferencedExport */
24/** @typedef {import("./ExportsInfo")} ExportsInfo */
25/** @typedef {import("./Module")} Module */
26/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
27
28const { NO_EXPORTS_REFERENCED, EXPORTS_OBJECT_REFERENCED } = Dependency;
29
30class FlagDependencyUsagePlugin {
31 /**
32 * @param {boolean} global do a global analysis instead of per runtime
33 */
34 constructor(global) {
35 this.global = global;
36 }
37
38 /**
39 * Apply the plugin
40 * @param {Compiler} compiler the compiler instance
41 * @returns {void}
42 */
43 apply(compiler) {
44 compiler.hooks.compilation.tap("FlagDependencyUsagePlugin", compilation => {
45 const moduleGraph = compilation.moduleGraph;
46 compilation.hooks.optimizeDependencies.tap(
47 {
48 name: "FlagDependencyUsagePlugin",
49 stage: STAGE_DEFAULT
50 },
51 modules => {
52 const logger = compilation.getLogger(
53 "webpack.FlagDependencyUsagePlugin"
54 );
55
56 /** @type {Map<ExportsInfo, Module>} */
57 const exportInfoToModuleMap = new Map();
58
59 /** @type {TupleQueue<[Module, RuntimeSpec]>} */
60 const queue = new TupleQueue();
61
62 /**
63 * @param {Module} module module to process
64 * @param {(string[] | ReferencedExport)[]} usedExports list of used exports
65 * @param {RuntimeSpec} runtime part of which runtime
66 * @param {boolean} forceSideEffects always apply side effects
67 * @returns {void}
68 */
69 const processReferencedModule = (
70 module,
71 usedExports,
72 runtime,
73 forceSideEffects
74 ) => {
75 const exportsInfo = moduleGraph.getExportsInfo(module);
76 if (usedExports.length > 0) {
77 if (!module.buildMeta || !module.buildMeta.exportsType) {
78 if (exportsInfo.setUsedWithoutInfo(runtime)) {
79 queue.enqueue(module, runtime);
80 }
81 return;
82 }
83 for (const usedExportInfo of usedExports) {
84 let usedExport;
85 let canMangle = true;
86 if (Array.isArray(usedExportInfo)) {
87 usedExport = usedExportInfo;
88 } else {
89 usedExport = usedExportInfo.name;
90 canMangle = usedExportInfo.canMangle !== false;
91 }
92 if (usedExport.length === 0) {
93 if (exportsInfo.setUsedInUnknownWay(runtime)) {
94 queue.enqueue(module, runtime);
95 }
96 } else {
97 let currentExportsInfo = exportsInfo;
98 for (let i = 0; i < usedExport.length; i++) {
99 const exportInfo = currentExportsInfo.getExportInfo(
100 usedExport[i]
101 );
102 if (canMangle === false) {
103 exportInfo.canMangleUse = false;
104 }
105 const lastOne = i === usedExport.length - 1;
106 if (!lastOne) {
107 const nestedInfo = exportInfo.getNestedExportsInfo();
108 if (nestedInfo) {
109 if (
110 exportInfo.setUsedConditionally(
111 used => used === UsageState.Unused,
112 UsageState.OnlyPropertiesUsed,
113 runtime
114 )
115 ) {
116 const currentModule =
117 currentExportsInfo === exportsInfo
118 ? module
119 : exportInfoToModuleMap.get(currentExportsInfo);
120 if (currentModule) {
121 queue.enqueue(currentModule, runtime);
122 }
123 }
124 currentExportsInfo = nestedInfo;
125 continue;
126 }
127 }
128 if (
129 exportInfo.setUsedConditionally(
130 v => v !== UsageState.Used,
131 UsageState.Used,
132 runtime
133 )
134 ) {
135 const currentModule =
136 currentExportsInfo === exportsInfo
137 ? module
138 : exportInfoToModuleMap.get(currentExportsInfo);
139 if (currentModule) {
140 queue.enqueue(currentModule, runtime);
141 }
142 }
143 break;
144 }
145 }
146 }
147 } else {
148 // for a module without side effects we stop tracking usage here when no export is used
149 // This module won't be evaluated in this case
150 // TODO webpack 6 remove this check
151 if (
152 !forceSideEffects &&
153 module.factoryMeta !== undefined &&
154 module.factoryMeta.sideEffectFree
155 ) {
156 return;
157 }
158 if (exportsInfo.setUsedForSideEffectsOnly(runtime)) {
159 queue.enqueue(module, runtime);
160 }
161 }
162 };
163
164 /**
165 * @param {DependenciesBlock} module the module
166 * @param {RuntimeSpec} runtime part of which runtime
167 * @returns {void}
168 */
169 const processModule = (module, runtime) => {
170 /** @type {Map<Module, (string[] | ReferencedExport)[] | Map<string, string[] | ReferencedExport>>} */
171 const map = new Map();
172
173 /** @type {DependenciesBlock[]} */
174 const queue = [module];
175 for (const block of queue) {
176 for (const b of block.blocks) {
177 if (b.groupOptions && b.groupOptions.entryOptions) {
178 processModule(b, b.groupOptions.entryOptions.runtime);
179 } else {
180 queue.push(b);
181 }
182 }
183 for (const dep of block.dependencies) {
184 const connection = moduleGraph.getConnection(dep);
185 if (
186 !connection ||
187 !connection.module ||
188 !connection.isTargetActive(runtime)
189 ) {
190 continue;
191 }
192 const { module } = connection;
193 const oldReferencedExports = map.get(module);
194 if (oldReferencedExports === EXPORTS_OBJECT_REFERENCED) {
195 continue;
196 }
197 const referencedExports = compilation.getDependencyReferencedExports(
198 dep,
199 runtime
200 );
201 if (
202 oldReferencedExports === undefined ||
203 oldReferencedExports === NO_EXPORTS_REFERENCED ||
204 referencedExports === EXPORTS_OBJECT_REFERENCED
205 ) {
206 map.set(module, referencedExports);
207 } else if (oldReferencedExports === NO_EXPORTS_REFERENCED) {
208 continue;
209 } else {
210 let exportsMap;
211 if (Array.isArray(oldReferencedExports)) {
212 exportsMap = new Map();
213 for (const item of oldReferencedExports) {
214 if (Array.isArray(item)) {
215 exportsMap.set(item.join("\n"), item);
216 } else {
217 exportsMap.set(item.name.join("\n"), item);
218 }
219 }
220 map.set(module, exportsMap);
221 } else {
222 exportsMap = oldReferencedExports;
223 }
224 for (const item of referencedExports) {
225 if (Array.isArray(item)) {
226 const key = item.join("\n");
227 const oldItem = exportsMap.get(key);
228 if (oldItem === undefined) {
229 exportsMap.set(key, item);
230 }
231 // if oldItem is already an array we have to do nothing
232 // if oldItem is an ReferencedExport object, we don't have to do anything
233 // as canMangle defaults to true for arrays
234 } else {
235 const key = item.name.join("\n");
236 const oldItem = exportsMap.get(key);
237 if (oldItem === undefined || Array.isArray(oldItem)) {
238 exportsMap.set(key, item);
239 } else {
240 exportsMap.set(key, {
241 name: item.name,
242 canMangle: item.canMangle && oldItem.canMangle
243 });
244 }
245 }
246 }
247 }
248 }
249 }
250
251 for (const [module, referencedExports] of map) {
252 if (Array.isArray(referencedExports)) {
253 processReferencedModule(
254 module,
255 referencedExports,
256 runtime,
257 false
258 );
259 } else {
260 processReferencedModule(
261 module,
262 Array.from(referencedExports.values()),
263 runtime,
264 false
265 );
266 }
267 }
268 };
269
270 logger.time("initialize exports usage");
271 for (const module of modules) {
272 const exportsInfo = moduleGraph.getExportsInfo(module);
273 exportInfoToModuleMap.set(exportsInfo, module);
274 exportsInfo.setHasUseInfo();
275 }
276 logger.timeEnd("initialize exports usage");
277
278 logger.time("trace exports usage in graph");
279
280 /**
281 * @param {Dependency} dep dependency
282 * @param {RuntimeSpec} runtime runtime
283 */
284 const processEntryDependency = (dep, runtime) => {
285 const module = moduleGraph.getModule(dep);
286 if (module) {
287 processReferencedModule(
288 module,
289 NO_EXPORTS_REFERENCED,
290 runtime,
291 true
292 );
293 }
294 };
295 /** @type {RuntimeSpec} */
296 let globalRuntime = undefined;
297 for (const [
298 entryName,
299 { dependencies: deps, includeDependencies: includeDeps, options }
300 ] of compilation.entries) {
301 const runtime = this.global
302 ? undefined
303 : getEntryRuntime(compilation, entryName, options);
304 for (const dep of deps) {
305 processEntryDependency(dep, runtime);
306 }
307 for (const dep of includeDeps) {
308 processEntryDependency(dep, runtime);
309 }
310 globalRuntime = mergeRuntimeOwned(globalRuntime, runtime);
311 }
312 for (const dep of compilation.globalEntry.dependencies) {
313 processEntryDependency(dep, globalRuntime);
314 }
315 for (const dep of compilation.globalEntry.includeDependencies) {
316 processEntryDependency(dep, globalRuntime);
317 }
318
319 while (queue.length) {
320 const [module, runtime] = queue.dequeue();
321 processModule(module, runtime);
322 }
323 logger.timeEnd("trace exports usage in graph");
324 }
325 );
326 if (!this.global) {
327 compilation.hooks.afterChunks.tap("FlagDependencyUsagePlugin", () => {
328 const processEntrypoint = entrypoint => {
329 const runtime = getEntryRuntime(
330 compilation,
331 entrypoint.name,
332 entrypoint.options
333 );
334 for (const chunk of entrypoint
335 .getEntrypointChunk()
336 .getAllReferencedChunks()) {
337 chunk.runtime = mergeRuntime(chunk.runtime, runtime);
338 }
339 };
340 for (const entrypoint of compilation.entrypoints.values()) {
341 processEntrypoint(entrypoint);
342 }
343 for (const asyncEntry of compilation.asyncEntrypoints) {
344 processEntrypoint(asyncEntry);
345 }
346 });
347
348 compilation.hooks.optimizeChunkModules.tap(
349 "FlagDependencyUsagePlugin",
350 (chunks, modules) => {
351 for (const module of modules) {
352 const exportsInfo = compilation.moduleGraph.getExportsInfo(
353 module
354 );
355 let removeFromRuntimes = undefined;
356 for (const runtime of compilation.chunkGraph.getModuleRuntimes(
357 module
358 )) {
359 if (!exportsInfo.isModuleUsed(runtime)) {
360 if (removeFromRuntimes === undefined) {
361 removeFromRuntimes = new Set();
362 }
363 removeFromRuntimes.add(runtime);
364 }
365 }
366 if (removeFromRuntimes !== undefined) {
367 for (const chunk of compilation.chunkGraph.getModuleChunksIterable(
368 module
369 )) {
370 if (removeFromRuntimes.has(chunk.runtime)) {
371 compilation.chunkGraph.disconnectChunkAndModule(
372 chunk,
373 module
374 );
375 }
376 }
377 }
378 }
379 }
380 );
381 }
382 });
383 }
384}
385
386module.exports = FlagDependencyUsagePlugin;