source: trip-planner-front/node_modules/webpack/lib/optimize/SideEffectsFlagPlugin.js@ 8d391a1

Last change on this file since 8d391a1 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

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