UNPKG

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