You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
169 lines
5.1 KiB
169 lines
5.1 KiB
/* |
|
MIT License http://www.opensource.org/licenses/mit-license.php |
|
Author Tobias Koppers @sokra |
|
*/ |
|
"use strict"; |
|
|
|
const mm = require("micromatch"); |
|
const HarmonyExportImportedSpecifierDependency = require("../dependencies/HarmonyExportImportedSpecifierDependency"); |
|
const HarmonyImportSideEffectDependency = require("../dependencies/HarmonyImportSideEffectDependency"); |
|
const HarmonyImportSpecifierDependency = require("../dependencies/HarmonyImportSpecifierDependency"); |
|
|
|
/** @typedef {import("../Module")} Module */ |
|
/** @typedef {import("../Dependency")} Dependency */ |
|
|
|
/** |
|
* @typedef {Object} ExportInModule |
|
* @property {Module} module the module |
|
* @property {string} exportName the name of the export |
|
*/ |
|
|
|
class SideEffectsFlagPlugin { |
|
apply(compiler) { |
|
compiler.hooks.normalModuleFactory.tap("SideEffectsFlagPlugin", nmf => { |
|
nmf.hooks.module.tap("SideEffectsFlagPlugin", (module, data) => { |
|
const resolveData = data.resourceResolveData; |
|
if ( |
|
resolveData && |
|
resolveData.descriptionFileData && |
|
resolveData.relativePath |
|
) { |
|
const sideEffects = resolveData.descriptionFileData.sideEffects; |
|
const hasSideEffects = SideEffectsFlagPlugin.moduleHasSideEffects( |
|
resolveData.relativePath, |
|
sideEffects |
|
); |
|
if (!hasSideEffects) { |
|
module.factoryMeta.sideEffectFree = true; |
|
} |
|
} |
|
|
|
return module; |
|
}); |
|
nmf.hooks.module.tap("SideEffectsFlagPlugin", (module, data) => { |
|
if (data.settings.sideEffects === false) { |
|
module.factoryMeta.sideEffectFree = true; |
|
} else if (data.settings.sideEffects === true) { |
|
module.factoryMeta.sideEffectFree = false; |
|
} |
|
}); |
|
}); |
|
compiler.hooks.compilation.tap("SideEffectsFlagPlugin", compilation => { |
|
compilation.hooks.optimizeDependencies.tap( |
|
"SideEffectsFlagPlugin", |
|
modules => { |
|
/** @type {Map<Module, Map<string, ExportInModule>>} */ |
|
const reexportMaps = new Map(); |
|
|
|
// Capture reexports of sideEffectFree modules |
|
for (const module of modules) { |
|
/** @type {Dependency[]} */ |
|
const removeDependencies = []; |
|
for (const dep of module.dependencies) { |
|
if (dep instanceof HarmonyImportSideEffectDependency) { |
|
if (dep.module && dep.module.factoryMeta.sideEffectFree) { |
|
removeDependencies.push(dep); |
|
} |
|
} else if ( |
|
dep instanceof HarmonyExportImportedSpecifierDependency |
|
) { |
|
if (module.factoryMeta.sideEffectFree) { |
|
const mode = dep.getMode(true); |
|
if (mode.type === "safe-reexport") { |
|
let map = reexportMaps.get(module); |
|
if (!map) { |
|
reexportMaps.set(module, (map = new Map())); |
|
} |
|
for (const pair of mode.map) { |
|
map.set(pair[0], { |
|
module: mode.module, |
|
exportName: pair[1] |
|
}); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Flatten reexports |
|
for (const map of reexportMaps.values()) { |
|
for (const pair of map) { |
|
let mapping = pair[1]; |
|
while (mapping) { |
|
const innerMap = reexportMaps.get(mapping.module); |
|
if (!innerMap) break; |
|
const newMapping = innerMap.get(mapping.exportName); |
|
if (newMapping) { |
|
map.set(pair[0], newMapping); |
|
} |
|
mapping = newMapping; |
|
} |
|
} |
|
} |
|
|
|
// Update imports along the reexports from sideEffectFree modules |
|
for (const pair of reexportMaps) { |
|
const module = pair[0]; |
|
const map = pair[1]; |
|
let newReasons = undefined; |
|
for (let i = 0; i < module.reasons.length; i++) { |
|
const reason = module.reasons[i]; |
|
const dep = reason.dependency; |
|
if ( |
|
dep instanceof HarmonyExportImportedSpecifierDependency || |
|
(dep instanceof HarmonyImportSpecifierDependency && |
|
!dep.namespaceObjectAsContext) |
|
) { |
|
const mapping = map.get(dep.id); |
|
if (mapping) { |
|
dep.redirectedModule = mapping.module; |
|
dep.redirectedId = mapping.exportName; |
|
mapping.module.addReason( |
|
reason.module, |
|
dep, |
|
reason.explanation |
|
? reason.explanation + |
|
" (skipped side-effect-free modules)" |
|
: "(skipped side-effect-free modules)" |
|
); |
|
// removing the currect reason, by not adding it to the newReasons array |
|
// lazily create the newReasons array |
|
if (newReasons === undefined) { |
|
newReasons = i === 0 ? [] : module.reasons.slice(0, i); |
|
} |
|
continue; |
|
} |
|
} |
|
if (newReasons !== undefined) newReasons.push(reason); |
|
} |
|
if (newReasons !== undefined) { |
|
module.reasons = newReasons; |
|
} |
|
} |
|
} |
|
); |
|
}); |
|
} |
|
|
|
static moduleHasSideEffects(moduleName, flagValue) { |
|
switch (typeof flagValue) { |
|
case "undefined": |
|
return true; |
|
case "boolean": |
|
return flagValue; |
|
case "string": |
|
if (process.platform === "win32") { |
|
flagValue = flagValue.replace(/\\/g, "/"); |
|
} |
|
return mm.isMatch(moduleName, flagValue, { |
|
matchBase: true |
|
}); |
|
case "object": |
|
return flagValue.some(glob => |
|
SideEffectsFlagPlugin.moduleHasSideEffects(moduleName, glob) |
|
); |
|
} |
|
} |
|
} |
|
module.exports = SideEffectsFlagPlugin;
|
|
|