| 1 | /*
|
|---|
| 2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
|---|
| 3 | Author Tobias Koppers @sokra
|
|---|
| 4 | */
|
|---|
| 5 |
|
|---|
| 6 | "use strict";
|
|---|
| 7 |
|
|---|
| 8 | const glob2regexp = require("glob-to-regexp");
|
|---|
| 9 | const {
|
|---|
| 10 | JAVASCRIPT_MODULE_TYPE_AUTO,
|
|---|
| 11 | JAVASCRIPT_MODULE_TYPE_DYNAMIC,
|
|---|
| 12 | JAVASCRIPT_MODULE_TYPE_ESM
|
|---|
| 13 | } = require("../ModuleTypeConstants");
|
|---|
| 14 | const { STAGE_DEFAULT } = require("../OptimizationStages");
|
|---|
| 15 | const HarmonyExportImportedSpecifierDependency = require("../dependencies/HarmonyExportImportedSpecifierDependency");
|
|---|
| 16 | const HarmonyImportSpecifierDependency = require("../dependencies/HarmonyImportSpecifierDependency");
|
|---|
| 17 | const formatLocation = require("../util/formatLocation");
|
|---|
| 18 | const { CompilerHintNotationRegExp } = require("../util/magicComment");
|
|---|
| 19 |
|
|---|
| 20 | /** @typedef {import("estree").MaybeNamedClassDeclaration} MaybeNamedClassDeclaration */
|
|---|
| 21 | /** @typedef {import("estree").MaybeNamedFunctionDeclaration} MaybeNamedFunctionDeclaration */
|
|---|
| 22 | /** @typedef {import("estree").ModuleDeclaration} ModuleDeclaration */
|
|---|
| 23 | /** @typedef {import("estree").Statement} Statement */
|
|---|
| 24 | /** @typedef {import("estree").CallExpression} CallExpression */
|
|---|
| 25 | /** @typedef {import("../Compiler")} Compiler */
|
|---|
| 26 | /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
|
|---|
| 27 | /** @typedef {import("../Module")} Module */
|
|---|
| 28 | /** @typedef {import("../Module").BuildMeta} BuildMeta */
|
|---|
| 29 | /** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */
|
|---|
| 30 | /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
|
|---|
| 31 | /** @typedef {import("../javascript/JavascriptParser").Range} Range */
|
|---|
| 32 |
|
|---|
| 33 | /**
|
|---|
| 34 | * Defines the export in module type used by this module.
|
|---|
| 35 | * @typedef {object} ExportInModule
|
|---|
| 36 | * @property {Module} module the module
|
|---|
| 37 | * @property {string} exportName the name of the export
|
|---|
| 38 | * @property {boolean} checked if the export is conditional
|
|---|
| 39 | */
|
|---|
| 40 |
|
|---|
| 41 | /** @typedef {string | boolean | string[] | undefined} SideEffectsFlagValue */
|
|---|
| 42 |
|
|---|
| 43 | /** @typedef {Map<string, RegExp>} CacheItem */
|
|---|
| 44 |
|
|---|
| 45 | /** @type {WeakMap<Compiler, CacheItem>} */
|
|---|
| 46 | const globToRegexpCache = new WeakMap();
|
|---|
| 47 |
|
|---|
| 48 | /**
|
|---|
| 49 | * Returns a regular expression.
|
|---|
| 50 | * @param {string} glob the pattern
|
|---|
| 51 | * @param {CacheItem} cache the glob to RegExp cache
|
|---|
| 52 | * @returns {RegExp} a regular expression
|
|---|
| 53 | */
|
|---|
| 54 | const 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 |
|
|---|
| 67 | /**
|
|---|
| 68 | * @param {JavascriptParser} parser parser
|
|---|
| 69 | * @param {number} start start position
|
|---|
| 70 | * @param {number} end end position
|
|---|
| 71 | * @returns {boolean} if annotation is found in the range
|
|---|
| 72 | */
|
|---|
| 73 | const hasNoSideEffectsNotation = (parser, start, end) => {
|
|---|
| 74 | // Fast path
|
|---|
| 75 | if (end - start < 18) return false;
|
|---|
| 76 |
|
|---|
| 77 | const comments = parser.getComments([start, end]);
|
|---|
| 78 | return comments.some(
|
|---|
| 79 | (c) =>
|
|---|
| 80 | c.type === "Block" &&
|
|---|
| 81 | CompilerHintNotationRegExp.NoSideEffects.test(c.value)
|
|---|
| 82 | );
|
|---|
| 83 | };
|
|---|
| 84 |
|
|---|
| 85 | const PLUGIN_NAME = "SideEffectsFlagPlugin";
|
|---|
| 86 |
|
|---|
| 87 | class SideEffectsFlagPlugin {
|
|---|
| 88 | /**
|
|---|
| 89 | * Creates an instance of SideEffectsFlagPlugin.
|
|---|
| 90 | * @param {boolean} analyseSource analyse source code for side effects
|
|---|
| 91 | */
|
|---|
| 92 | constructor(analyseSource = true) {
|
|---|
| 93 | /** @type {boolean} */
|
|---|
| 94 | this._analyseSource = analyseSource;
|
|---|
| 95 | }
|
|---|
| 96 |
|
|---|
| 97 | /**
|
|---|
| 98 | * Applies the plugin by registering its hooks on the compiler.
|
|---|
| 99 | * @param {Compiler} compiler the compiler instance
|
|---|
| 100 | * @returns {void}
|
|---|
| 101 | */
|
|---|
| 102 | apply(compiler) {
|
|---|
| 103 | let cache = globToRegexpCache.get(compiler.root);
|
|---|
| 104 | if (cache === undefined) {
|
|---|
| 105 | cache = new Map();
|
|---|
| 106 | globToRegexpCache.set(compiler.root, cache);
|
|---|
| 107 | }
|
|---|
| 108 | compiler.hooks.compilation.tap(
|
|---|
| 109 | PLUGIN_NAME,
|
|---|
| 110 | (compilation, { normalModuleFactory }) => {
|
|---|
| 111 | const moduleGraph = compilation.moduleGraph;
|
|---|
| 112 | normalModuleFactory.hooks.module.tap(PLUGIN_NAME, (module, data) => {
|
|---|
| 113 | const resolveData = data.resourceResolveData;
|
|---|
| 114 | if (
|
|---|
| 115 | resolveData &&
|
|---|
| 116 | resolveData.descriptionFileData &&
|
|---|
| 117 | resolveData.relativePath
|
|---|
| 118 | ) {
|
|---|
| 119 | const sideEffects = resolveData.descriptionFileData.sideEffects;
|
|---|
| 120 | if (sideEffects !== undefined) {
|
|---|
| 121 | if (module.factoryMeta === undefined) {
|
|---|
| 122 | module.factoryMeta = {};
|
|---|
| 123 | }
|
|---|
| 124 | const hasSideEffects = SideEffectsFlagPlugin.moduleHasSideEffects(
|
|---|
| 125 | resolveData.relativePath,
|
|---|
| 126 | /** @type {SideEffectsFlagValue} */ (sideEffects),
|
|---|
| 127 | /** @type {CacheItem} */ (cache)
|
|---|
| 128 | );
|
|---|
| 129 | module.factoryMeta.sideEffectFree = !hasSideEffects;
|
|---|
| 130 | }
|
|---|
| 131 | }
|
|---|
| 132 |
|
|---|
| 133 | return module;
|
|---|
| 134 | });
|
|---|
| 135 | normalModuleFactory.hooks.module.tap(PLUGIN_NAME, (module, data) => {
|
|---|
| 136 | const settings = data.settings;
|
|---|
| 137 | if (typeof settings.sideEffects === "boolean") {
|
|---|
| 138 | if (module.factoryMeta === undefined) {
|
|---|
| 139 | module.factoryMeta = {};
|
|---|
| 140 | }
|
|---|
| 141 | module.factoryMeta.sideEffectFree = !settings.sideEffects;
|
|---|
| 142 | }
|
|---|
| 143 | return module;
|
|---|
| 144 | });
|
|---|
| 145 | if (this._analyseSource) {
|
|---|
| 146 | /**
|
|---|
| 147 | * Processes the provided parser.
|
|---|
| 148 | * @param {JavascriptParser} parser the parser
|
|---|
| 149 | * @returns {void}
|
|---|
| 150 | */
|
|---|
| 151 | const applySideEffectsStmtHandler = (parser) => {
|
|---|
| 152 | /** @type {undefined | Statement | ModuleDeclaration | MaybeNamedFunctionDeclaration | MaybeNamedClassDeclaration} */
|
|---|
| 153 | let sideEffectsStatement;
|
|---|
| 154 | parser.hooks.program.tap(PLUGIN_NAME, () => {
|
|---|
| 155 | sideEffectsStatement = undefined;
|
|---|
| 156 | });
|
|---|
| 157 | parser.hooks.statement.tap(
|
|---|
| 158 | { name: PLUGIN_NAME, stage: -100 },
|
|---|
| 159 | (statement) => {
|
|---|
| 160 | if (sideEffectsStatement) return;
|
|---|
| 161 | if (parser.scope.topLevelScope !== true) return;
|
|---|
| 162 | switch (statement.type) {
|
|---|
| 163 | case "ExpressionStatement":
|
|---|
| 164 | if (
|
|---|
| 165 | !parser.isPure(
|
|---|
| 166 | statement.expression,
|
|---|
| 167 | /** @type {Range} */
|
|---|
| 168 | (statement.range)[0]
|
|---|
| 169 | )
|
|---|
| 170 | ) {
|
|---|
| 171 | sideEffectsStatement = statement;
|
|---|
| 172 | }
|
|---|
| 173 | break;
|
|---|
| 174 | case "IfStatement":
|
|---|
| 175 | case "WhileStatement":
|
|---|
| 176 | case "DoWhileStatement":
|
|---|
| 177 | if (
|
|---|
| 178 | !parser.isPure(
|
|---|
| 179 | statement.test,
|
|---|
| 180 | /** @type {Range} */
|
|---|
| 181 | (statement.range)[0]
|
|---|
| 182 | )
|
|---|
| 183 | ) {
|
|---|
| 184 | sideEffectsStatement = statement;
|
|---|
| 185 | }
|
|---|
| 186 | // statement hook will be called for child statements too
|
|---|
| 187 | break;
|
|---|
| 188 | case "ForStatement":
|
|---|
| 189 | if (
|
|---|
| 190 | !parser.isPure(
|
|---|
| 191 | statement.init,
|
|---|
| 192 | /** @type {Range} */ (statement.range)[0]
|
|---|
| 193 | ) ||
|
|---|
| 194 | !parser.isPure(
|
|---|
| 195 | statement.test,
|
|---|
| 196 | statement.init
|
|---|
| 197 | ? /** @type {Range} */ (statement.init.range)[1]
|
|---|
| 198 | : /** @type {Range} */ (statement.range)[0]
|
|---|
| 199 | ) ||
|
|---|
| 200 | !parser.isPure(
|
|---|
| 201 | statement.update,
|
|---|
| 202 | statement.test
|
|---|
| 203 | ? /** @type {Range} */ (statement.test.range)[1]
|
|---|
| 204 | : statement.init
|
|---|
| 205 | ? /** @type {Range} */ (statement.init.range)[1]
|
|---|
| 206 | : /** @type {Range} */ (statement.range)[0]
|
|---|
| 207 | )
|
|---|
| 208 | ) {
|
|---|
| 209 | sideEffectsStatement = statement;
|
|---|
| 210 | }
|
|---|
| 211 | // statement hook will be called for child statements too
|
|---|
| 212 | break;
|
|---|
| 213 | case "SwitchStatement":
|
|---|
| 214 | if (
|
|---|
| 215 | !parser.isPure(
|
|---|
| 216 | statement.discriminant,
|
|---|
| 217 | /** @type {Range} */
|
|---|
| 218 | (statement.range)[0]
|
|---|
| 219 | )
|
|---|
| 220 | ) {
|
|---|
| 221 | sideEffectsStatement = statement;
|
|---|
| 222 | }
|
|---|
| 223 | // statement hook will be called for child statements too
|
|---|
| 224 | break;
|
|---|
| 225 | case "VariableDeclaration":
|
|---|
| 226 | case "ClassDeclaration":
|
|---|
| 227 | case "FunctionDeclaration":
|
|---|
| 228 | if (
|
|---|
| 229 | !parser.isPure(
|
|---|
| 230 | statement,
|
|---|
| 231 | /** @type {Range} */ (statement.range)[0]
|
|---|
| 232 | )
|
|---|
| 233 | ) {
|
|---|
| 234 | sideEffectsStatement = statement;
|
|---|
| 235 | }
|
|---|
| 236 | break;
|
|---|
| 237 | case "ExportNamedDeclaration":
|
|---|
| 238 | case "ExportDefaultDeclaration":
|
|---|
| 239 | if (
|
|---|
| 240 | !parser.isPure(
|
|---|
| 241 | statement.declaration,
|
|---|
| 242 | /** @type {Range} */
|
|---|
| 243 | (statement.range)[0]
|
|---|
| 244 | )
|
|---|
| 245 | ) {
|
|---|
| 246 | sideEffectsStatement = statement;
|
|---|
| 247 | }
|
|---|
| 248 | break;
|
|---|
| 249 | case "LabeledStatement":
|
|---|
| 250 | case "BlockStatement":
|
|---|
| 251 | // statement hook will be called for child statements too
|
|---|
| 252 | break;
|
|---|
| 253 | case "EmptyStatement":
|
|---|
| 254 | break;
|
|---|
| 255 | case "ExportAllDeclaration":
|
|---|
| 256 | case "ImportDeclaration":
|
|---|
| 257 | // imports will be handled by the dependencies
|
|---|
| 258 | break;
|
|---|
| 259 | default:
|
|---|
| 260 | sideEffectsStatement = statement;
|
|---|
| 261 | break;
|
|---|
| 262 | }
|
|---|
| 263 | }
|
|---|
| 264 | );
|
|---|
| 265 | parser.hooks.finish.tap(PLUGIN_NAME, () => {
|
|---|
| 266 | if (sideEffectsStatement === undefined) {
|
|---|
| 267 | /** @type {BuildMeta} */
|
|---|
| 268 | (parser.state.module.buildMeta).sideEffectFree = true;
|
|---|
| 269 | } else {
|
|---|
| 270 | const { loc, type } = sideEffectsStatement;
|
|---|
| 271 | moduleGraph
|
|---|
| 272 | .getOptimizationBailout(parser.state.module)
|
|---|
| 273 | .push(
|
|---|
| 274 | () =>
|
|---|
| 275 | `Statement (${type}) with side effects in source code at ${formatLocation(
|
|---|
| 276 | /** @type {DependencyLocation} */ (loc)
|
|---|
| 277 | )}`
|
|---|
| 278 | );
|
|---|
| 279 | }
|
|---|
| 280 | });
|
|---|
| 281 | };
|
|---|
| 282 |
|
|---|
| 283 | /**
|
|---|
| 284 | * @param {JavascriptParser} parser the parser
|
|---|
| 285 | * @returns {void}
|
|---|
| 286 | */
|
|---|
| 287 | const applyNoSideEffectsNotationHandler = (parser) => {
|
|---|
| 288 | /** @type {Set<string>} */
|
|---|
| 289 | let noSideEffectsFnNames;
|
|---|
| 290 |
|
|---|
| 291 | parser.hooks.program.tap(PLUGIN_NAME, () => {
|
|---|
| 292 | noSideEffectsFnNames = new Set();
|
|---|
| 293 | });
|
|---|
| 294 |
|
|---|
| 295 | // Detect on function declarations
|
|---|
| 296 | // Covers:
|
|---|
| 297 | // 1. function foo
|
|---|
| 298 | // 2. export function foo
|
|---|
| 299 | // 3. export default function foo
|
|---|
| 300 | parser.hooks.preStatement.tap(PLUGIN_NAME, (statement) => {
|
|---|
| 301 | if (parser.scope.topLevelScope !== true) return;
|
|---|
| 302 | if (statement.type !== "FunctionDeclaration" || !statement.id) {
|
|---|
| 303 | return;
|
|---|
| 304 | }
|
|---|
| 305 | const commentsStart = parser.prevStatement
|
|---|
| 306 | ? /** @type {Range} */ (parser.prevStatement.range)[1]
|
|---|
| 307 | : 0;
|
|---|
| 308 | if (
|
|---|
| 309 | hasNoSideEffectsNotation(
|
|---|
| 310 | parser,
|
|---|
| 311 | commentsStart,
|
|---|
| 312 | /** @type {Range} */ (statement.range)[0]
|
|---|
| 313 | )
|
|---|
| 314 | ) {
|
|---|
| 315 | noSideEffectsFnNames.add(statement.id.name);
|
|---|
| 316 | }
|
|---|
| 317 | });
|
|---|
| 318 |
|
|---|
| 319 | // Detect on variable declarations with function init
|
|---|
| 320 | parser.hooks.preDeclarator.tap(PLUGIN_NAME, (decl, statement) => {
|
|---|
| 321 | if (parser.scope.topLevelScope !== true) return;
|
|---|
| 322 | if (!decl.init || decl.id.type !== "Identifier") return;
|
|---|
| 323 | if (!decl.init.type.endsWith("FunctionExpression")) return;
|
|---|
| 324 |
|
|---|
| 325 | let hasAnnotation = false;
|
|---|
| 326 | // Before the VariableDeclaration (only for const)
|
|---|
| 327 | if (statement.kind === "const") {
|
|---|
| 328 | const commentsStart = parser.prevStatement
|
|---|
| 329 | ? /** @type {Range} */ (parser.prevStatement.range)[1]
|
|---|
| 330 | : 0;
|
|---|
| 331 | hasAnnotation = hasNoSideEffectsNotation(
|
|---|
| 332 | parser,
|
|---|
| 333 | commentsStart,
|
|---|
| 334 | /** @type {Range} */ (statement.range)[0]
|
|---|
| 335 | );
|
|---|
| 336 | }
|
|---|
| 337 |
|
|---|
| 338 | if (!hasAnnotation) {
|
|---|
| 339 | hasAnnotation = hasNoSideEffectsNotation(
|
|---|
| 340 | parser,
|
|---|
| 341 | /** @type {Range} */ (decl.id.range)[1],
|
|---|
| 342 | /** @type {Range} */ (decl.init.range)[0]
|
|---|
| 343 | );
|
|---|
| 344 | }
|
|---|
| 345 | if (hasAnnotation) {
|
|---|
| 346 | noSideEffectsFnNames.add(decl.id.name);
|
|---|
| 347 | }
|
|---|
| 348 | });
|
|---|
| 349 |
|
|---|
| 350 | // Mark calls to annotated functions as pure
|
|---|
| 351 | parser.hooks.isPure
|
|---|
| 352 | .for("CallExpression")
|
|---|
| 353 | .tap(PLUGIN_NAME, (expression, commentsStartPos) => {
|
|---|
| 354 | const expr = /** @type {CallExpression} */ (expression);
|
|---|
| 355 | if (expr.callee.type !== "Identifier") return;
|
|---|
| 356 | if (!noSideEffectsFnNames.has(expr.callee.name)) return;
|
|---|
| 357 | commentsStartPos = /** @type {Range} */ (expr.callee.range)[1];
|
|---|
| 358 | for (const arg of expr.arguments) {
|
|---|
| 359 | if (arg.type === "SpreadElement") return;
|
|---|
| 360 | if (!parser.isPure(arg, commentsStartPos)) return;
|
|---|
| 361 | commentsStartPos = /** @type {Range} */ (arg.range)[1];
|
|---|
| 362 | }
|
|---|
| 363 | return true;
|
|---|
| 364 | });
|
|---|
| 365 | };
|
|---|
| 366 |
|
|---|
| 367 | for (const key of [
|
|---|
| 368 | JAVASCRIPT_MODULE_TYPE_AUTO,
|
|---|
| 369 | JAVASCRIPT_MODULE_TYPE_ESM,
|
|---|
| 370 | JAVASCRIPT_MODULE_TYPE_DYNAMIC
|
|---|
| 371 | ]) {
|
|---|
| 372 | normalModuleFactory.hooks.parser
|
|---|
| 373 | .for(key)
|
|---|
| 374 | .tap(PLUGIN_NAME, (parser) => {
|
|---|
| 375 | applyNoSideEffectsNotationHandler(parser);
|
|---|
| 376 | applySideEffectsStmtHandler(parser);
|
|---|
| 377 | });
|
|---|
| 378 | }
|
|---|
| 379 | }
|
|---|
| 380 | compilation.hooks.optimizeDependencies.tap(
|
|---|
| 381 | {
|
|---|
| 382 | name: PLUGIN_NAME,
|
|---|
| 383 | stage: STAGE_DEFAULT
|
|---|
| 384 | },
|
|---|
| 385 | (modules) => {
|
|---|
| 386 | const logger = compilation.getLogger(
|
|---|
| 387 | "webpack.SideEffectsFlagPlugin"
|
|---|
| 388 | );
|
|---|
| 389 |
|
|---|
| 390 | logger.time("update dependencies");
|
|---|
| 391 |
|
|---|
| 392 | /** @type {Set<Module>} */
|
|---|
| 393 | const optimizedModules = new Set();
|
|---|
| 394 |
|
|---|
| 395 | /**
|
|---|
| 396 | * Optimize incoming connections.
|
|---|
| 397 | * @param {Module} module module
|
|---|
| 398 | */
|
|---|
| 399 | const optimizeIncomingConnections = (module) => {
|
|---|
| 400 | if (optimizedModules.has(module)) return;
|
|---|
| 401 | optimizedModules.add(module);
|
|---|
| 402 | if (module.getSideEffectsConnectionState(moduleGraph) === false) {
|
|---|
| 403 | const exportsInfo = moduleGraph.getExportsInfo(module);
|
|---|
| 404 | for (const connection of moduleGraph.getIncomingConnections(
|
|---|
| 405 | module
|
|---|
| 406 | )) {
|
|---|
| 407 | const dep = connection.dependency;
|
|---|
| 408 | /** @type {boolean} */
|
|---|
| 409 | let isReexport;
|
|---|
| 410 | if (
|
|---|
| 411 | (isReexport =
|
|---|
| 412 | dep instanceof
|
|---|
| 413 | HarmonyExportImportedSpecifierDependency) ||
|
|---|
| 414 | (dep instanceof HarmonyImportSpecifierDependency &&
|
|---|
| 415 | !dep.namespaceObjectAsContext)
|
|---|
| 416 | ) {
|
|---|
| 417 | if (connection.originModule !== null) {
|
|---|
| 418 | optimizeIncomingConnections(connection.originModule);
|
|---|
| 419 | }
|
|---|
| 420 | // TODO improve for export *
|
|---|
| 421 | if (isReexport && dep.name) {
|
|---|
| 422 | const exportInfo = moduleGraph.getExportInfo(
|
|---|
| 423 | /** @type {Module} */ (connection.originModule),
|
|---|
| 424 | dep.name
|
|---|
| 425 | );
|
|---|
| 426 | exportInfo.moveTarget(
|
|---|
| 427 | moduleGraph,
|
|---|
| 428 | ({ module }) =>
|
|---|
| 429 | module.getSideEffectsConnectionState(moduleGraph) ===
|
|---|
| 430 | false,
|
|---|
| 431 | ({
|
|---|
| 432 | module: newModule,
|
|---|
| 433 | export: exportName,
|
|---|
| 434 | connection: targetConnection
|
|---|
| 435 | }) => {
|
|---|
| 436 | moduleGraph.updateModule(dep, newModule);
|
|---|
| 437 | moduleGraph.updateParent(
|
|---|
| 438 | dep,
|
|---|
| 439 | targetConnection,
|
|---|
| 440 | /** @type {Module} */ (connection.originModule)
|
|---|
| 441 | );
|
|---|
| 442 | moduleGraph.addExplanation(
|
|---|
| 443 | dep,
|
|---|
| 444 | "(skipped side-effect-free modules)"
|
|---|
| 445 | );
|
|---|
| 446 | const ids = dep.getIds(moduleGraph);
|
|---|
| 447 | dep.setIds(
|
|---|
| 448 | moduleGraph,
|
|---|
| 449 | exportName
|
|---|
| 450 | ? [...exportName, ...ids.slice(1)]
|
|---|
| 451 | : ids.slice(1)
|
|---|
| 452 | );
|
|---|
| 453 | return /** @type {ModuleGraphConnection} */ (
|
|---|
| 454 | moduleGraph.getConnection(dep)
|
|---|
| 455 | );
|
|---|
| 456 | }
|
|---|
| 457 | );
|
|---|
| 458 | continue;
|
|---|
| 459 | }
|
|---|
| 460 | // TODO improve for nested imports
|
|---|
| 461 | const ids = dep.getIds(moduleGraph);
|
|---|
| 462 | if (ids.length > 0) {
|
|---|
| 463 | const exportInfo = exportsInfo.getExportInfo(ids[0]);
|
|---|
| 464 | const target = exportInfo.getTarget(
|
|---|
| 465 | moduleGraph,
|
|---|
| 466 | ({ module }) =>
|
|---|
| 467 | module.getSideEffectsConnectionState(moduleGraph) ===
|
|---|
| 468 | false
|
|---|
| 469 | );
|
|---|
| 470 | if (!target) continue;
|
|---|
| 471 |
|
|---|
| 472 | moduleGraph.updateModule(dep, target.module);
|
|---|
| 473 | moduleGraph.updateParent(
|
|---|
| 474 | dep,
|
|---|
| 475 | /** @type {ModuleGraphConnection} */ (
|
|---|
| 476 | target.connection
|
|---|
| 477 | ),
|
|---|
| 478 | /** @type {Module} */ (connection.originModule)
|
|---|
| 479 | );
|
|---|
| 480 | moduleGraph.addExplanation(
|
|---|
| 481 | dep,
|
|---|
| 482 | "(skipped side-effect-free modules)"
|
|---|
| 483 | );
|
|---|
| 484 | dep.setIds(
|
|---|
| 485 | moduleGraph,
|
|---|
| 486 | target.export
|
|---|
| 487 | ? [...target.export, ...ids.slice(1)]
|
|---|
| 488 | : ids.slice(1)
|
|---|
| 489 | );
|
|---|
| 490 | }
|
|---|
| 491 | }
|
|---|
| 492 | }
|
|---|
| 493 | }
|
|---|
| 494 | };
|
|---|
| 495 |
|
|---|
| 496 | for (const module of modules) {
|
|---|
| 497 | optimizeIncomingConnections(module);
|
|---|
| 498 | }
|
|---|
| 499 | moduleGraph.finishUpdateParent();
|
|---|
| 500 | logger.timeEnd("update dependencies");
|
|---|
| 501 | }
|
|---|
| 502 | );
|
|---|
| 503 | }
|
|---|
| 504 | );
|
|---|
| 505 | }
|
|---|
| 506 |
|
|---|
| 507 | /**
|
|---|
| 508 | * Module has side effects.
|
|---|
| 509 | * @param {string} moduleName the module name
|
|---|
| 510 | * @param {SideEffectsFlagValue} flagValue the flag value
|
|---|
| 511 | * @param {CacheItem} cache cache for glob to regexp
|
|---|
| 512 | * @returns {boolean | undefined} true, when the module has side effects, undefined or false when not
|
|---|
| 513 | */
|
|---|
| 514 | static moduleHasSideEffects(moduleName, flagValue, cache) {
|
|---|
| 515 | switch (typeof flagValue) {
|
|---|
| 516 | case "undefined":
|
|---|
| 517 | return true;
|
|---|
| 518 | case "boolean":
|
|---|
| 519 | return flagValue;
|
|---|
| 520 | case "string":
|
|---|
| 521 | return globToRegexp(flagValue, cache).test(moduleName);
|
|---|
| 522 | case "object":
|
|---|
| 523 | return flagValue.some((glob) =>
|
|---|
| 524 | SideEffectsFlagPlugin.moduleHasSideEffects(moduleName, glob, cache)
|
|---|
| 525 | );
|
|---|
| 526 | }
|
|---|
| 527 | }
|
|---|
| 528 | }
|
|---|
| 529 |
|
|---|
| 530 | module.exports = SideEffectsFlagPlugin;
|
|---|