UNPKG

7.23 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 dependencyType = dependency.category || "";
176 const promise = externals(
177 {
178 context,
179 request: dependency.request,
180 dependencyType,
181 contextInfo,
182 getResolve: options => (context, request, callback) => {
183 const resolveContext = {
184 fileDependencies: data.fileDependencies,
185 missingDependencies: data.missingDependencies,
186 contextDependencies: data.contextDependencies
187 };
188 let resolver = normalModuleFactory.getResolver(
189 "normal",
190 dependencyType
191 ? cachedSetProperty(
192 data.resolveOptions || EMPTY_RESOLVE_OPTIONS,
193 "dependencyType",
194 dependencyType
195 )
196 : data.resolveOptions
197 );
198 if (options) resolver = resolver.withOptions(options);
199 if (callback) {
200 resolver.resolve(
201 {},
202 context,
203 request,
204 resolveContext,
205 callback
206 );
207 } else {
208 return new Promise((resolve, reject) => {
209 resolver.resolve(
210 {},
211 context,
212 request,
213 resolveContext,
214 (err, result) => {
215 if (err) reject(err);
216 else resolve(result);
217 }
218 );
219 });
220 }
221 }
222 },
223 cb
224 );
225 if (promise && promise.then) promise.then(r => cb(null, r), cb);
226 }
227 return;
228 } else if (typeof externals === "object") {
229 const resolvedExternals = resolveLayer(
230 externals,
231 contextInfo.issuerLayer
232 );
233 if (
234 Object.prototype.hasOwnProperty.call(
235 resolvedExternals,
236 dependency.request
237 )
238 ) {
239 return handleExternal(
240 resolvedExternals[dependency.request],
241 undefined,
242 callback
243 );
244 }
245 }
246 callback();
247 };
248
249 handleExternals(this.externals, callback);
250 }
251 );
252 }
253}
254module.exports = ExternalModuleFactoryPlugin;