ProvideSharedPlugin.js 6.7 KB

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