LegacyJavascriptPlugin.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Alexander Akait @alexander-akait
  4. */
  5. "use strict";
  6. const LegacyJavascriptWarning = require("../errors/LegacyJavascriptWarning");
  7. const { compareStrings } = require("../util/comparators");
  8. const { WINDOWS_PATH_SEPARATOR_REGEXP } = require("../util/identifier");
  9. const getModuleSize = require("./getModuleSize");
  10. const getSourceModules = require("./getSourceModules");
  11. /** @import { Environment, PerformanceOptions } from "../../declarations/WebpackOptions" */
  12. /** @import Compiler from "../Compiler" */
  13. /** @import { LegacyPackageDetails } from "../errors/LegacyJavascriptWarning" */
  14. const PLUGIN_NAME = "LegacyJavascriptPlugin";
  15. // Packages that exist to emulate syntax. `core-js` is absent on purpose: it
  16. // polyfills APIs, which `output.environment` says nothing about.
  17. const EMULATING_PACKAGES = ["regenerator-runtime", "@babel/runtime"];
  18. // The syntax the report leans on: each is what one of those packages exists to
  19. // lower, so a target with all of them needs none of the lowering.
  20. /** @type {(keyof Environment)[]} */
  21. const NATIVE_FEATURES = [
  22. "arrowFunction",
  23. "asyncFunction",
  24. "const",
  25. "destructuring",
  26. "forOf",
  27. "generator",
  28. "templateLiteral"
  29. ];
  30. /**
  31. * The emulating package a module belongs to, if it is in one.
  32. * @param {string} resource the module's path
  33. * @returns {string | undefined} the package, or undefined when it is not one
  34. */
  35. const emulatingPackage = (resource) => {
  36. // Separators normalized so one check serves both platforms, and matched
  37. // against the path so an alias or re-export is still recognized.
  38. const path = resource.replace(WINDOWS_PATH_SEPARATOR_REGEXP, "/");
  39. for (const name of EMULATING_PACKAGES) {
  40. if (path.includes(`node_modules/${name}/`)) return name;
  41. }
  42. return undefined;
  43. };
  44. class LegacyJavascriptPlugin {
  45. /**
  46. * Creates an instance of LegacyJavascriptPlugin.
  47. * @param {PerformanceOptions} options the plugin options
  48. */
  49. constructor(options) {
  50. /** @type {PerformanceOptions["hints"]} */
  51. this.hints = options.hints;
  52. }
  53. /**
  54. * Applies the plugin by registering its hooks on the compiler.
  55. * @param {Compiler} compiler the compiler instance
  56. * @returns {void}
  57. */
  58. apply(compiler) {
  59. const hints = this.hints;
  60. if (!hints) return;
  61. compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
  62. // `afterSeal` is past the hash, which folds every message into it — a
  63. // hint reported earlier would change the build's identity.
  64. compilation.hooks.afterSeal.tap(PLUGIN_NAME, () => {
  65. const environment = compilation.outputOptions.environment;
  66. // Reported only when the target has every one of them: emulating a
  67. // feature the target is missing is the polyfill doing its job.
  68. for (const feature of NATIVE_FEATURES) {
  69. if (!environment[feature]) return;
  70. }
  71. /** @type {Map<string, LegacyPackageDetails>} */
  72. const found = new Map();
  73. let totalSize = 0;
  74. for (const module of compilation.modules) {
  75. for (const inner of getSourceModules(module)) {
  76. const resource = inner.nameForCondition();
  77. if (!resource) continue;
  78. const name = emulatingPackage(resource);
  79. if (!name) continue;
  80. const size = getModuleSize(inner);
  81. const details = found.get(name);
  82. totalSize += size;
  83. if (details === undefined) {
  84. found.set(name, { name, modules: 1, size });
  85. } else {
  86. details.modules++;
  87. details.size += size;
  88. }
  89. }
  90. }
  91. if (found.size === 0) return;
  92. // Ties break by name: module order is not stable across runs.
  93. const packages = [...found.values()].sort(
  94. (a, b) => b.size - a.size || compareStrings(a.name, b.name)
  95. );
  96. const warning = new LegacyJavascriptWarning(
  97. packages,
  98. totalSize,
  99. NATIVE_FEATURES
  100. );
  101. if (hints === "error") {
  102. compilation.errors.push(warning);
  103. } else if (hints === "stats") {
  104. compilation.hints.push(warning);
  105. } else {
  106. compilation.warnings.push(warning);
  107. }
  108. });
  109. });
  110. }
  111. }
  112. module.exports = LegacyJavascriptPlugin;