source: imaps-frontend/node_modules/webpack/lib/optimize/SideEffectsFlagPlugin.js

main
Last change on this file was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 4 days ago

F4 Finalna Verzija

  • Property mode set to 100644
File size: 12.2 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const glob2regexp = require("glob-to-regexp");
9const {
10 JAVASCRIPT_MODULE_TYPE_AUTO,
11 JAVASCRIPT_MODULE_TYPE_ESM,
12 JAVASCRIPT_MODULE_TYPE_DYNAMIC
13} = require("../ModuleTypeConstants");
14const { STAGE_DEFAULT } = require("../OptimizationStages");
15const HarmonyExportImportedSpecifierDependency = require("../dependencies/HarmonyExportImportedSpecifierDependency");
16const HarmonyImportSpecifierDependency = require("../dependencies/HarmonyImportSpecifierDependency");
17const formatLocation = require("../formatLocation");
18
19/** @typedef {import("estree").ModuleDeclaration} ModuleDeclaration */
20/** @typedef {import("estree").Statement} Statement */
21/** @typedef {import("../Compiler")} Compiler */
22/** @typedef {import("../Dependency")} Dependency */
23/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
24/** @typedef {import("../Module")} Module */
25/** @typedef {import("../Module").BuildMeta} BuildMeta */
26/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */
27/** @typedef {import("../NormalModuleFactory").ModuleSettings} ModuleSettings */
28/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
29/** @typedef {import("../javascript/JavascriptParser").Range} Range */
30
31/**
32 * @typedef {object} ExportInModule
33 * @property {Module} module the module
34 * @property {string} exportName the name of the export
35 * @property {boolean} checked if the export is conditional
36 */
37
38/**
39 * @typedef {object} ReexportInfo
40 * @property {Map<string, ExportInModule[]>} static
41 * @property {Map<Module, Set<string>>} dynamic
42 */
43
44/** @typedef {Map<string, RegExp>} CacheItem */
45
46/** @type {WeakMap<any, CacheItem>} */
47const globToRegexpCache = new WeakMap();
48
49/**
50 * @param {string} glob the pattern
51 * @param {Map<string, RegExp>} cache the glob to RegExp cache
52 * @returns {RegExp} a regular expression
53 */
54const globToRegexp = (glob, cache) => {
55 const cacheEntry = cache.get(glob);
56 if (cacheEntry !== undefined) return cacheEntry;
57 if (!glob.includes("/")) {
58 glob = `**/${glob}`;
59 }
60 const baseRegexp = glob2regexp(glob, { globstar: true, extended: true });
61 const regexpSource = baseRegexp.source;
62 const regexp = new RegExp(`^(\\./)?${regexpSource.slice(1)}`);
63 cache.set(glob, regexp);
64 return regexp;
65};
66
67const PLUGIN_NAME = "SideEffectsFlagPlugin";
68
69class SideEffectsFlagPlugin {
70 /**
71 * @param {boolean} analyseSource analyse source code for side effects
72 */
73 constructor(analyseSource = true) {
74 this._analyseSource = analyseSource;
75 }
76
77 /**
78 * Apply the plugin
79 * @param {Compiler} compiler the compiler instance
80 * @returns {void}
81 */
82 apply(compiler) {
83 let cache = globToRegexpCache.get(compiler.root);
84 if (cache === undefined) {
85 cache = new Map();
86 globToRegexpCache.set(compiler.root, cache);
87 }
88 compiler.hooks.compilation.tap(
89 PLUGIN_NAME,
90 (compilation, { normalModuleFactory }) => {
91 const moduleGraph = compilation.moduleGraph;
92 normalModuleFactory.hooks.module.tap(PLUGIN_NAME, (module, data) => {
93 const resolveData = data.resourceResolveData;
94 if (
95 resolveData &&
96 resolveData.descriptionFileData &&
97 resolveData.relativePath
98 ) {
99 const sideEffects = resolveData.descriptionFileData.sideEffects;
100 if (sideEffects !== undefined) {
101 if (module.factoryMeta === undefined) {
102 module.factoryMeta = {};
103 }
104 const hasSideEffects = SideEffectsFlagPlugin.moduleHasSideEffects(
105 resolveData.relativePath,
106 sideEffects,
107 /** @type {CacheItem} */ (cache)
108 );
109 module.factoryMeta.sideEffectFree = !hasSideEffects;
110 }
111 }
112
113 return module;
114 });
115 normalModuleFactory.hooks.module.tap(PLUGIN_NAME, (module, data) => {
116 const settings = /** @type {ModuleSettings} */ (data.settings);
117 if (typeof settings.sideEffects === "boolean") {
118 if (module.factoryMeta === undefined) {
119 module.factoryMeta = {};
120 }
121 module.factoryMeta.sideEffectFree = !settings.sideEffects;
122 }
123 return module;
124 });
125 if (this._analyseSource) {
126 /**
127 * @param {JavascriptParser} parser the parser
128 * @returns {void}
129 */
130 const parserHandler = parser => {
131 /** @type {undefined | Statement | ModuleDeclaration} */
132 let sideEffectsStatement;
133 parser.hooks.program.tap(PLUGIN_NAME, () => {
134 sideEffectsStatement = undefined;
135 });
136 parser.hooks.statement.tap(
137 { name: PLUGIN_NAME, stage: -100 },
138 statement => {
139 if (sideEffectsStatement) return;
140 if (parser.scope.topLevelScope !== true) return;
141 switch (statement.type) {
142 case "ExpressionStatement":
143 if (
144 !parser.isPure(
145 statement.expression,
146 /** @type {Range} */ (statement.range)[0]
147 )
148 ) {
149 sideEffectsStatement = statement;
150 }
151 break;
152 case "IfStatement":
153 case "WhileStatement":
154 case "DoWhileStatement":
155 if (
156 !parser.isPure(
157 statement.test,
158 /** @type {Range} */ (statement.range)[0]
159 )
160 ) {
161 sideEffectsStatement = statement;
162 }
163 // statement hook will be called for child statements too
164 break;
165 case "ForStatement":
166 if (
167 !parser.isPure(
168 statement.init,
169 /** @type {Range} */ (statement.range)[0]
170 ) ||
171 !parser.isPure(
172 statement.test,
173 statement.init
174 ? /** @type {Range} */ (statement.init.range)[1]
175 : /** @type {Range} */ (statement.range)[0]
176 ) ||
177 !parser.isPure(
178 statement.update,
179 statement.test
180 ? /** @type {Range} */ (statement.test.range)[1]
181 : statement.init
182 ? /** @type {Range} */ (statement.init.range)[1]
183 : /** @type {Range} */ (statement.range)[0]
184 )
185 ) {
186 sideEffectsStatement = statement;
187 }
188 // statement hook will be called for child statements too
189 break;
190 case "SwitchStatement":
191 if (
192 !parser.isPure(
193 statement.discriminant,
194 /** @type {Range} */ (statement.range)[0]
195 )
196 ) {
197 sideEffectsStatement = statement;
198 }
199 // statement hook will be called for child statements too
200 break;
201 case "VariableDeclaration":
202 case "ClassDeclaration":
203 case "FunctionDeclaration":
204 if (
205 !parser.isPure(
206 statement,
207 /** @type {Range} */ (statement.range)[0]
208 )
209 ) {
210 sideEffectsStatement = statement;
211 }
212 break;
213 case "ExportNamedDeclaration":
214 case "ExportDefaultDeclaration":
215 if (
216 !parser.isPure(
217 /** @type {TODO} */
218 (statement.declaration),
219 /** @type {Range} */ (statement.range)[0]
220 )
221 ) {
222 sideEffectsStatement = statement;
223 }
224 break;
225 case "LabeledStatement":
226 case "BlockStatement":
227 // statement hook will be called for child statements too
228 break;
229 case "EmptyStatement":
230 break;
231 case "ExportAllDeclaration":
232 case "ImportDeclaration":
233 // imports will be handled by the dependencies
234 break;
235 default:
236 sideEffectsStatement = statement;
237 break;
238 }
239 }
240 );
241 parser.hooks.finish.tap(PLUGIN_NAME, () => {
242 if (sideEffectsStatement === undefined) {
243 /** @type {BuildMeta} */
244 (parser.state.module.buildMeta).sideEffectFree = true;
245 } else {
246 const { loc, type } = sideEffectsStatement;
247 moduleGraph
248 .getOptimizationBailout(parser.state.module)
249 .push(
250 () =>
251 `Statement (${type}) with side effects in source code at ${formatLocation(
252 /** @type {DependencyLocation} */ (loc)
253 )}`
254 );
255 }
256 });
257 };
258 for (const key of [
259 JAVASCRIPT_MODULE_TYPE_AUTO,
260 JAVASCRIPT_MODULE_TYPE_ESM,
261 JAVASCRIPT_MODULE_TYPE_DYNAMIC
262 ]) {
263 normalModuleFactory.hooks.parser
264 .for(key)
265 .tap(PLUGIN_NAME, parserHandler);
266 }
267 }
268 compilation.hooks.optimizeDependencies.tap(
269 {
270 name: PLUGIN_NAME,
271 stage: STAGE_DEFAULT
272 },
273 modules => {
274 const logger = compilation.getLogger(
275 "webpack.SideEffectsFlagPlugin"
276 );
277
278 logger.time("update dependencies");
279
280 const optimizedModules = new Set();
281
282 /**
283 * @param {Module} module module
284 */
285 const optimizeIncomingConnections = module => {
286 if (optimizedModules.has(module)) return;
287 optimizedModules.add(module);
288 if (module.getSideEffectsConnectionState(moduleGraph) === false) {
289 const exportsInfo = moduleGraph.getExportsInfo(module);
290 for (const connection of moduleGraph.getIncomingConnections(
291 module
292 )) {
293 const dep = connection.dependency;
294 let isReexport;
295 if (
296 (isReexport =
297 dep instanceof
298 HarmonyExportImportedSpecifierDependency) ||
299 (dep instanceof HarmonyImportSpecifierDependency &&
300 !dep.namespaceObjectAsContext)
301 ) {
302 if (connection.originModule !== null) {
303 optimizeIncomingConnections(connection.originModule);
304 }
305 // TODO improve for export *
306 if (isReexport && dep.name) {
307 const exportInfo = moduleGraph.getExportInfo(
308 /** @type {Module} */ (connection.originModule),
309 dep.name
310 );
311 exportInfo.moveTarget(
312 moduleGraph,
313 ({ module }) =>
314 module.getSideEffectsConnectionState(moduleGraph) ===
315 false,
316 ({ module: newModule, export: exportName }) => {
317 moduleGraph.updateModule(dep, newModule);
318 moduleGraph.addExplanation(
319 dep,
320 "(skipped side-effect-free modules)"
321 );
322 const ids = dep.getIds(moduleGraph);
323 dep.setIds(
324 moduleGraph,
325 exportName
326 ? [...exportName, ...ids.slice(1)]
327 : ids.slice(1)
328 );
329 return /** @type {ModuleGraphConnection} */ (
330 moduleGraph.getConnection(dep)
331 );
332 }
333 );
334 continue;
335 }
336 // TODO improve for nested imports
337 const ids = dep.getIds(moduleGraph);
338 if (ids.length > 0) {
339 const exportInfo = exportsInfo.getExportInfo(ids[0]);
340 const target = exportInfo.getTarget(
341 moduleGraph,
342 ({ module }) =>
343 module.getSideEffectsConnectionState(moduleGraph) ===
344 false
345 );
346 if (!target) continue;
347
348 moduleGraph.updateModule(dep, target.module);
349 moduleGraph.addExplanation(
350 dep,
351 "(skipped side-effect-free modules)"
352 );
353 dep.setIds(
354 moduleGraph,
355 target.export
356 ? [...target.export, ...ids.slice(1)]
357 : ids.slice(1)
358 );
359 }
360 }
361 }
362 }
363 };
364
365 for (const module of modules) {
366 optimizeIncomingConnections(module);
367 }
368 logger.timeEnd("update dependencies");
369 }
370 );
371 }
372 );
373 }
374
375 /**
376 * @param {string} moduleName the module name
377 * @param {undefined | boolean | string | string[]} flagValue the flag value
378 * @param {Map<string, RegExp>} cache cache for glob to regexp
379 * @returns {boolean | undefined} true, when the module has side effects, undefined or false when not
380 */
381 static moduleHasSideEffects(moduleName, flagValue, cache) {
382 switch (typeof flagValue) {
383 case "undefined":
384 return true;
385 case "boolean":
386 return flagValue;
387 case "string":
388 return globToRegexp(flagValue, cache).test(moduleName);
389 case "object":
390 return flagValue.some(glob =>
391 SideEffectsFlagPlugin.moduleHasSideEffects(moduleName, glob, cache)
392 );
393 }
394 }
395}
396module.exports = SideEffectsFlagPlugin;
Note: See TracBrowser for help on using the repository browser.