UNPKG

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