ExternalModuleFactoryPlugin.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const util = require("util");
  7. const ExternalModule = require("./ExternalModule");
  8. const { resolveByProperty, cachedSetProperty } = require("./util/cleverMerge");
  9. /** @typedef {import("../declarations/WebpackOptions").Externals} Externals */
  10. /** @typedef {import("./NormalModuleFactory")} NormalModuleFactory */
  11. const UNSPECIFIED_EXTERNAL_TYPE_REGEXP = /^[a-z0-9-]+ /;
  12. const EMPTY_RESOLVE_OPTIONS = {};
  13. // TODO webpack 6 remove this
  14. const callDeprecatedExternals = util.deprecate(
  15. (externalsFunction, context, request, cb) => {
  16. externalsFunction.call(null, context, request, cb);
  17. },
  18. "The externals-function should be defined like ({context, request}, cb) => { ... }",
  19. "DEP_WEBPACK_EXTERNALS_FUNCTION_PARAMETERS"
  20. );
  21. const cache = new WeakMap();
  22. const resolveLayer = (obj, layer) => {
  23. let map = cache.get(obj);
  24. if (map === undefined) {
  25. map = new Map();
  26. cache.set(obj, map);
  27. } else {
  28. const cacheEntry = map.get(layer);
  29. if (cacheEntry !== undefined) return cacheEntry;
  30. }
  31. const result = resolveByProperty(obj, "byLayer", layer);
  32. map.set(layer, result);
  33. return result;
  34. };
  35. class ExternalModuleFactoryPlugin {
  36. /**
  37. * @param {string | undefined} type default external type
  38. * @param {Externals} externals externals config
  39. */
  40. constructor(type, externals) {
  41. this.type = type;
  42. this.externals = externals;
  43. }
  44. /**
  45. * @param {NormalModuleFactory} normalModuleFactory the normal module factory
  46. * @returns {void}
  47. */
  48. apply(normalModuleFactory) {
  49. const globalType = this.type;
  50. normalModuleFactory.hooks.factorize.tapAsync(
  51. "ExternalModuleFactoryPlugin",
  52. (data, callback) => {
  53. const context = data.context;
  54. const contextInfo = data.contextInfo;
  55. const dependency = data.dependencies[0];
  56. const dependencyType = data.dependencyType;
  57. /**
  58. * @param {string|string[]|boolean|Record<string, string|string[]>} value the external config
  59. * @param {string|undefined} type type of external
  60. * @param {function(Error=, ExternalModule=): void} callback callback
  61. * @returns {void}
  62. */
  63. const handleExternal = (value, type, callback) => {
  64. if (value === false) {
  65. // Not externals, fallback to original factory
  66. return callback();
  67. }
  68. /** @type {string | string[] | Record<string, string|string[]>} */
  69. let externalConfig;
  70. if (value === true) {
  71. externalConfig = dependency.request;
  72. } else {
  73. externalConfig = value;
  74. }
  75. // When no explicit type is specified, extract it from the externalConfig
  76. if (type === undefined) {
  77. if (
  78. typeof externalConfig === "string" &&
  79. UNSPECIFIED_EXTERNAL_TYPE_REGEXP.test(externalConfig)
  80. ) {
  81. const idx = externalConfig.indexOf(" ");
  82. type = externalConfig.slice(0, idx);
  83. externalConfig = externalConfig.slice(idx + 1);
  84. } else if (
  85. Array.isArray(externalConfig) &&
  86. externalConfig.length > 0 &&
  87. UNSPECIFIED_EXTERNAL_TYPE_REGEXP.test(externalConfig[0])
  88. ) {
  89. const firstItem = externalConfig[0];
  90. const idx = firstItem.indexOf(" ");
  91. type = firstItem.slice(0, idx);
  92. externalConfig = [
  93. firstItem.slice(idx + 1),
  94. ...externalConfig.slice(1)
  95. ];
  96. }
  97. }
  98. callback(
  99. null,
  100. new ExternalModule(
  101. externalConfig,
  102. type || globalType,
  103. dependency.request
  104. )
  105. );
  106. };
  107. /**
  108. * @param {Externals} externals externals config
  109. * @param {function((Error | null)=, ExternalModule=): void} callback callback
  110. * @returns {void}
  111. */
  112. const handleExternals = (externals, callback) => {
  113. if (typeof externals === "string") {
  114. if (externals === dependency.request) {
  115. return handleExternal(dependency.request, undefined, callback);
  116. }
  117. } else if (Array.isArray(externals)) {
  118. let i = 0;
  119. const next = () => {
  120. let asyncFlag;
  121. const handleExternalsAndCallback = (err, module) => {
  122. if (err) return callback(err);
  123. if (!module) {
  124. if (asyncFlag) {
  125. asyncFlag = false;
  126. return;
  127. }
  128. return next();
  129. }
  130. callback(null, module);
  131. };
  132. do {
  133. asyncFlag = true;
  134. if (i >= externals.length) return callback();
  135. handleExternals(externals[i++], handleExternalsAndCallback);
  136. } while (!asyncFlag);
  137. asyncFlag = false;
  138. };
  139. next();
  140. return;
  141. } else if (externals instanceof RegExp) {
  142. if (externals.test(dependency.request)) {
  143. return handleExternal(dependency.request, undefined, callback);
  144. }
  145. } else if (typeof externals === "function") {
  146. const cb = (err, value, type) => {
  147. if (err) return callback(err);
  148. if (value !== undefined) {
  149. handleExternal(value, type, callback);
  150. } else {
  151. callback();
  152. }
  153. };
  154. if (externals.length === 3) {
  155. // TODO webpack 6 remove this
  156. callDeprecatedExternals(
  157. externals,
  158. context,
  159. dependency.request,
  160. cb
  161. );
  162. } else {
  163. const promise = externals(
  164. {
  165. context,
  166. request: dependency.request,
  167. dependencyType,
  168. contextInfo,
  169. getResolve: options => (context, request, callback) => {
  170. const resolveContext = {
  171. fileDependencies: data.fileDependencies,
  172. missingDependencies: data.missingDependencies,
  173. contextDependencies: data.contextDependencies
  174. };
  175. let resolver = normalModuleFactory.getResolver(
  176. "normal",
  177. dependencyType
  178. ? cachedSetProperty(
  179. data.resolveOptions || EMPTY_RESOLVE_OPTIONS,
  180. "dependencyType",
  181. dependencyType
  182. )
  183. : data.resolveOptions
  184. );
  185. if (options) resolver = resolver.withOptions(options);
  186. if (callback) {
  187. resolver.resolve(
  188. {},
  189. context,
  190. request,
  191. resolveContext,
  192. callback
  193. );
  194. } else {
  195. return new Promise((resolve, reject) => {
  196. resolver.resolve(
  197. {},
  198. context,
  199. request,
  200. resolveContext,
  201. (err, result) => {
  202. if (err) reject(err);
  203. else resolve(result);
  204. }
  205. );
  206. });
  207. }
  208. }
  209. },
  210. cb
  211. );
  212. if (promise && promise.then) promise.then(r => cb(null, r), cb);
  213. }
  214. return;
  215. } else if (typeof externals === "object") {
  216. const resolvedExternals = resolveLayer(
  217. externals,
  218. contextInfo.issuerLayer
  219. );
  220. if (
  221. Object.prototype.hasOwnProperty.call(
  222. resolvedExternals,
  223. dependency.request
  224. )
  225. ) {
  226. return handleExternal(
  227. resolvedExternals[dependency.request],
  228. undefined,
  229. callback
  230. );
  231. }
  232. }
  233. callback();
  234. };
  235. handleExternals(this.externals, callback);
  236. }
  237. );
  238. }
  239. }
  240. module.exports = ExternalModuleFactoryPlugin;