2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
8 const Dependency = require("./Dependency");
9 const { UsageState } = require("./ExportsInfo");
10 const ModuleGraphConnection = require("./ModuleGraphConnection");
11 const { STAGE_DEFAULT } = require("./OptimizationStages");
12 const ArrayQueue = require("./util/ArrayQueue");
13 const TupleQueue = require("./util/TupleQueue");
14 const { getEntryRuntime, mergeRuntimeOwned } = require("./util/runtime");
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 */
25 const { NO_EXPORTS_REFERENCED, EXPORTS_OBJECT_REFERENCED } = Dependency;
27 class FlagDependencyUsagePlugin {
29 * @param {boolean} global do a global analysis instead of per runtime
37 * @param {Compiler} compiler the compiler instance
41 compiler.hooks.compilation.tap("FlagDependencyUsagePlugin", compilation => {
42 const moduleGraph = compilation.moduleGraph;
43 compilation.hooks.optimizeDependencies.tap(
45 name: "FlagDependencyUsagePlugin",
49 if (compilation.moduleMemCaches) {
51 "optimization.usedExports can't be used with cacheUnaffected as export usage is a global effect"
55 const logger = compilation.getLogger(
56 "webpack.FlagDependencyUsagePlugin"
58 /** @type {Map<ExportsInfo, Module>} */
59 const exportInfoToModuleMap = new Map();
61 /** @type {TupleQueue<[Module, RuntimeSpec]>} */
62 const queue = new TupleQueue();
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
71 const processReferencedModule = (
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);
85 for (const usedExportInfo of usedExports) {
88 if (Array.isArray(usedExportInfo)) {
89 usedExport = usedExportInfo;
91 usedExport = usedExportInfo.name;
92 canMangle = usedExportInfo.canMangle !== false;
94 if (usedExport.length === 0) {
95 if (exportsInfo.setUsedInUnknownWay(runtime)) {
96 queue.enqueue(module, runtime);
99 let currentExportsInfo = exportsInfo;
100 for (let i = 0; i < usedExport.length; i++) {
101 const exportInfo = currentExportsInfo.getExportInfo(
104 if (canMangle === false) {
105 exportInfo.canMangleUse = false;
107 const lastOne = i === usedExport.length - 1;
109 const nestedInfo = exportInfo.getNestedExportsInfo();
112 exportInfo.setUsedConditionally(
113 used => used === UsageState.Unused,
114 UsageState.OnlyPropertiesUsed,
118 const currentModule =
119 currentExportsInfo === exportsInfo
121 : exportInfoToModuleMap.get(currentExportsInfo);
123 queue.enqueue(currentModule, runtime);
126 currentExportsInfo = nestedInfo;
131 exportInfo.setUsedConditionally(
132 v => v !== UsageState.Used,
137 const currentModule =
138 currentExportsInfo === exportsInfo
140 : exportInfoToModuleMap.get(currentExportsInfo);
142 queue.enqueue(currentModule, runtime);
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
155 module.factoryMeta !== undefined &&
156 module.factoryMeta.sideEffectFree
160 if (exportsInfo.setUsedForSideEffectsOnly(runtime)) {
161 queue.enqueue(module, runtime);
167 * @param {DependenciesBlock} module the module
168 * @param {RuntimeSpec} runtime part of which runtime
169 * @param {boolean} forceSideEffects always apply side effects
172 const processModule = (module, runtime, forceSideEffects) => {
173 /** @type {Map<Module, (string[] | ReferencedExport)[] | Map<string, string[] | ReferencedExport>>} */
174 const map = new Map();
176 /** @type {ArrayQueue<DependenciesBlock>} */
177 const queue = new ArrayQueue();
178 queue.enqueue(module);
180 const block = queue.dequeue();
181 if (block === undefined) break;
182 for (const b of block.blocks) {
186 b.groupOptions.entryOptions
190 b.groupOptions.entryOptions.runtime || undefined,
197 for (const dep of block.dependencies) {
198 const connection = moduleGraph.getConnection(dep);
199 if (!connection || !connection.module) {
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);
209 const oldReferencedExports = map.get(module);
210 if (oldReferencedExports === EXPORTS_OBJECT_REFERENCED) {
213 const referencedExports =
214 compilation.getDependencyReferencedExports(dep, runtime);
216 oldReferencedExports === undefined ||
217 oldReferencedExports === NO_EXPORTS_REFERENCED ||
218 referencedExports === EXPORTS_OBJECT_REFERENCED
220 map.set(module, referencedExports);
222 oldReferencedExports !== undefined &&
223 referencedExports === NO_EXPORTS_REFERENCED
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);
234 exportsMap.set(item.name.join("\n"), item);
237 map.set(module, exportsMap);
239 exportsMap = oldReferencedExports;
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);
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
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);
257 exportsMap.set(key, {
259 canMangle: item.canMangle && oldItem.canMangle
268 for (const [module, referencedExports] of map) {
269 if (Array.isArray(referencedExports)) {
270 processReferencedModule(
277 processReferencedModule(
279 Array.from(referencedExports.values()),
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();
293 logger.timeEnd("initialize exports usage");
295 logger.time("trace exports usage in graph");
298 * @param {Dependency} dep dependency
299 * @param {RuntimeSpec} runtime runtime
301 const processEntryDependency = (dep, runtime) => {
302 const module = moduleGraph.getModule(dep);
304 processReferencedModule(
306 NO_EXPORTS_REFERENCED,
312 /** @type {RuntimeSpec} */
313 let globalRuntime = undefined;
316 { dependencies: deps, includeDependencies: includeDeps, options }
317 ] of compilation.entries) {
318 const runtime = this.global
320 : getEntryRuntime(compilation, entryName, options);
321 for (const dep of deps) {
322 processEntryDependency(dep, runtime);
324 for (const dep of includeDeps) {
325 processEntryDependency(dep, runtime);
327 globalRuntime = mergeRuntimeOwned(globalRuntime, runtime);
329 for (const dep of compilation.globalEntry.dependencies) {
330 processEntryDependency(dep, globalRuntime);
332 for (const dep of compilation.globalEntry.includeDependencies) {
333 processEntryDependency(dep, globalRuntime);
336 while (queue.length) {
337 const [module, runtime] = queue.dequeue();
338 processModule(module, runtime, false);
340 logger.timeEnd("trace exports usage in graph");
347 module.exports = FlagDependencyUsagePlugin;