ProvideSharedPlugin.js 7.2 KB

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