ProvideSharedPlugin.js 6.7 KB

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