ExternalModuleFactoryPlugin.js 10 KB

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