ProvideSharedPlugin.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra and Zackary Jackson @ScriptedAlchemy
  4. */
  5. "use strict";
  6. const WebpackError = require("../WebpackError");
  7. const { parseOptions } = require("../container/options");
  8. const createSchemaValidation = require("../util/create-schema-validation");
  9. const ProvideForSharedDependency = require("./ProvideForSharedDependency");
  10. const ProvideSharedDependency = require("./ProvideSharedDependency");
  11. const ProvideSharedModuleFactory = require("./ProvideSharedModuleFactory");
  12. /** @typedef {import("../../declarations/plugins/sharing/ProvideSharedPlugin").ProvideSharedPluginOptions} ProvideSharedPluginOptions */
  13. /** @typedef {import("../Compilation")} Compilation */
  14. /** @typedef {import("../Compiler")} Compiler */
  15. /** @typedef {import("../NormalModuleFactory").NormalModuleCreateData} NormalModuleCreateData */
  16. const validate = createSchemaValidation(
  17. require("../../schemas/plugins/sharing/ProvideSharedPlugin.check.js"),
  18. () => require("../../schemas/plugins/sharing/ProvideSharedPlugin.json"),
  19. {
  20. name: "Provide Shared Plugin",
  21. baseDataPath: "options"
  22. }
  23. );
  24. /**
  25. * @typedef {object} ProvideOptions
  26. * @property {string} shareKey
  27. * @property {string} shareScope
  28. * @property {string | undefined | false} version
  29. * @property {boolean} eager
  30. */
  31. /** @typedef {Map<string, { config: ProvideOptions, version: string | undefined | false }>} ResolvedProvideMap */
  32. const PLUGIN_NAME = "ProvideSharedPlugin";
  33. class ProvideSharedPlugin {
  34. /**
  35. * @param {ProvideSharedPluginOptions} options options
  36. */
  37. constructor(options) {
  38. validate(options);
  39. this._provides = /** @type {[string, ProvideOptions][]} */ (
  40. parseOptions(
  41. options.provides,
  42. item => {
  43. if (Array.isArray(item))
  44. throw new Error("Unexpected array of provides");
  45. /** @type {ProvideOptions} */
  46. const result = {
  47. shareKey: item,
  48. version: undefined,
  49. shareScope: options.shareScope || "default",
  50. eager: false
  51. };
  52. return result;
  53. },
  54. item => ({
  55. shareKey: item.shareKey,
  56. version: item.version,
  57. shareScope: item.shareScope || options.shareScope || "default",
  58. eager: Boolean(item.eager)
  59. })
  60. )
  61. );
  62. this._provides.sort(([a], [b]) => {
  63. if (a < b) return -1;
  64. if (b < a) return 1;
  65. return 0;
  66. });
  67. }
  68. /**
  69. * Apply the plugin
  70. * @param {Compiler} compiler the compiler instance
  71. * @returns {void}
  72. */
  73. apply(compiler) {
  74. /** @type {WeakMap<Compilation, ResolvedProvideMap>} */
  75. const compilationData = new WeakMap();
  76. compiler.hooks.compilation.tap(
  77. PLUGIN_NAME,
  78. (compilation, { normalModuleFactory }) => {
  79. /** @type {ResolvedProvideMap} */
  80. const resolvedProvideMap = new Map();
  81. /** @type {Map<string, ProvideOptions>} */
  82. const matchProvides = new Map();
  83. /** @type {Map<string, ProvideOptions>} */
  84. const prefixMatchProvides = new Map();
  85. for (const [request, config] of this._provides) {
  86. if (/^(\/|[A-Za-z]:\\|\\\\|\.\.?(\/|$))/.test(request)) {
  87. // relative request
  88. resolvedProvideMap.set(request, {
  89. config,
  90. version: config.version
  91. });
  92. } else if (/^(\/|[A-Za-z]:\\|\\\\)/.test(request)) {
  93. // absolute path
  94. resolvedProvideMap.set(request, {
  95. config,
  96. version: config.version
  97. });
  98. } else if (request.endsWith("/")) {
  99. // module request prefix
  100. prefixMatchProvides.set(request, config);
  101. } else {
  102. // module request
  103. matchProvides.set(request, config);
  104. }
  105. }
  106. compilationData.set(compilation, resolvedProvideMap);
  107. /**
  108. * @param {string} key key
  109. * @param {ProvideOptions} config config
  110. * @param {NormalModuleCreateData["resource"]} resource resource
  111. * @param {NormalModuleCreateData["resourceResolveData"]} resourceResolveData resource resolve data
  112. */
  113. const provideSharedModule = (
  114. key,
  115. config,
  116. resource,
  117. resourceResolveData
  118. ) => {
  119. let version = config.version;
  120. if (version === undefined) {
  121. let details = "";
  122. if (!resourceResolveData) {
  123. details = "No resolve data provided from resolver.";
  124. } else {
  125. const descriptionFileData =
  126. resourceResolveData.descriptionFileData;
  127. if (!descriptionFileData) {
  128. details =
  129. "No description file (usually package.json) found. Add description file with name and version, or manually specify version in shared config.";
  130. } else if (!descriptionFileData.version) {
  131. details = `No version in description file (usually package.json). Add version to description file ${resourceResolveData.descriptionFilePath}, or manually specify version in shared config.`;
  132. } else {
  133. version = descriptionFileData.version;
  134. }
  135. }
  136. if (!version) {
  137. const error = new WebpackError(
  138. `No version specified and unable to automatically determine one. ${details}`
  139. );
  140. error.file = `shared module ${key} -> ${resource}`;
  141. compilation.warnings.push(error);
  142. }
  143. }
  144. resolvedProvideMap.set(resource, {
  145. config,
  146. version
  147. });
  148. };
  149. normalModuleFactory.hooks.module.tap(
  150. PLUGIN_NAME,
  151. (module, { resource, resourceResolveData }, resolveData) => {
  152. if (resolvedProvideMap.has(/** @type {string} */ (resource))) {
  153. return module;
  154. }
  155. const { request } = resolveData;
  156. {
  157. const config = matchProvides.get(request);
  158. if (config !== undefined) {
  159. provideSharedModule(
  160. request,
  161. config,
  162. /** @type {string} */ (resource),
  163. resourceResolveData
  164. );
  165. resolveData.cacheable = false;
  166. }
  167. }
  168. for (const [prefix, config] of prefixMatchProvides) {
  169. if (request.startsWith(prefix)) {
  170. const remainder = request.slice(prefix.length);
  171. provideSharedModule(
  172. /** @type {string} */ (resource),
  173. {
  174. ...config,
  175. shareKey: config.shareKey + remainder
  176. },
  177. /** @type {string} */ (resource),
  178. resourceResolveData
  179. );
  180. resolveData.cacheable = false;
  181. }
  182. }
  183. return module;
  184. }
  185. );
  186. }
  187. );
  188. compiler.hooks.finishMake.tapPromise(PLUGIN_NAME, compilation => {
  189. const resolvedProvideMap = compilationData.get(compilation);
  190. if (!resolvedProvideMap) return Promise.resolve();
  191. return Promise.all(
  192. Array.from(
  193. resolvedProvideMap,
  194. ([resource, { config, version }]) =>
  195. new Promise((resolve, reject) => {
  196. compilation.addInclude(
  197. compiler.context,
  198. new ProvideSharedDependency(
  199. config.shareScope,
  200. config.shareKey,
  201. version || false,
  202. resource,
  203. config.eager
  204. ),
  205. {
  206. name: undefined
  207. },
  208. err => {
  209. if (err) return reject(err);
  210. resolve(null);
  211. }
  212. );
  213. })
  214. )
  215. ).then(() => {});
  216. });
  217. compiler.hooks.compilation.tap(
  218. PLUGIN_NAME,
  219. (compilation, { normalModuleFactory }) => {
  220. compilation.dependencyFactories.set(
  221. ProvideForSharedDependency,
  222. normalModuleFactory
  223. );
  224. compilation.dependencyFactories.set(
  225. ProvideSharedDependency,
  226. new ProvideSharedModuleFactory()
  227. );
  228. }
  229. );
  230. }
  231. }
  232. module.exports = ProvideSharedPlugin;