UNPKG

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