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

4 years ago
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const mm = require("micromatch");
  7. const HarmonyExportImportedSpecifierDependency = require("../dependencies/HarmonyExportImportedSpecifierDependency");
  8. const HarmonyImportSideEffectDependency = require("../dependencies/HarmonyImportSideEffectDependency");
  9. const HarmonyImportSpecifierDependency = require("../dependencies/HarmonyImportSpecifierDependency");
  10. /** @typedef {import("../Module")} Module */
  11. /** @typedef {import("../Dependency")} Dependency */
  12. /**
  13. * @typedef {Object} ExportInModule
  14. * @property {Module} module the module
  15. * @property {string} exportName the name of the export
  16. */
  17. class SideEffectsFlagPlugin {
  18. apply(compiler) {
  19. compiler.hooks.normalModuleFactory.tap("SideEffectsFlagPlugin", nmf => {
  20. nmf.hooks.module.tap("SideEffectsFlagPlugin", (module, data) => {
  21. const resolveData = data.resourceResolveData;
  22. if (
  23. resolveData &&
  24. resolveData.descriptionFileData &&
  25. resolveData.relativePath
  26. ) {
  27. const sideEffects = resolveData.descriptionFileData.sideEffects;
  28. const hasSideEffects = SideEffectsFlagPlugin.moduleHasSideEffects(
  29. resolveData.relativePath,
  30. sideEffects
  31. );
  32. if (!hasSideEffects) {
  33. module.factoryMeta.sideEffectFree = true;
  34. }
  35. }
  36. return module;
  37. });
  38. nmf.hooks.module.tap("SideEffectsFlagPlugin", (module, data) => {
  39. if (data.settings.sideEffects === false) {
  40. module.factoryMeta.sideEffectFree = true;
  41. } else if (data.settings.sideEffects === true) {
  42. module.factoryMeta.sideEffectFree = false;
  43. }
  44. });
  45. });
  46. compiler.hooks.compilation.tap("SideEffectsFlagPlugin", compilation => {
  47. compilation.hooks.optimizeDependencies.tap(
  48. "SideEffectsFlagPlugin",
  49. modules => {
  50. /** @type {Map<Module, Map<string, ExportInModule>>} */
  51. const reexportMaps = new Map();
  52. // Capture reexports of sideEffectFree modules
  53. for (const module of modules) {
  54. /** @type {Dependency[]} */
  55. const removeDependencies = [];
  56. for (const dep of module.dependencies) {
  57. if (dep instanceof HarmonyImportSideEffectDependency) {
  58. if (dep.module && dep.module.factoryMeta.sideEffectFree) {
  59. removeDependencies.push(dep);
  60. }
  61. } else if (
  62. dep instanceof HarmonyExportImportedSpecifierDependency
  63. ) {
  64. if (module.factoryMeta.sideEffectFree) {
  65. const mode = dep.getMode(true);
  66. if (mode.type === "safe-reexport") {
  67. let map = reexportMaps.get(module);
  68. if (!map) {
  69. reexportMaps.set(module, (map = new Map()));
  70. }
  71. for (const pair of mode.map) {
  72. map.set(pair[0], {
  73. module: mode.module,
  74. exportName: pair[1]
  75. });
  76. }
  77. }
  78. }
  79. }
  80. }
  81. }
  82. // Flatten reexports
  83. for (const map of reexportMaps.values()) {
  84. for (const pair of map) {
  85. let mapping = pair[1];
  86. while (mapping) {
  87. const innerMap = reexportMaps.get(mapping.module);
  88. if (!innerMap) break;
  89. const newMapping = innerMap.get(mapping.exportName);
  90. if (newMapping) {
  91. map.set(pair[0], newMapping);
  92. }
  93. mapping = newMapping;
  94. }
  95. }
  96. }
  97. // Update imports along the reexports from sideEffectFree modules
  98. for (const pair of reexportMaps) {
  99. const module = pair[0];
  100. const map = pair[1];
  101. let newReasons = undefined;
  102. for (let i = 0; i < module.reasons.length; i++) {
  103. const reason = module.reasons[i];
  104. const dep = reason.dependency;
  105. if (
  106. dep instanceof HarmonyExportImportedSpecifierDependency ||
  107. (dep instanceof HarmonyImportSpecifierDependency &&
  108. !dep.namespaceObjectAsContext)
  109. ) {
  110. const mapping = map.get(dep._id);
  111. if (mapping) {
  112. dep.redirectedModule = mapping.module;
  113. dep.redirectedId = mapping.exportName;
  114. mapping.module.addReason(
  115. reason.module,
  116. dep,
  117. reason.explanation
  118. ? reason.explanation +
  119. " (skipped side-effect-free modules)"
  120. : "(skipped side-effect-free modules)"
  121. );
  122. // removing the currect reason, by not adding it to the newReasons array
  123. // lazily create the newReasons array
  124. if (newReasons === undefined) {
  125. newReasons = i === 0 ? [] : module.reasons.slice(0, i);
  126. }
  127. continue;
  128. }
  129. }
  130. if (newReasons !== undefined) newReasons.push(reason);
  131. }
  132. if (newReasons !== undefined) {
  133. module.reasons = newReasons;
  134. }
  135. }
  136. }
  137. );
  138. });
  139. }
  140. static moduleHasSideEffects(moduleName, flagValue) {
  141. switch (typeof flagValue) {
  142. case "undefined":
  143. return true;
  144. case "boolean":
  145. return flagValue;
  146. case "string":
  147. if (process.platform === "win32") {
  148. flagValue = flagValue.replace(/\\/g, "/");
  149. }
  150. return mm.isMatch(moduleName, flagValue, {
  151. matchBase: true
  152. });
  153. case "object":
  154. return flagValue.some(glob =>
  155. SideEffectsFlagPlugin.moduleHasSideEffects(moduleName, glob)
  156. );
  157. }
  158. }
  159. }
  160. module.exports = SideEffectsFlagPlugin;