UNPKG

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