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 =
205 compilation.getDependencyReferencedExports(dep, runtime);
206 if (
207 oldReferencedExports === undefined ||
208 oldReferencedExports === NO_EXPORTS_REFERENCED ||
209 referencedExports === EXPORTS_OBJECT_REFERENCED
210 ) {
211 map.set(module, referencedExports);
212 } else if (
213 oldReferencedExports !== undefined &&
214 referencedExports === NO_EXPORTS_REFERENCED
215 ) {
216 continue;
217 } else {
218 let exportsMap;
219 if (Array.isArray(oldReferencedExports)) {
220 exportsMap = new Map();
221 for (const item of oldReferencedExports) {
222 if (Array.isArray(item)) {
223 exportsMap.set(item.join("\n"), item);
224 } else {
225 exportsMap.set(item.name.join("\n"), item);
226 }
227 }
228 map.set(module, exportsMap);
229 } else {
230 exportsMap = oldReferencedExports;
231 }
232 for (const item of referencedExports) {
233 if (Array.isArray(item)) {
234 const key = item.join("\n");
235 const oldItem = exportsMap.get(key);
236 if (oldItem === undefined) {
237 exportsMap.set(key, item);
238 }
239 // if oldItem is already an array we have to do nothing
240 // if oldItem is an ReferencedExport object, we don't have to do anything
241 // as canMangle defaults to true for arrays
242 } else {
243 const key = item.name.join("\n");
244 const oldItem = exportsMap.get(key);
245 if (oldItem === undefined || Array.isArray(oldItem)) {
246 exportsMap.set(key, item);
247 } else {
248 exportsMap.set(key, {
249 name: item.name,
250 canMangle: item.canMangle && oldItem.canMangle
251 });
252 }
253 }
254 }
255 }
256 }
257 }
258
259 for (const [module, referencedExports] of map) {
260 if (Array.isArray(referencedExports)) {
261 processReferencedModule(
262 module,
263 referencedExports,
264 runtime,
265 forceSideEffects
266 );
267 } else {
268 processReferencedModule(
269 module,
270 Array.from(referencedExports.values()),
271 runtime,
272 forceSideEffects
273 );
274 }
275 }
276 };
277
278 logger.time("initialize exports usage");
279 for (const module of modules) {
280 const exportsInfo = moduleGraph.getExportsInfo(module);
281 exportInfoToModuleMap.set(exportsInfo, module);
282 exportsInfo.setHasUseInfo();
283 }
284 logger.timeEnd("initialize exports usage");
285
286 logger.time("trace exports usage in graph");
287
288 /**
289 * @param {Dependency} dep dependency
290 * @param {RuntimeSpec} runtime runtime
291 */
292 const processEntryDependency = (dep, runtime) => {
293 const module = moduleGraph.getModule(dep);
294 if (module) {
295 processReferencedModule(
296 module,
297 NO_EXPORTS_REFERENCED,
298 runtime,
299 true
300 );
301 }
302 };
303 /** @type {RuntimeSpec} */
304 let globalRuntime = undefined;
305 for (const [
306 entryName,
307 { dependencies: deps, includeDependencies: includeDeps, options }
308 ] of compilation.entries) {
309 const runtime = this.global
310 ? undefined
311 : getEntryRuntime(compilation, entryName, options);
312 for (const dep of deps) {
313 processEntryDependency(dep, runtime);
314 }
315 for (const dep of includeDeps) {
316 processEntryDependency(dep, runtime);
317 }
318 globalRuntime = mergeRuntimeOwned(globalRuntime, runtime);
319 }
320 for (const dep of compilation.globalEntry.dependencies) {
321 processEntryDependency(dep, globalRuntime);
322 }
323 for (const dep of compilation.globalEntry.includeDependencies) {
324 processEntryDependency(dep, globalRuntime);
325 }
326
327 while (queue.length) {
328 const [module, runtime] = queue.dequeue();
329 processModule(module, runtime, false);
330 }
331 logger.timeEnd("trace exports usage in graph");
332 }
333 );
334 });
335 }
336}
337
338module.exports = FlagDependencyUsagePlugin;