UNPKG

20.2 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 { UsageState } = require("./ExportsInfo");
11const InitFragment = require("./InitFragment");
12const Module = require("./Module");
13const RuntimeGlobals = require("./RuntimeGlobals");
14const Template = require("./Template");
15const StaticExportsDependency = require("./dependencies/StaticExportsDependency");
16const extractUrlAndGlobal = require("./util/extractUrlAndGlobal");
17const makeSerializable = require("./util/makeSerializable");
18const propertyAccess = require("./util/propertyAccess");
19
20/** @typedef {import("webpack-sources").Source} Source */
21/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */
22/** @typedef {import("./Chunk")} Chunk */
23/** @typedef {import("./ChunkGraph")} ChunkGraph */
24/** @typedef {import("./Compilation")} Compilation */
25/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */
26/** @typedef {import("./DependencyTemplates")} DependencyTemplates */
27/** @typedef {import("./ExportsInfo")} ExportsInfo */
28/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */
29/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */
30/** @typedef {import("./Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
31/** @typedef {import("./Module").LibIdentOptions} LibIdentOptions */
32/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */
33/** @typedef {import("./RequestShortener")} RequestShortener */
34/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */
35/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
36/** @typedef {import("./WebpackError")} WebpackError */
37/** @typedef {import("./javascript/JavascriptModulesPlugin").ChunkRenderContext} ChunkRenderContext */
38/** @typedef {import("./util/Hash")} Hash */
39/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
40/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
41
42/**
43 * @typedef {Object} SourceData
44 * @property {boolean=} iife
45 * @property {string=} init
46 * @property {string} expression
47 * @property {InitFragment<ChunkRenderContext>[]=} chunkInitFragments
48 * @property {ReadonlySet<string>=} runtimeRequirements
49 */
50
51const TYPES = new Set(["javascript"]);
52const RUNTIME_REQUIREMENTS = new Set([RuntimeGlobals.module]);
53const RUNTIME_REQUIREMENTS_FOR_SCRIPT = new Set([RuntimeGlobals.loadScript]);
54const RUNTIME_REQUIREMENTS_FOR_MODULE = new Set([
55 RuntimeGlobals.definePropertyGetters
56]);
57const EMPTY_RUNTIME_REQUIREMENTS = new Set([]);
58
59/**
60 * @param {string|string[]} variableName the variable name or path
61 * @param {string} type the module system
62 * @returns {SourceData} the generated source
63 */
64const getSourceForGlobalVariableExternal = (variableName, type) => {
65 if (!Array.isArray(variableName)) {
66 // make it an array as the look up works the same basically
67 variableName = [variableName];
68 }
69
70 // needed for e.g. window["some"]["thing"]
71 const objectLookup = variableName.map(r => `[${JSON.stringify(r)}]`).join("");
72 return {
73 iife: type === "this",
74 expression: `${type}${objectLookup}`
75 };
76};
77
78/**
79 * @param {string|string[]} moduleAndSpecifiers the module request
80 * @returns {SourceData} the generated source
81 */
82const getSourceForCommonJsExternal = moduleAndSpecifiers => {
83 if (!Array.isArray(moduleAndSpecifiers)) {
84 return {
85 expression: `require(${JSON.stringify(moduleAndSpecifiers)})`
86 };
87 }
88 const moduleName = moduleAndSpecifiers[0];
89 return {
90 expression: `require(${JSON.stringify(moduleName)})${propertyAccess(
91 moduleAndSpecifiers,
92 1
93 )}`
94 };
95};
96
97/**
98 * @param {string|string[]} moduleAndSpecifiers the module request
99 * @returns {SourceData} the generated source
100 */
101const getSourceForCommonJsExternalInNodeModule = moduleAndSpecifiers => {
102 const chunkInitFragments = [
103 new InitFragment(
104 'import { createRequire as __WEBPACK_EXTERNAL_createRequire } from "module";\n',
105 InitFragment.STAGE_HARMONY_IMPORTS,
106 0,
107 "external module node-commonjs"
108 )
109 ];
110 if (!Array.isArray(moduleAndSpecifiers)) {
111 return {
112 expression: `__WEBPACK_EXTERNAL_createRequire(import.meta.url)(${JSON.stringify(
113 moduleAndSpecifiers
114 )})`,
115 chunkInitFragments
116 };
117 }
118 const moduleName = moduleAndSpecifiers[0];
119 return {
120 expression: `__WEBPACK_EXTERNAL_createRequire(import.meta.url)(${JSON.stringify(
121 moduleName
122 )})${propertyAccess(moduleAndSpecifiers, 1)}`,
123 chunkInitFragments
124 };
125};
126
127/**
128 * @param {string|string[]} moduleAndSpecifiers the module request
129 * @param {RuntimeTemplate} runtimeTemplate the runtime template
130 * @returns {SourceData} the generated source
131 */
132const getSourceForImportExternal = (moduleAndSpecifiers, runtimeTemplate) => {
133 const importName = runtimeTemplate.outputOptions.importFunctionName;
134 if (!runtimeTemplate.supportsDynamicImport() && importName === "import") {
135 throw new Error(
136 "The target environment doesn't support 'import()' so it's not possible to use external type 'import'"
137 );
138 }
139 if (!Array.isArray(moduleAndSpecifiers)) {
140 return {
141 expression: `${importName}(${JSON.stringify(moduleAndSpecifiers)});`
142 };
143 }
144 if (moduleAndSpecifiers.length === 1) {
145 return {
146 expression: `${importName}(${JSON.stringify(moduleAndSpecifiers[0])});`
147 };
148 }
149 const moduleName = moduleAndSpecifiers[0];
150 return {
151 expression: `${importName}(${JSON.stringify(
152 moduleName
153 )}).then(${runtimeTemplate.returningFunction(
154 `module${propertyAccess(moduleAndSpecifiers, 1)}`,
155 "module"
156 )});`
157 };
158};
159
160class ModuleExternalInitFragment extends InitFragment {
161 constructor(id, request) {
162 const identifier = `__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier(
163 `${id}`
164 )}__`;
165 super(
166 `import * as ${identifier} from ${JSON.stringify(request)};\n`,
167 InitFragment.STAGE_HARMONY_IMPORTS,
168 0,
169 `external module import ${id}`
170 );
171 this._identifier = identifier;
172 }
173
174 getNamespaceIdentifier() {
175 return this._identifier;
176 }
177}
178
179const generateModuleRemapping = (input, exportsInfo, runtime) => {
180 if (exportsInfo.otherExportsInfo.getUsed(runtime) === UsageState.Unused) {
181 const properties = [];
182 for (const exportInfo of exportsInfo.orderedExports) {
183 const used = exportInfo.getUsedName(exportInfo.name, runtime);
184 if (!used) continue;
185 const nestedInfo = exportInfo.getNestedExportsInfo();
186 if (nestedInfo) {
187 const nestedExpr = generateModuleRemapping(
188 `${input}${propertyAccess([exportInfo.name])}`,
189 nestedInfo
190 );
191 if (nestedExpr) {
192 properties.push(`[${JSON.stringify(used)}]: y(${nestedExpr})`);
193 continue;
194 }
195 }
196 properties.push(
197 `[${JSON.stringify(used)}]: () => ${input}${propertyAccess([
198 exportInfo.name
199 ])}`
200 );
201 }
202 return `x({ ${properties.join(", ")} })`;
203 }
204};
205
206/**
207 * @param {string|number} id the module id
208 * @param {string|string[]} moduleAndSpecifiers the module request
209 * @param {ExportsInfo} exportsInfo exports info of this module
210 * @param {RuntimeSpec} runtime the runtime
211 * @returns {SourceData} the generated source
212 */
213const getSourceForModuleExternal = (
214 id,
215 moduleAndSpecifiers,
216 exportsInfo,
217 runtime
218) => {
219 if (!Array.isArray(moduleAndSpecifiers))
220 moduleAndSpecifiers = [moduleAndSpecifiers];
221 const initFragment = new ModuleExternalInitFragment(
222 id,
223 moduleAndSpecifiers[0]
224 );
225 const baseAccess = `${initFragment.getNamespaceIdentifier()}${propertyAccess(
226 moduleAndSpecifiers,
227 1
228 )}`;
229 const moduleRemapping = generateModuleRemapping(
230 baseAccess,
231 exportsInfo,
232 runtime
233 );
234 let expression = moduleRemapping || baseAccess;
235 return {
236 expression,
237 init: `var x = y => { var x = {}; ${RuntimeGlobals.definePropertyGetters}(x, y); return x; }\nvar y = x => () => x`,
238 runtimeRequirements: moduleRemapping
239 ? RUNTIME_REQUIREMENTS_FOR_MODULE
240 : undefined,
241 chunkInitFragments: [initFragment]
242 };
243};
244
245/**
246 * @param {string|string[]} urlAndGlobal the script request
247 * @param {RuntimeTemplate} runtimeTemplate the runtime template
248 * @returns {SourceData} the generated source
249 */
250const getSourceForScriptExternal = (urlAndGlobal, runtimeTemplate) => {
251 if (typeof urlAndGlobal === "string") {
252 urlAndGlobal = extractUrlAndGlobal(urlAndGlobal);
253 }
254 const url = urlAndGlobal[0];
255 const globalName = urlAndGlobal[1];
256 return {
257 init: "var __webpack_error__ = new Error();",
258 expression: `new Promise(${runtimeTemplate.basicFunction(
259 "resolve, reject",
260 [
261 `if(typeof ${globalName} !== "undefined") return resolve();`,
262 `${RuntimeGlobals.loadScript}(${JSON.stringify(
263 url
264 )}, ${runtimeTemplate.basicFunction("event", [
265 `if(typeof ${globalName} !== "undefined") return resolve();`,
266 "var errorType = event && (event.type === 'load' ? 'missing' : event.type);",
267 "var realSrc = event && event.target && event.target.src;",
268 "__webpack_error__.message = 'Loading script failed.\\n(' + errorType + ': ' + realSrc + ')';",
269 "__webpack_error__.name = 'ScriptExternalLoadError';",
270 "__webpack_error__.type = errorType;",
271 "__webpack_error__.request = realSrc;",
272 "reject(__webpack_error__);"
273 ])}, ${JSON.stringify(globalName)});`
274 ]
275 )}).then(${runtimeTemplate.returningFunction(
276 `${globalName}${propertyAccess(urlAndGlobal, 2)}`
277 )})`,
278 runtimeRequirements: RUNTIME_REQUIREMENTS_FOR_SCRIPT
279 };
280};
281
282/**
283 * @param {string} variableName the variable name to check
284 * @param {string} request the request path
285 * @param {RuntimeTemplate} runtimeTemplate the runtime template
286 * @returns {string} the generated source
287 */
288const checkExternalVariable = (variableName, request, runtimeTemplate) => {
289 return `if(typeof ${variableName} === 'undefined') { ${runtimeTemplate.throwMissingModuleErrorBlock(
290 { request }
291 )} }\n`;
292};
293
294/**
295 * @param {string|number} id the module id
296 * @param {boolean} optional true, if the module is optional
297 * @param {string|string[]} request the request path
298 * @param {RuntimeTemplate} runtimeTemplate the runtime template
299 * @returns {SourceData} the generated source
300 */
301const getSourceForAmdOrUmdExternal = (
302 id,
303 optional,
304 request,
305 runtimeTemplate
306) => {
307 const externalVariable = `__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier(
308 `${id}`
309 )}__`;
310 return {
311 init: optional
312 ? checkExternalVariable(
313 externalVariable,
314 Array.isArray(request) ? request.join(".") : request,
315 runtimeTemplate
316 )
317 : undefined,
318 expression: externalVariable
319 };
320};
321
322/**
323 * @param {boolean} optional true, if the module is optional
324 * @param {string|string[]} request the request path
325 * @param {RuntimeTemplate} runtimeTemplate the runtime template
326 * @returns {SourceData} the generated source
327 */
328const getSourceForDefaultCase = (optional, request, runtimeTemplate) => {
329 if (!Array.isArray(request)) {
330 // make it an array as the look up works the same basically
331 request = [request];
332 }
333
334 const variableName = request[0];
335 const objectLookup = propertyAccess(request, 1);
336 return {
337 init: optional
338 ? checkExternalVariable(variableName, request.join("."), runtimeTemplate)
339 : undefined,
340 expression: `${variableName}${objectLookup}`
341 };
342};
343
344class ExternalModule extends Module {
345 constructor(request, type, userRequest) {
346 super("javascript/dynamic", null);
347
348 // Info from Factory
349 /** @type {string | string[] | Record<string, string | string[]>} */
350 this.request = request;
351 /** @type {string} */
352 this.externalType = type;
353 /** @type {string} */
354 this.userRequest = userRequest;
355 }
356
357 /**
358 * @returns {Set<string>} types available (do not mutate)
359 */
360 getSourceTypes() {
361 return TYPES;
362 }
363
364 /**
365 * @param {LibIdentOptions} options options
366 * @returns {string | null} an identifier for library inclusion
367 */
368 libIdent(options) {
369 return this.userRequest;
370 }
371
372 /**
373 * @param {Chunk} chunk the chunk which condition should be checked
374 * @param {Compilation} compilation the compilation
375 * @returns {boolean} true, if the chunk is ok for the module
376 */
377 chunkCondition(chunk, { chunkGraph }) {
378 return chunkGraph.getNumberOfEntryModules(chunk) > 0;
379 }
380
381 /**
382 * @returns {string} a unique identifier of the module
383 */
384 identifier() {
385 return "external " + JSON.stringify(this.request);
386 }
387
388 /**
389 * @param {RequestShortener} requestShortener the request shortener
390 * @returns {string} a user readable identifier of the module
391 */
392 readableIdentifier(requestShortener) {
393 return "external " + JSON.stringify(this.request);
394 }
395
396 /**
397 * @param {NeedBuildContext} context context info
398 * @param {function(WebpackError=, boolean=): void} callback callback function, returns true, if the module needs a rebuild
399 * @returns {void}
400 */
401 needBuild(context, callback) {
402 return callback(null, !this.buildMeta);
403 }
404
405 /**
406 * @param {WebpackOptions} options webpack options
407 * @param {Compilation} compilation the compilation
408 * @param {ResolverWithOptions} resolver the resolver
409 * @param {InputFileSystem} fs the file system
410 * @param {function(WebpackError=): void} callback callback function
411 * @returns {void}
412 */
413 build(options, compilation, resolver, fs, callback) {
414 this.buildMeta = {
415 async: false,
416 exportsType: undefined
417 };
418 this.buildInfo = {
419 strict: true,
420 topLevelDeclarations: new Set(),
421 module: compilation.outputOptions.module
422 };
423 const { request, externalType } = this._getRequestAndExternalType();
424 this.buildMeta.exportsType = "dynamic";
425 let canMangle = false;
426 this.clearDependenciesAndBlocks();
427 switch (externalType) {
428 case "this":
429 this.buildInfo.strict = false;
430 break;
431 case "system":
432 case "module":
433 if (this.buildInfo.module) {
434 if (!Array.isArray(request) || request.length === 1) {
435 this.buildMeta.exportsType = "namespace";
436 canMangle = true;
437 }
438 } else {
439 this.buildMeta.async = true;
440 if (!Array.isArray(request) || request.length === 1) {
441 this.buildMeta.exportsType = "namespace";
442 canMangle = false;
443 }
444 }
445 break;
446 case "script":
447 case "promise":
448 this.buildMeta.async = true;
449 break;
450 case "import":
451 this.buildMeta.async = true;
452 if (!Array.isArray(request) || request.length === 1) {
453 this.buildMeta.exportsType = "namespace";
454 canMangle = false;
455 }
456 break;
457 }
458 this.addDependency(new StaticExportsDependency(true, canMangle));
459 callback();
460 }
461
462 /**
463 * @param {ConcatenationBailoutReasonContext} context context
464 * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
465 */
466 getConcatenationBailoutReason({ moduleGraph }) {
467 switch (this.externalType) {
468 case "amd":
469 case "amd-require":
470 case "umd":
471 case "umd2":
472 case "system":
473 case "jsonp":
474 return `${this.externalType} externals can't be concatenated`;
475 }
476 return undefined;
477 }
478
479 _getRequestAndExternalType() {
480 let { request, externalType } = this;
481 if (typeof request === "object" && !Array.isArray(request))
482 request = request[externalType];
483 return { request, externalType };
484 }
485
486 _getSourceData(runtimeTemplate, moduleGraph, chunkGraph, runtime) {
487 const { request, externalType } = this._getRequestAndExternalType();
488 switch (externalType) {
489 case "this":
490 case "window":
491 case "self":
492 return getSourceForGlobalVariableExternal(request, this.externalType);
493 case "global":
494 return getSourceForGlobalVariableExternal(
495 request,
496 runtimeTemplate.outputOptions.globalObject
497 );
498 case "commonjs":
499 case "commonjs2":
500 case "commonjs-module":
501 return getSourceForCommonJsExternal(request);
502 case "node-commonjs":
503 return this.buildInfo.module
504 ? getSourceForCommonJsExternalInNodeModule(request)
505 : getSourceForCommonJsExternal(request);
506 case "amd":
507 case "amd-require":
508 case "umd":
509 case "umd2":
510 case "system":
511 case "jsonp":
512 return getSourceForAmdOrUmdExternal(
513 chunkGraph.getModuleId(this),
514 this.isOptional(moduleGraph),
515 request,
516 runtimeTemplate
517 );
518 case "import":
519 return getSourceForImportExternal(request, runtimeTemplate);
520 case "script":
521 return getSourceForScriptExternal(request, runtimeTemplate);
522 case "module":
523 if (!this.buildInfo.module) {
524 if (!runtimeTemplate.supportsDynamicImport()) {
525 throw new Error(
526 "The target environment doesn't support dynamic import() syntax so it's not possible to use external type 'module' within a script" +
527 (runtimeTemplate.supportsEcmaScriptModuleSyntax()
528 ? "\nDid you mean to build a EcmaScript Module ('output.module: true')?"
529 : "")
530 );
531 }
532 return getSourceForImportExternal(request, runtimeTemplate);
533 }
534 if (!runtimeTemplate.supportsEcmaScriptModuleSyntax()) {
535 throw new Error(
536 "The target environment doesn't support EcmaScriptModule syntax so it's not possible to use external type 'module'"
537 );
538 }
539 return getSourceForModuleExternal(
540 chunkGraph.getModuleId(this),
541 request,
542 moduleGraph.getExportsInfo(this),
543 runtime
544 );
545 case "var":
546 case "promise":
547 case "const":
548 case "let":
549 case "assign":
550 default:
551 return getSourceForDefaultCase(
552 this.isOptional(moduleGraph),
553 request,
554 runtimeTemplate
555 );
556 }
557 }
558
559 /**
560 * @param {CodeGenerationContext} context context for code generation
561 * @returns {CodeGenerationResult} result
562 */
563 codeGeneration({
564 runtimeTemplate,
565 moduleGraph,
566 chunkGraph,
567 runtime,
568 concatenationScope
569 }) {
570 const sourceData = this._getSourceData(
571 runtimeTemplate,
572 moduleGraph,
573 chunkGraph,
574 runtime
575 );
576
577 let sourceString = sourceData.expression;
578 if (sourceData.iife)
579 sourceString = `(function() { return ${sourceString}; }())`;
580 if (concatenationScope) {
581 sourceString = `${runtimeTemplate.supportsConst() ? "const" : "var"} ${
582 ConcatenationScope.NAMESPACE_OBJECT_EXPORT
583 } = ${sourceString};`;
584 concatenationScope.registerNamespaceExport(
585 ConcatenationScope.NAMESPACE_OBJECT_EXPORT
586 );
587 } else {
588 sourceString = `module.exports = ${sourceString};`;
589 }
590 if (sourceData.init) sourceString = `${sourceData.init}\n${sourceString}`;
591
592 let data = undefined;
593 if (sourceData.chunkInitFragments) {
594 data = new Map();
595 data.set("chunkInitFragments", sourceData.chunkInitFragments);
596 }
597
598 const sources = new Map();
599 if (this.useSourceMap || this.useSimpleSourceMap) {
600 sources.set(
601 "javascript",
602 new OriginalSource(sourceString, this.identifier())
603 );
604 } else {
605 sources.set("javascript", new RawSource(sourceString));
606 }
607
608 let runtimeRequirements = sourceData.runtimeRequirements;
609 if (!concatenationScope) {
610 if (!runtimeRequirements) {
611 runtimeRequirements = RUNTIME_REQUIREMENTS;
612 } else {
613 const set = new Set(runtimeRequirements);
614 set.add(RuntimeGlobals.module);
615 runtimeRequirements = set;
616 }
617 }
618
619 return {
620 sources,
621 runtimeRequirements: runtimeRequirements || EMPTY_RUNTIME_REQUIREMENTS,
622 data
623 };
624 }
625
626 /**
627 * @param {string=} type the source type for which the size should be estimated
628 * @returns {number} the estimated size of the module (must be non-zero)
629 */
630 size(type) {
631 return 42;
632 }
633
634 /**
635 * @param {Hash} hash the hash used to track dependencies
636 * @param {UpdateHashContext} context context
637 * @returns {void}
638 */
639 updateHash(hash, context) {
640 const { chunkGraph } = context;
641 hash.update(this.externalType);
642 hash.update(JSON.stringify(this.request));
643 hash.update(
644 JSON.stringify(Boolean(this.isOptional(chunkGraph.moduleGraph)))
645 );
646 super.updateHash(hash, context);
647 }
648
649 serialize(context) {
650 const { write } = context;
651
652 write(this.request);
653 write(this.externalType);
654 write(this.userRequest);
655
656 super.serialize(context);
657 }
658
659 deserialize(context) {
660 const { read } = context;
661
662 this.request = read();
663 this.externalType = read();
664 this.userRequest = read();
665
666 super.deserialize(context);
667 }
668}
669
670makeSerializable(ExternalModule, "webpack/lib/ExternalModule");
671
672module.exports = ExternalModule;