UNPKG

14.7 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 { OriginalSource, RawSource } = require("webpack-sources");
9const ConcatenationScope = require("./ConcatenationScope");
10const Module = require("./Module");
11const RuntimeGlobals = require("./RuntimeGlobals");
12const Template = require("./Template");
13const StaticExportsDependency = require("./dependencies/StaticExportsDependency");
14const extractUrlAndGlobal = require("./util/extractUrlAndGlobal");
15const makeSerializable = require("./util/makeSerializable");
16const propertyAccess = require("./util/propertyAccess");
17
18/** @typedef {import("webpack-sources").Source} Source */
19/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */
20/** @typedef {import("./Chunk")} Chunk */
21/** @typedef {import("./ChunkGraph")} ChunkGraph */
22/** @typedef {import("./Compilation")} Compilation */
23/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */
24/** @typedef {import("./DependencyTemplates")} DependencyTemplates */
25/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */
26/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */
27/** @typedef {import("./Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
28/** @typedef {import("./Module").LibIdentOptions} LibIdentOptions */
29/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */
30/** @typedef {import("./RequestShortener")} RequestShortener */
31/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */
32/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
33/** @typedef {import("./WebpackError")} WebpackError */
34/** @typedef {import("./util/Hash")} Hash */
35/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
36
37/**
38 * @typedef {Object} SourceData
39 * @property {boolean=} iife
40 * @property {string=} init
41 * @property {string} expression
42 */
43
44/**
45 * @param {string|string[]} variableName the variable name or path
46 * @param {string} type the module system
47 * @returns {SourceData} the generated source
48 */
49const getSourceForGlobalVariableExternal = (variableName, type) => {
50 if (!Array.isArray(variableName)) {
51 // make it an array as the look up works the same basically
52 variableName = [variableName];
53 }
54
55 // needed for e.g. window["some"]["thing"]
56 const objectLookup = variableName.map(r => `[${JSON.stringify(r)}]`).join("");
57 return {
58 iife: type === "this",
59 expression: `${type}${objectLookup}`
60 };
61};
62
63/**
64 * @param {string|string[]} moduleAndSpecifiers the module request
65 * @returns {SourceData} the generated source
66 */
67const getSourceForCommonJsExternal = moduleAndSpecifiers => {
68 if (!Array.isArray(moduleAndSpecifiers)) {
69 return {
70 expression: `require(${JSON.stringify(moduleAndSpecifiers)});`
71 };
72 }
73 const moduleName = moduleAndSpecifiers[0];
74 return {
75 expression: `require(${JSON.stringify(moduleName)})${propertyAccess(
76 moduleAndSpecifiers,
77 1
78 )};`
79 };
80};
81
82/**
83 * @param {string|string[]} moduleAndSpecifiers the module request
84 * @param {RuntimeTemplate} runtimeTemplate the runtime template
85 * @returns {SourceData} the generated source
86 */
87const getSourceForImportExternal = (moduleAndSpecifiers, runtimeTemplate) => {
88 const importName = runtimeTemplate.outputOptions.importFunctionName;
89 if (!runtimeTemplate.supportsDynamicImport() && importName === "import") {
90 throw new Error(
91 "The target environment doesn't support 'import()' so it's not possible to use external type 'import'"
92 );
93 }
94 if (!Array.isArray(moduleAndSpecifiers)) {
95 return {
96 expression: `${importName}(${JSON.stringify(moduleAndSpecifiers)});`
97 };
98 }
99 if (moduleAndSpecifiers.length === 1) {
100 return {
101 expression: `${importName}(${JSON.stringify(moduleAndSpecifiers[0])});`
102 };
103 }
104 const moduleName = moduleAndSpecifiers[0];
105 return {
106 expression: `${importName}(${JSON.stringify(
107 moduleName
108 )}).then(${runtimeTemplate.returningFunction(
109 `module${propertyAccess(moduleAndSpecifiers, 1)}`,
110 "module"
111 )});`
112 };
113};
114
115/**
116 * @param {string|string[]} urlAndGlobal the script request
117 * @param {RuntimeTemplate} runtimeTemplate the runtime template
118 * @returns {SourceData} the generated source
119 */
120const getSourceForScriptExternal = (urlAndGlobal, runtimeTemplate) => {
121 if (typeof urlAndGlobal === "string") {
122 urlAndGlobal = extractUrlAndGlobal(urlAndGlobal);
123 }
124 const url = urlAndGlobal[0];
125 const globalName = urlAndGlobal[1];
126 return {
127 init: "var error = new Error();",
128 expression: `new Promise(${runtimeTemplate.basicFunction(
129 "resolve, reject",
130 [
131 `if(typeof ${globalName} !== "undefined") return resolve();`,
132 `${RuntimeGlobals.loadScript}(${JSON.stringify(
133 url
134 )}, ${runtimeTemplate.basicFunction("event", [
135 `if(typeof ${globalName} !== "undefined") return resolve();`,
136 "var errorType = event && (event.type === 'load' ? 'missing' : event.type);",
137 "var realSrc = event && event.target && event.target.src;",
138 "error.message = 'Loading script failed.\\n(' + errorType + ': ' + realSrc + ')';",
139 "error.name = 'ScriptExternalLoadError';",
140 "error.type = errorType;",
141 "error.request = realSrc;",
142 "reject(error);"
143 ])}, ${JSON.stringify(globalName)});`
144 ]
145 )}).then(${runtimeTemplate.returningFunction(
146 `${globalName}${propertyAccess(urlAndGlobal, 2)}`
147 )})`
148 };
149};
150
151/**
152 * @param {string} variableName the variable name to check
153 * @param {string} request the request path
154 * @param {RuntimeTemplate} runtimeTemplate the runtime template
155 * @returns {string} the generated source
156 */
157const checkExternalVariable = (variableName, request, runtimeTemplate) => {
158 return `if(typeof ${variableName} === 'undefined') { ${runtimeTemplate.throwMissingModuleErrorBlock(
159 { request }
160 )} }\n`;
161};
162
163/**
164 * @param {string|number} id the module id
165 * @param {boolean} optional true, if the module is optional
166 * @param {string|string[]} request the request path
167 * @param {RuntimeTemplate} runtimeTemplate the runtime template
168 * @returns {SourceData} the generated source
169 */
170const getSourceForAmdOrUmdExternal = (
171 id,
172 optional,
173 request,
174 runtimeTemplate
175) => {
176 const externalVariable = `__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier(
177 `${id}`
178 )}__`;
179 return {
180 init: optional
181 ? checkExternalVariable(
182 externalVariable,
183 Array.isArray(request) ? request.join(".") : request,
184 runtimeTemplate
185 )
186 : undefined,
187 expression: externalVariable
188 };
189};
190
191/**
192 * @param {boolean} optional true, if the module is optional
193 * @param {string|string[]} request the request path
194 * @param {RuntimeTemplate} runtimeTemplate the runtime template
195 * @returns {SourceData} the generated source
196 */
197const getSourceForDefaultCase = (optional, request, runtimeTemplate) => {
198 if (!Array.isArray(request)) {
199 // make it an array as the look up works the same basically
200 request = [request];
201 }
202
203 const variableName = request[0];
204 const objectLookup = propertyAccess(request, 1);
205 return {
206 init: optional
207 ? checkExternalVariable(variableName, request.join("."), runtimeTemplate)
208 : undefined,
209 expression: `${variableName}${objectLookup}`
210 };
211};
212
213const TYPES = new Set(["javascript"]);
214const RUNTIME_REQUIREMENTS = new Set([RuntimeGlobals.module]);
215const RUNTIME_REQUIREMENTS_FOR_SCRIPT = new Set([
216 RuntimeGlobals.module,
217 RuntimeGlobals.loadScript
218]);
219const RUNTIME_REQUIREMENTS_CONCATENATED = new Set([]);
220
221class ExternalModule extends Module {
222 constructor(request, type, userRequest) {
223 super("javascript/dynamic", null);
224
225 // Info from Factory
226 /** @type {string | string[] | Record<string, string | string[]>} */
227 this.request = request;
228 /** @type {string} */
229 this.externalType = type;
230 /** @type {string} */
231 this.userRequest = userRequest;
232 }
233
234 /**
235 * @returns {Set<string>} types available (do not mutate)
236 */
237 getSourceTypes() {
238 return TYPES;
239 }
240
241 /**
242 * @param {LibIdentOptions} options options
243 * @returns {string | null} an identifier for library inclusion
244 */
245 libIdent(options) {
246 return this.userRequest;
247 }
248
249 /**
250 * @param {Chunk} chunk the chunk which condition should be checked
251 * @param {Compilation} compilation the compilation
252 * @returns {boolean} true, if the chunk is ok for the module
253 */
254 chunkCondition(chunk, { chunkGraph }) {
255 return chunkGraph.getNumberOfEntryModules(chunk) > 0;
256 }
257
258 /**
259 * @returns {string} a unique identifier of the module
260 */
261 identifier() {
262 return "external " + JSON.stringify(this.request);
263 }
264
265 /**
266 * @param {RequestShortener} requestShortener the request shortener
267 * @returns {string} a user readable identifier of the module
268 */
269 readableIdentifier(requestShortener) {
270 return "external " + JSON.stringify(this.request);
271 }
272
273 /**
274 * @param {NeedBuildContext} context context info
275 * @param {function(WebpackError=, boolean=): void} callback callback function, returns true, if the module needs a rebuild
276 * @returns {void}
277 */
278 needBuild(context, callback) {
279 return callback(null, !this.buildMeta);
280 }
281
282 /**
283 * @param {WebpackOptions} options webpack options
284 * @param {Compilation} compilation the compilation
285 * @param {ResolverWithOptions} resolver the resolver
286 * @param {InputFileSystem} fs the file system
287 * @param {function(WebpackError=): void} callback callback function
288 * @returns {void}
289 */
290 build(options, compilation, resolver, fs, callback) {
291 this.buildMeta = {
292 async: false,
293 exportsType: undefined
294 };
295 this.buildInfo = {
296 strict: true
297 };
298 this.buildMeta.exportsType = "dynamic";
299 let canMangle = false;
300 this.clearDependenciesAndBlocks();
301 switch (this.externalType) {
302 case "system":
303 if (!Array.isArray(this.request) || this.request.length === 1) {
304 this.buildMeta.exportsType = "namespace";
305 canMangle = true;
306 }
307 break;
308 case "promise":
309 this.buildMeta.async = true;
310 break;
311 case "import":
312 this.buildMeta.async = true;
313 if (!Array.isArray(this.request) || this.request.length === 1) {
314 this.buildMeta.exportsType = "namespace";
315 canMangle = false;
316 }
317 break;
318 case "script":
319 this.buildMeta.async = true;
320 break;
321 }
322 this.addDependency(new StaticExportsDependency(true, canMangle));
323 callback();
324 }
325
326 /**
327 * @param {ConcatenationBailoutReasonContext} context context
328 * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
329 */
330 getConcatenationBailoutReason({ moduleGraph }) {
331 switch (this.externalType) {
332 case "amd":
333 case "amd-require":
334 case "umd":
335 case "umd2":
336 case "system":
337 case "jsonp":
338 return `${this.externalType} externals can't be concatenated`;
339 }
340 return undefined;
341 }
342
343 getSourceData(runtimeTemplate, moduleGraph, chunkGraph) {
344 const request =
345 typeof this.request === "object" && !Array.isArray(this.request)
346 ? this.request[this.externalType]
347 : this.request;
348 switch (this.externalType) {
349 case "this":
350 case "window":
351 case "self":
352 return getSourceForGlobalVariableExternal(request, this.externalType);
353 case "global":
354 return getSourceForGlobalVariableExternal(
355 request,
356 runtimeTemplate.outputOptions.globalObject
357 );
358 case "commonjs":
359 case "commonjs2":
360 case "commonjs-module":
361 return getSourceForCommonJsExternal(request);
362 case "amd":
363 case "amd-require":
364 case "umd":
365 case "umd2":
366 case "system":
367 case "jsonp":
368 return getSourceForAmdOrUmdExternal(
369 chunkGraph.getModuleId(this),
370 this.isOptional(moduleGraph),
371 request,
372 runtimeTemplate
373 );
374 case "import":
375 return getSourceForImportExternal(request, runtimeTemplate);
376 case "script":
377 return getSourceForScriptExternal(request, runtimeTemplate);
378 case "module":
379 if (!runtimeTemplate.supportsEcmaScriptModuleSyntax()) {
380 throw new Error(
381 "The target environment doesn't support EcmaScriptModule syntax so it's not possible to use external type 'module'"
382 );
383 }
384 throw new Error("Module external type is not implemented yet");
385 case "var":
386 case "promise":
387 case "const":
388 case "let":
389 case "assign":
390 default:
391 return getSourceForDefaultCase(
392 this.isOptional(moduleGraph),
393 request,
394 runtimeTemplate
395 );
396 }
397 }
398
399 /**
400 * @param {CodeGenerationContext} context context for code generation
401 * @returns {CodeGenerationResult} result
402 */
403 codeGeneration({
404 runtimeTemplate,
405 moduleGraph,
406 chunkGraph,
407 concatenationScope
408 }) {
409 const sourceData = this.getSourceData(
410 runtimeTemplate,
411 moduleGraph,
412 chunkGraph
413 );
414
415 let sourceString;
416 if (concatenationScope) {
417 sourceString = `${runtimeTemplate.supportsConst() ? "const" : "var"} ${
418 ConcatenationScope.NAMESPACE_OBJECT_EXPORT
419 } = ${sourceData.expression};`;
420 concatenationScope.registerNamespaceExport(
421 ConcatenationScope.NAMESPACE_OBJECT_EXPORT
422 );
423 } else {
424 sourceString = `module.exports = ${sourceData.expression};`;
425 }
426 if (sourceData.iife) sourceString = `(function() { ${sourceString} })();`;
427 if (sourceData.init) sourceString = `${sourceData.init}\n${sourceString}`;
428
429 const sources = new Map();
430 if (this.useSourceMap) {
431 sources.set(
432 "javascript",
433 new OriginalSource(sourceString, this.identifier())
434 );
435 } else {
436 sources.set("javascript", new RawSource(sourceString));
437 }
438
439 return {
440 sources,
441 runtimeRequirements: concatenationScope
442 ? RUNTIME_REQUIREMENTS_CONCATENATED
443 : this.externalType === "script"
444 ? RUNTIME_REQUIREMENTS_FOR_SCRIPT
445 : RUNTIME_REQUIREMENTS
446 };
447 }
448
449 /**
450 * @param {string=} type the source type for which the size should be estimated
451 * @returns {number} the estimated size of the module (must be non-zero)
452 */
453 size(type) {
454 return 42;
455 }
456
457 /**
458 * @param {Hash} hash the hash used to track dependencies
459 * @param {UpdateHashContext} context context
460 * @returns {void}
461 */
462 updateHash(hash, context) {
463 const { chunkGraph } = context;
464 hash.update(this.externalType);
465 hash.update(JSON.stringify(this.request));
466 hash.update(
467 JSON.stringify(Boolean(this.isOptional(chunkGraph.moduleGraph)))
468 );
469 super.updateHash(hash, context);
470 }
471
472 serialize(context) {
473 const { write } = context;
474
475 write(this.request);
476 write(this.externalType);
477 write(this.userRequest);
478
479 super.serialize(context);
480 }
481
482 deserialize(context) {
483 const { read } = context;
484
485 this.request = read();
486 this.externalType = read();
487 this.userRequest = read();
488
489 super.deserialize(context);
490 }
491}
492
493makeSerializable(ExternalModule, "webpack/lib/ExternalModule");
494
495module.exports = ExternalModule;