123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- /*
- 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
- * @property {boolean} checked if the export is conditional
- */
- /**
- * @typedef {Object} ReexportInfo
- * @property {Map<string, ExportInModule[]>} static
- * @property {Map<Module, Set<string>>} dynamic
- */
- /**
- * @param {ReexportInfo} info info object
- * @param {string} exportName name of export
- * @returns {ExportInModule | undefined} static export
- */
- const getMappingFromInfo = (info, exportName) => {
- const staticMappings = info.static.get(exportName);
- if (staticMappings !== undefined) {
- if (staticMappings.length === 1) return staticMappings[0];
- return undefined;
- }
- const dynamicMappings = Array.from(info.dynamic).filter(
- ([_, ignored]) => !ignored.has(exportName)
- );
- if (dynamicMappings.length === 1) {
- return {
- module: dynamicMappings[0][0],
- exportName,
- checked: true
- };
- }
- return undefined;
- };
- /**
- * @param {ReexportInfo} info info object
- * @param {string} exportName name of export of source module
- * @param {Module} module the target module
- * @param {string} innerExportName name of export of target module
- * @param {boolean} checked true, if existence of target module is checked
- */
- const addStaticReexport = (
- info,
- exportName,
- module,
- innerExportName,
- checked
- ) => {
- let mappings = info.static.get(exportName);
- if (mappings !== undefined) {
- for (const mapping of mappings) {
- if (mapping.module === module && mapping.exportName === innerExportName) {
- mapping.checked = mapping.checked && checked;
- return;
- }
- }
- } else {
- mappings = [];
- info.static.set(exportName, mappings);
- }
- mappings.push({
- module,
- exportName: innerExportName,
- checked
- });
- };
- /**
- * @param {ReexportInfo} info info object
- * @param {Module} module the reexport module
- * @param {Set<string>} ignored ignore list
- * @returns {void}
- */
- const addDynamicReexport = (info, module, ignored) => {
- const existingList = info.dynamic.get(module);
- if (existingList !== undefined) {
- for (const key of existingList) {
- if (!ignored.has(key)) existingList.delete(key);
- }
- } else {
- info.dynamic.set(module, new Set(ignored));
- }
- };
- 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, ReexportInfo>} */
- 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" ||
- mode.type === "checked-reexport" ||
- mode.type === "dynamic-reexport" ||
- mode.type === "reexport-non-harmony-default" ||
- mode.type === "reexport-non-harmony-default-strict" ||
- mode.type === "reexport-named-default"
- ) {
- let info = reexportMaps.get(module);
- if (!info) {
- reexportMaps.set(
- module,
- (info = {
- static: new Map(),
- dynamic: new Map()
- })
- );
- }
- const targetModule = dep._module;
- switch (mode.type) {
- case "safe-reexport":
- for (const [key, id] of mode.map) {
- if (id) {
- addStaticReexport(
- info,
- key,
- targetModule,
- id,
- false
- );
- }
- }
- break;
- case "checked-reexport":
- for (const [key, id] of mode.map) {
- if (id) {
- addStaticReexport(
- info,
- key,
- targetModule,
- id,
- true
- );
- }
- }
- break;
- case "dynamic-reexport":
- addDynamicReexport(info, targetModule, mode.ignored);
- break;
- case "reexport-non-harmony-default":
- case "reexport-non-harmony-default-strict":
- case "reexport-named-default":
- addStaticReexport(
- info,
- mode.name,
- targetModule,
- "default",
- false
- );
- break;
- }
- }
- }
- }
- }
- }
- // Flatten reexports
- for (const info of reexportMaps.values()) {
- const dynamicReexports = info.dynamic;
- info.dynamic = new Map();
- for (const reexport of dynamicReexports) {
- let [targetModule, ignored] = reexport;
- for (;;) {
- const innerInfo = reexportMaps.get(targetModule);
- if (!innerInfo) break;
- for (const [key, reexports] of innerInfo.static) {
- if (ignored.has(key)) continue;
- for (const { module, exportName, checked } of reexports) {
- addStaticReexport(info, key, module, exportName, checked);
- }
- }
- // Follow dynamic reexport if there is only one
- if (innerInfo.dynamic.size !== 1) {
- // When there are more then one, we don't know which one
- break;
- }
- ignored = new Set(ignored);
- for (const [innerModule, innerIgnored] of innerInfo.dynamic) {
- for (const key of innerIgnored) {
- if (ignored.has(key)) continue;
- // This reexports ends here
- addStaticReexport(info, key, targetModule, key, true);
- ignored.add(key);
- }
- targetModule = innerModule;
- }
- }
- // Update reexport as all other cases has been handled
- addDynamicReexport(info, targetModule, ignored);
- }
- }
- for (const info of reexportMaps.values()) {
- const staticReexports = info.static;
- info.static = new Map();
- for (const [key, reexports] of staticReexports) {
- for (let mapping of reexports) {
- for (;;) {
- const innerInfo = reexportMaps.get(mapping.module);
- if (!innerInfo) break;
- const newMapping = getMappingFromInfo(
- innerInfo,
- mapping.exportName
- );
- if (!newMapping) break;
- mapping = newMapping;
- }
- addStaticReexport(
- info,
- key,
- mapping.module,
- mapping.exportName,
- mapping.checked
- );
- }
- }
- }
- // Update imports along the reexports from sideEffectFree modules
- for (const pair of reexportMaps) {
- const module = pair[0];
- const info = 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)) &&
- dep._id
- ) {
- const mapping = getMappingFromInfo(info, 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;
|