ProvideSharedPlugin.js 7.2 KB

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