ExternalModuleFactoryPlugin.js 8.4 KB

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