ExternalModuleFactoryPlugin.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  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 ContextElementDependency = require("./dependencies/ContextElementDependency");
  9. const CssImportDependency = require("./dependencies/CssImportDependency");
  10. const CssUrlDependency = require("./dependencies/CssUrlDependency");
  11. const HarmonyImportDependency = require("./dependencies/HarmonyImportDependency");
  12. const ImportDependency = require("./dependencies/ImportDependency");
  13. const { resolveByProperty, cachedSetProperty } = require("./util/cleverMerge");
  14. /** @typedef {import("../declarations/WebpackOptions").ExternalItemFunctionData} ExternalItemFunctionData */
  15. /** @typedef {import("../declarations/WebpackOptions").Externals} Externals */
  16. /** @typedef {import("./Compilation").DepConstructor} DepConstructor */
  17. /** @typedef {import("./ExternalModule").DependencyMeta} DependencyMeta */
  18. /** @typedef {import("./Module")} Module */
  19. /** @typedef {import("./NormalModuleFactory")} NormalModuleFactory */
  20. const UNSPECIFIED_EXTERNAL_TYPE_REGEXP = /^[a-z0-9-]+ /;
  21. const EMPTY_RESOLVE_OPTIONS = {};
  22. // TODO webpack 6 remove this
  23. const callDeprecatedExternals = util.deprecate(
  24. /**
  25. * @param {TODO} externalsFunction externals function
  26. * @param {string} context context
  27. * @param {string} request request
  28. * @param {(err: Error | null | undefined, value: ExternalValue | undefined, ty: ExternalType | undefined) => void} cb cb
  29. */
  30. (externalsFunction, context, request, cb) => {
  31. // eslint-disable-next-line no-useless-call
  32. externalsFunction.call(null, context, request, cb);
  33. },
  34. "The externals-function should be defined like ({context, request}, cb) => { ... }",
  35. "DEP_WEBPACK_EXTERNALS_FUNCTION_PARAMETERS"
  36. );
  37. const cache = new WeakMap();
  38. /**
  39. * @template {object} T
  40. * @param {T} obj obj
  41. * @param {TODO} layer layer
  42. * @returns {Omit<T, "byLayer">} result
  43. */
  44. const resolveLayer = (obj, layer) => {
  45. let map = cache.get(/** @type {object} */ (obj));
  46. if (map === undefined) {
  47. map = new Map();
  48. cache.set(/** @type {object} */ (obj), map);
  49. } else {
  50. const cacheEntry = map.get(layer);
  51. if (cacheEntry !== undefined) return cacheEntry;
  52. }
  53. const result = resolveByProperty(obj, "byLayer", layer);
  54. map.set(layer, result);
  55. return result;
  56. };
  57. /** @typedef {string | string[] | boolean | Record<string, string | string[]>} ExternalValue */
  58. /** @typedef {string | undefined} ExternalType */
  59. class ExternalModuleFactoryPlugin {
  60. /**
  61. * @param {string | undefined} type default external type
  62. * @param {Externals} externals externals config
  63. */
  64. constructor(type, externals) {
  65. this.type = type;
  66. this.externals = externals;
  67. }
  68. /**
  69. * @param {NormalModuleFactory} normalModuleFactory the normal module factory
  70. * @returns {void}
  71. */
  72. apply(normalModuleFactory) {
  73. const globalType = this.type;
  74. normalModuleFactory.hooks.factorize.tapAsync(
  75. "ExternalModuleFactoryPlugin",
  76. (data, callback) => {
  77. const context = data.context;
  78. const contextInfo = data.contextInfo;
  79. const dependency = data.dependencies[0];
  80. const dependencyType = data.dependencyType;
  81. /**
  82. * @param {ExternalValue} value the external config
  83. * @param {ExternalType | undefined} type type of external
  84. * @param {function((Error | null)=, ExternalModule=): void} callback callback
  85. * @returns {void}
  86. */
  87. const handleExternal = (value, type, callback) => {
  88. if (value === false) {
  89. // Not externals, fallback to original factory
  90. return callback();
  91. }
  92. /** @type {string | string[] | Record<string, string|string[]>} */
  93. let externalConfig = value === true ? dependency.request : value;
  94. // When no explicit type is specified, extract it from the externalConfig
  95. if (type === undefined) {
  96. if (
  97. typeof externalConfig === "string" &&
  98. UNSPECIFIED_EXTERNAL_TYPE_REGEXP.test(externalConfig)
  99. ) {
  100. const idx = externalConfig.indexOf(" ");
  101. type = externalConfig.slice(0, idx);
  102. externalConfig = externalConfig.slice(idx + 1);
  103. } else if (
  104. Array.isArray(externalConfig) &&
  105. externalConfig.length > 0 &&
  106. UNSPECIFIED_EXTERNAL_TYPE_REGEXP.test(externalConfig[0])
  107. ) {
  108. const firstItem = externalConfig[0];
  109. const idx = firstItem.indexOf(" ");
  110. type = firstItem.slice(0, idx);
  111. externalConfig = [
  112. firstItem.slice(idx + 1),
  113. ...externalConfig.slice(1)
  114. ];
  115. }
  116. }
  117. const resolvedType = /** @type {string} */ (type || globalType);
  118. // TODO make it pluggable/add hooks to `ExternalModule` to allow output modules own externals?
  119. /** @type {DependencyMeta | undefined} */
  120. let dependencyMeta;
  121. if (
  122. dependency instanceof HarmonyImportDependency ||
  123. dependency instanceof ImportDependency ||
  124. dependency instanceof ContextElementDependency
  125. ) {
  126. const externalType =
  127. dependency instanceof HarmonyImportDependency
  128. ? "module"
  129. : dependency instanceof ImportDependency
  130. ? "import"
  131. : undefined;
  132. dependencyMeta = {
  133. attributes: dependency.assertions,
  134. externalType
  135. };
  136. } else if (dependency instanceof CssImportDependency) {
  137. dependencyMeta = {
  138. layer: dependency.layer,
  139. supports: dependency.supports,
  140. media: dependency.media
  141. };
  142. }
  143. if (
  144. resolvedType === "asset" &&
  145. dependency instanceof CssUrlDependency
  146. ) {
  147. dependencyMeta = { sourceType: "css-url" };
  148. }
  149. callback(
  150. null,
  151. new ExternalModule(
  152. externalConfig,
  153. resolvedType,
  154. dependency.request,
  155. dependencyMeta
  156. )
  157. );
  158. };
  159. /**
  160. * @param {Externals} externals externals config
  161. * @param {function((Error | null)=, ExternalModule=): void} callback callback
  162. * @returns {void}
  163. */
  164. const handleExternals = (externals, callback) => {
  165. if (typeof externals === "string") {
  166. if (externals === dependency.request) {
  167. return handleExternal(dependency.request, undefined, callback);
  168. }
  169. } else if (Array.isArray(externals)) {
  170. let i = 0;
  171. const next = () => {
  172. /** @type {boolean | undefined} */
  173. let asyncFlag;
  174. /**
  175. * @param {(Error | null)=} err err
  176. * @param {ExternalModule=} module module
  177. * @returns {void}
  178. */
  179. const handleExternalsAndCallback = (err, module) => {
  180. if (err) return callback(err);
  181. if (!module) {
  182. if (asyncFlag) {
  183. asyncFlag = false;
  184. return;
  185. }
  186. return next();
  187. }
  188. callback(null, module);
  189. };
  190. do {
  191. asyncFlag = true;
  192. if (i >= externals.length) return callback();
  193. handleExternals(externals[i++], handleExternalsAndCallback);
  194. } while (!asyncFlag);
  195. asyncFlag = false;
  196. };
  197. next();
  198. return;
  199. } else if (externals instanceof RegExp) {
  200. if (externals.test(dependency.request)) {
  201. return handleExternal(dependency.request, undefined, callback);
  202. }
  203. } else if (typeof externals === "function") {
  204. /**
  205. * @param {Error | null | undefined} err err
  206. * @param {ExternalValue=} value value
  207. * @param {ExternalType=} type type
  208. * @returns {void}
  209. */
  210. const cb = (err, value, type) => {
  211. if (err) return callback(err);
  212. if (value !== undefined) {
  213. handleExternal(value, type, callback);
  214. } else {
  215. callback();
  216. }
  217. };
  218. if (externals.length === 3) {
  219. // TODO webpack 6 remove this
  220. callDeprecatedExternals(
  221. externals,
  222. context,
  223. dependency.request,
  224. cb
  225. );
  226. } else {
  227. const promise = externals(
  228. {
  229. context,
  230. request: dependency.request,
  231. dependencyType,
  232. contextInfo,
  233. getResolve: options => (context, request, callback) => {
  234. const resolveContext = {
  235. fileDependencies: data.fileDependencies,
  236. missingDependencies: data.missingDependencies,
  237. contextDependencies: data.contextDependencies
  238. };
  239. let resolver = normalModuleFactory.getResolver(
  240. "normal",
  241. dependencyType
  242. ? cachedSetProperty(
  243. data.resolveOptions || EMPTY_RESOLVE_OPTIONS,
  244. "dependencyType",
  245. dependencyType
  246. )
  247. : data.resolveOptions
  248. );
  249. if (options) resolver = resolver.withOptions(options);
  250. if (callback) {
  251. resolver.resolve(
  252. {},
  253. context,
  254. request,
  255. resolveContext,
  256. /** @type {TODO} */
  257. (callback)
  258. );
  259. } else {
  260. return new Promise((resolve, reject) => {
  261. resolver.resolve(
  262. {},
  263. context,
  264. request,
  265. resolveContext,
  266. (err, result) => {
  267. if (err) reject(err);
  268. else resolve(result);
  269. }
  270. );
  271. });
  272. }
  273. }
  274. },
  275. cb
  276. );
  277. if (promise && promise.then) promise.then(r => cb(null, r), cb);
  278. }
  279. return;
  280. } else if (typeof externals === "object") {
  281. const resolvedExternals = resolveLayer(
  282. externals,
  283. contextInfo.issuerLayer
  284. );
  285. if (
  286. Object.prototype.hasOwnProperty.call(
  287. resolvedExternals,
  288. dependency.request
  289. )
  290. ) {
  291. return handleExternal(
  292. resolvedExternals[dependency.request],
  293. undefined,
  294. callback
  295. );
  296. }
  297. }
  298. callback();
  299. };
  300. handleExternals(this.externals, callback);
  301. }
  302. );
  303. }
  304. }
  305. module.exports = ExternalModuleFactoryPlugin;