UNPKG

14.8 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 __webpack_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 "__webpack_error__.message = 'Loading script failed.\\n(' + errorType + ': ' + realSrc + ')';",
139 "__webpack_error__.name = 'ScriptExternalLoadError';",
140 "__webpack_error__.type = errorType;",
141 "__webpack_error__.request = realSrc;",
142 "reject(__webpack_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: this.externalType !== "this",
297 topLevelDeclarations: new Set()
298 };
299 this.buildMeta.exportsType = "dynamic";
300 let canMangle = false;
301 this.clearDependenciesAndBlocks();
302 switch (this.externalType) {
303 case "system":
304 if (!Array.isArray(this.request) || this.request.length === 1) {
305 this.buildMeta.exportsType = "namespace";
306 canMangle = true;
307 }
308 break;
309 case "promise":
310 this.buildMeta.async = true;
311 break;
312 case "import":
313 this.buildMeta.async = true;
314 if (!Array.isArray(this.request) || this.request.length === 1) {
315 this.buildMeta.exportsType = "namespace";
316 canMangle = false;
317 }
318 break;
319 case "script":
320 this.buildMeta.async = true;
321 break;
322 }
323 this.addDependency(new StaticExportsDependency(true, canMangle));
324 callback();
325 }
326
327 /**
328 * @param {ConcatenationBailoutReasonContext} context context
329 * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
330 */
331 getConcatenationBailoutReason({ moduleGraph }) {
332 switch (this.externalType) {
333 case "amd":
334 case "amd-require":
335 case "umd":
336 case "umd2":
337 case "system":
338 case "jsonp":
339 return `${this.externalType} externals can't be concatenated`;
340 }
341 return undefined;
342 }
343
344 getSourceData(runtimeTemplate, moduleGraph, chunkGraph) {
345 const request =
346 typeof this.request === "object" && !Array.isArray(this.request)
347 ? this.request[this.externalType]
348 : this.request;
349 switch (this.externalType) {
350 case "this":
351 case "window":
352 case "self":
353 return getSourceForGlobalVariableExternal(request, this.externalType);
354 case "global":
355 return getSourceForGlobalVariableExternal(
356 request,
357 runtimeTemplate.outputOptions.globalObject
358 );
359 case "commonjs":
360 case "commonjs2":
361 case "commonjs-module":
362 return getSourceForCommonJsExternal(request);
363 case "amd":
364 case "amd-require":
365 case "umd":
366 case "umd2":
367 case "system":
368 case "jsonp":
369 return getSourceForAmdOrUmdExternal(
370 chunkGraph.getModuleId(this),
371 this.isOptional(moduleGraph),
372 request,
373 runtimeTemplate
374 );
375 case "import":
376 return getSourceForImportExternal(request, runtimeTemplate);
377 case "script":
378 return getSourceForScriptExternal(request, runtimeTemplate);
379 case "module":
380 if (!runtimeTemplate.supportsEcmaScriptModuleSyntax()) {
381 throw new Error(
382 "The target environment doesn't support EcmaScriptModule syntax so it's not possible to use external type 'module'"
383 );
384 }
385 throw new Error("Module external type is not implemented yet");
386 case "var":
387 case "promise":
388 case "const":
389 case "let":
390 case "assign":
391 default:
392 return getSourceForDefaultCase(
393 this.isOptional(moduleGraph),
394 request,
395 runtimeTemplate
396 );
397 }
398 }
399
400 /**
401 * @param {CodeGenerationContext} context context for code generation
402 * @returns {CodeGenerationResult} result
403 */
404 codeGeneration({
405 runtimeTemplate,
406 moduleGraph,
407 chunkGraph,
408 concatenationScope
409 }) {
410 const sourceData = this.getSourceData(
411 runtimeTemplate,
412 moduleGraph,
413 chunkGraph
414 );
415
416 let sourceString = sourceData.expression;
417 if (sourceData.iife)
418 sourceString = `(function() { return ${sourceString}; }())`;
419 if (concatenationScope) {
420 sourceString = `${runtimeTemplate.supportsConst() ? "const" : "var"} ${
421 ConcatenationScope.NAMESPACE_OBJECT_EXPORT
422 } = ${sourceString};`;
423 concatenationScope.registerNamespaceExport(
424 ConcatenationScope.NAMESPACE_OBJECT_EXPORT
425 );
426 } else {
427 sourceString = `module.exports = ${sourceString};`;
428 }
429 if (sourceData.init) sourceString = `${sourceData.init}\n${sourceString}`;
430
431 const sources = new Map();
432 if (this.useSourceMap || this.useSimpleSourceMap) {
433 sources.set(
434 "javascript",
435 new OriginalSource(sourceString, this.identifier())
436 );
437 } else {
438 sources.set("javascript", new RawSource(sourceString));
439 }
440
441 return {
442 sources,
443 runtimeRequirements: concatenationScope
444 ? RUNTIME_REQUIREMENTS_CONCATENATED
445 : this.externalType === "script"
446 ? RUNTIME_REQUIREMENTS_FOR_SCRIPT
447 : RUNTIME_REQUIREMENTS
448 };
449 }
450
451 /**
452 * @param {string=} type the source type for which the size should be estimated
453 * @returns {number} the estimated size of the module (must be non-zero)
454 */
455 size(type) {
456 return 42;
457 }
458
459 /**
460 * @param {Hash} hash the hash used to track dependencies
461 * @param {UpdateHashContext} context context
462 * @returns {void}
463 */
464 updateHash(hash, context) {
465 const { chunkGraph } = context;
466 hash.update(this.externalType);
467 hash.update(JSON.stringify(this.request));
468 hash.update(
469 JSON.stringify(Boolean(this.isOptional(chunkGraph.moduleGraph)))
470 );
471 super.updateHash(hash, context);
472 }
473
474 serialize(context) {
475 const { write } = context;
476
477 write(this.request);
478 write(this.externalType);
479 write(this.userRequest);
480
481 super.serialize(context);
482 }
483
484 deserialize(context) {
485 const { read } = context;
486
487 this.request = read();
488 this.externalType = read();
489 this.userRequest = read();
490
491 super.deserialize(context);
492 }
493}
494
495makeSerializable(ExternalModule, "webpack/lib/ExternalModule");
496
497module.exports = ExternalModule;