source: imaps-frontend/node_modules/webpack/lib/asset/AssetGenerator.js

main
Last change on this file was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 4 days ago

F4 Finalna Verzija

  • Property mode set to 100644
File size: 20.4 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Sergey Melyukov @smelukov
4*/
5
6"use strict";
7
8const mimeTypes = require("mime-types");
9const path = require("path");
10const { RawSource } = require("webpack-sources");
11const ConcatenationScope = require("../ConcatenationScope");
12const Generator = require("../Generator");
13const {
14 NO_TYPES,
15 ASSET_TYPES,
16 ASSET_AND_JS_TYPES,
17 ASSET_AND_JS_AND_CSS_URL_TYPES,
18 ASSET_AND_CSS_URL_TYPES,
19 JS_TYPES,
20 JS_AND_CSS_URL_TYPES,
21 CSS_URL_TYPES
22} = require("../ModuleSourceTypesConstants");
23const { ASSET_MODULE_TYPE } = require("../ModuleTypeConstants");
24const RuntimeGlobals = require("../RuntimeGlobals");
25const CssUrlDependency = require("../dependencies/CssUrlDependency");
26const createHash = require("../util/createHash");
27const { makePathsRelative } = require("../util/identifier");
28const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
29
30/** @typedef {import("webpack-sources").Source} Source */
31/** @typedef {import("../../declarations/WebpackOptions").AssetGeneratorDataUrlOptions} AssetGeneratorDataUrlOptions */
32/** @typedef {import("../../declarations/WebpackOptions").AssetGeneratorOptions} AssetGeneratorOptions */
33/** @typedef {import("../../declarations/WebpackOptions").AssetModuleFilename} AssetModuleFilename */
34/** @typedef {import("../../declarations/WebpackOptions").AssetModuleOutputPath} AssetModuleOutputPath */
35/** @typedef {import("../../declarations/WebpackOptions").RawPublicPath} RawPublicPath */
36/** @typedef {import("../Compilation")} Compilation */
37/** @typedef {import("../Compilation").AssetInfo} AssetInfo */
38/** @typedef {import("../Compilation").InterpolatedPathAndAssetInfo} InterpolatedPathAndAssetInfo */
39/** @typedef {import("../Compiler")} Compiler */
40/** @typedef {import("../Generator").GenerateContext} GenerateContext */
41/** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */
42/** @typedef {import("../Module")} Module */
43/** @typedef {import("../Module").BuildInfo} BuildInfo */
44/** @typedef {import("../Module").BuildMeta} BuildMeta */
45/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
46/** @typedef {import("../Module").SourceTypes} SourceTypes */
47/** @typedef {import("../ModuleGraph")} ModuleGraph */
48/** @typedef {import("../NormalModule")} NormalModule */
49/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
50/** @typedef {import("../TemplatedPathPlugin").TemplatePath} TemplatePath */
51/** @typedef {import("../util/Hash")} Hash */
52/** @typedef {import("../util/createHash").Algorithm} Algorithm */
53
54/**
55 * @template T
56 * @template U
57 * @param {Array<T> | Set<T>} a a
58 * @param {Array<U> | Set<U>} b b
59 * @returns {Array<T> & Array<U>} array
60 */
61const mergeMaybeArrays = (a, b) => {
62 const set = new Set();
63 if (Array.isArray(a)) for (const item of a) set.add(item);
64 else set.add(a);
65 if (Array.isArray(b)) for (const item of b) set.add(item);
66 else set.add(b);
67 return Array.from(set);
68};
69
70/**
71 * @template {object} T
72 * @template {object} U
73 * @param {TODO} a a
74 * @param {TODO} b b
75 * @returns {T & U} object
76 */
77const mergeAssetInfo = (a, b) => {
78 const result = { ...a, ...b };
79 for (const key of Object.keys(a)) {
80 if (key in b) {
81 if (a[key] === b[key]) continue;
82 switch (key) {
83 case "fullhash":
84 case "chunkhash":
85 case "modulehash":
86 case "contenthash":
87 result[key] = mergeMaybeArrays(a[key], b[key]);
88 break;
89 case "immutable":
90 case "development":
91 case "hotModuleReplacement":
92 case "javascriptModule":
93 result[key] = a[key] || b[key];
94 break;
95 case "related":
96 result[key] = mergeRelatedInfo(a[key], b[key]);
97 break;
98 default:
99 throw new Error(`Can't handle conflicting asset info for ${key}`);
100 }
101 }
102 }
103 return result;
104};
105
106/**
107 * @template {object} T
108 * @template {object} U
109 * @param {TODO} a a
110 * @param {TODO} b b
111 * @returns {T & U} object
112 */
113const mergeRelatedInfo = (a, b) => {
114 const result = { ...a, ...b };
115 for (const key of Object.keys(a)) {
116 if (key in b) {
117 if (a[key] === b[key]) continue;
118 result[key] = mergeMaybeArrays(a[key], b[key]);
119 }
120 }
121 return result;
122};
123
124/**
125 * @param {"base64" | false} encoding encoding
126 * @param {Source} source source
127 * @returns {string} encoded data
128 */
129const encodeDataUri = (encoding, source) => {
130 /** @type {string | undefined} */
131 let encodedContent;
132
133 switch (encoding) {
134 case "base64": {
135 encodedContent = source.buffer().toString("base64");
136 break;
137 }
138 case false: {
139 const content = source.source();
140
141 if (typeof content !== "string") {
142 encodedContent = content.toString("utf-8");
143 }
144
145 encodedContent = encodeURIComponent(
146 /** @type {string} */
147 (encodedContent)
148 ).replace(
149 /[!'()*]/g,
150 character =>
151 `%${/** @type {number} */ (character.codePointAt(0)).toString(16)}`
152 );
153 break;
154 }
155 default:
156 throw new Error(`Unsupported encoding '${encoding}'`);
157 }
158
159 return encodedContent;
160};
161
162/**
163 * @param {string} encoding encoding
164 * @param {string} content content
165 * @returns {Buffer} decoded content
166 */
167const decodeDataUriContent = (encoding, content) => {
168 const isBase64 = encoding === "base64";
169
170 if (isBase64) {
171 return Buffer.from(content, "base64");
172 }
173
174 // If we can't decode return the original body
175 try {
176 return Buffer.from(decodeURIComponent(content), "ascii");
177 } catch (_) {
178 return Buffer.from(content, "ascii");
179 }
180};
181
182const DEFAULT_ENCODING = "base64";
183
184class AssetGenerator extends Generator {
185 /**
186 * @param {ModuleGraph} moduleGraph the module graph
187 * @param {AssetGeneratorOptions["dataUrl"]=} dataUrlOptions the options for the data url
188 * @param {AssetModuleFilename=} filename override for output.assetModuleFilename
189 * @param {RawPublicPath=} publicPath override for output.assetModulePublicPath
190 * @param {AssetModuleOutputPath=} outputPath the output path for the emitted file which is not included in the runtime import
191 * @param {boolean=} emit generate output asset
192 */
193 constructor(
194 moduleGraph,
195 dataUrlOptions,
196 filename,
197 publicPath,
198 outputPath,
199 emit
200 ) {
201 super();
202 this.dataUrlOptions = dataUrlOptions;
203 this.filename = filename;
204 this.publicPath = publicPath;
205 this.outputPath = outputPath;
206 this.emit = emit;
207 this._moduleGraph = moduleGraph;
208 }
209
210 /**
211 * @param {NormalModule} module module
212 * @param {RuntimeTemplate} runtimeTemplate runtime template
213 * @returns {string} source file name
214 */
215 getSourceFileName(module, runtimeTemplate) {
216 return makePathsRelative(
217 runtimeTemplate.compilation.compiler.context,
218 module.matchResource || module.resource,
219 runtimeTemplate.compilation.compiler.root
220 ).replace(/^\.\//, "");
221 }
222
223 /**
224 * @param {NormalModule} module module for which the bailout reason should be determined
225 * @param {ConcatenationBailoutReasonContext} context context
226 * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
227 */
228 getConcatenationBailoutReason(module, context) {
229 return undefined;
230 }
231
232 /**
233 * @param {NormalModule} module module
234 * @returns {string} mime type
235 */
236 getMimeType(module) {
237 if (typeof this.dataUrlOptions === "function") {
238 throw new Error(
239 "This method must not be called when dataUrlOptions is a function"
240 );
241 }
242
243 /** @type {string | boolean | undefined} */
244 let mimeType =
245 /** @type {AssetGeneratorDataUrlOptions} */
246 (this.dataUrlOptions).mimetype;
247 if (mimeType === undefined) {
248 const ext = path.extname(
249 /** @type {string} */
250 (module.nameForCondition())
251 );
252 if (
253 module.resourceResolveData &&
254 module.resourceResolveData.mimetype !== undefined
255 ) {
256 mimeType =
257 module.resourceResolveData.mimetype +
258 module.resourceResolveData.parameters;
259 } else if (ext) {
260 mimeType = mimeTypes.lookup(ext);
261
262 if (typeof mimeType !== "string") {
263 throw new Error(
264 "DataUrl can't be generated automatically, " +
265 `because there is no mimetype for "${ext}" in mimetype database. ` +
266 'Either pass a mimetype via "generator.mimetype" or ' +
267 'use type: "asset/resource" to create a resource file instead of a DataUrl'
268 );
269 }
270 }
271 }
272
273 if (typeof mimeType !== "string") {
274 throw new Error(
275 "DataUrl can't be generated automatically. " +
276 'Either pass a mimetype via "generator.mimetype" or ' +
277 'use type: "asset/resource" to create a resource file instead of a DataUrl'
278 );
279 }
280
281 return /** @type {string} */ (mimeType);
282 }
283
284 /**
285 * @param {NormalModule} module module for which the code should be generated
286 * @returns {string} DataURI
287 */
288 generateDataUri(module) {
289 const source = /** @type {Source} */ (module.originalSource());
290
291 let encodedSource;
292
293 if (typeof this.dataUrlOptions === "function") {
294 encodedSource = this.dataUrlOptions.call(null, source.source(), {
295 filename: module.matchResource || module.resource,
296 module
297 });
298 } else {
299 /** @type {"base64" | false | undefined} */
300 let encoding =
301 /** @type {AssetGeneratorDataUrlOptions} */
302 (this.dataUrlOptions).encoding;
303 if (
304 encoding === undefined &&
305 module.resourceResolveData &&
306 module.resourceResolveData.encoding !== undefined
307 ) {
308 encoding = module.resourceResolveData.encoding;
309 }
310 if (encoding === undefined) {
311 encoding = DEFAULT_ENCODING;
312 }
313 const mimeType = this.getMimeType(module);
314
315 let encodedContent;
316
317 if (
318 module.resourceResolveData &&
319 module.resourceResolveData.encoding === encoding &&
320 decodeDataUriContent(
321 module.resourceResolveData.encoding,
322 module.resourceResolveData.encodedContent
323 ).equals(source.buffer())
324 ) {
325 encodedContent = module.resourceResolveData.encodedContent;
326 } else {
327 encodedContent = encodeDataUri(encoding, source);
328 }
329
330 encodedSource = `data:${mimeType}${
331 encoding ? `;${encoding}` : ""
332 },${encodedContent}`;
333 }
334
335 return encodedSource;
336 }
337
338 /**
339 * @private
340 * @param {NormalModule} module module for which the code should be generated
341 * @param {GenerateContext} generateContext context for generate
342 * @param {string} contentHash the content hash
343 * @returns {{ filename: string, originalFilename: string, assetInfo: AssetInfo }} info
344 */
345 _getFilenameWithInfo(
346 module,
347 { runtime, runtimeTemplate, chunkGraph },
348 contentHash
349 ) {
350 const assetModuleFilename =
351 this.filename ||
352 /** @type {AssetModuleFilename} */
353 (runtimeTemplate.outputOptions.assetModuleFilename);
354
355 const sourceFilename = this.getSourceFileName(module, runtimeTemplate);
356 let { path: filename, info: assetInfo } =
357 runtimeTemplate.compilation.getAssetPathWithInfo(assetModuleFilename, {
358 module,
359 runtime,
360 filename: sourceFilename,
361 chunkGraph,
362 contentHash
363 });
364
365 const originalFilename = filename;
366
367 if (this.outputPath) {
368 const { path: outputPath, info } =
369 runtimeTemplate.compilation.getAssetPathWithInfo(this.outputPath, {
370 module,
371 runtime,
372 filename: sourceFilename,
373 chunkGraph,
374 contentHash
375 });
376 filename = path.posix.join(outputPath, filename);
377 assetInfo = mergeAssetInfo(assetInfo, info);
378 }
379
380 return { originalFilename, filename, assetInfo };
381 }
382
383 /**
384 * @private
385 * @param {NormalModule} module module for which the code should be generated
386 * @param {GenerateContext} generateContext context for generate
387 * @param {string} filename the filename
388 * @param {AssetInfo} assetInfo the asset info
389 * @param {string} contentHash the content hash
390 * @returns {{ assetPath: string, assetInfo: AssetInfo }} asset path and info
391 */
392 _getAssetPathWithInfo(
393 module,
394 { runtimeTemplate, runtime, chunkGraph, type, runtimeRequirements },
395 filename,
396 assetInfo,
397 contentHash
398 ) {
399 const sourceFilename = this.getSourceFileName(module, runtimeTemplate);
400
401 let assetPath;
402
403 if (this.publicPath !== undefined && type === "javascript") {
404 const { path, info } = runtimeTemplate.compilation.getAssetPathWithInfo(
405 this.publicPath,
406 {
407 module,
408 runtime,
409 filename: sourceFilename,
410 chunkGraph,
411 contentHash
412 }
413 );
414 assetInfo = mergeAssetInfo(assetInfo, info);
415 assetPath = JSON.stringify(path + filename);
416 } else if (this.publicPath !== undefined && type === "css-url") {
417 const { path, info } = runtimeTemplate.compilation.getAssetPathWithInfo(
418 this.publicPath,
419 {
420 module,
421 runtime,
422 filename: sourceFilename,
423 chunkGraph,
424 contentHash
425 }
426 );
427 assetInfo = mergeAssetInfo(assetInfo, info);
428 assetPath = path + filename;
429 } else if (type === "javascript") {
430 // add __webpack_require__.p
431 runtimeRequirements.add(RuntimeGlobals.publicPath);
432 assetPath = runtimeTemplate.concatenation(
433 { expr: RuntimeGlobals.publicPath },
434 filename
435 );
436 } else if (type === "css-url") {
437 const compilation = runtimeTemplate.compilation;
438 const path =
439 compilation.outputOptions.publicPath === "auto"
440 ? CssUrlDependency.PUBLIC_PATH_AUTO
441 : compilation.getAssetPath(
442 /** @type {TemplatePath} */
443 (compilation.outputOptions.publicPath),
444 {
445 hash: compilation.hash
446 }
447 );
448
449 assetPath = path + filename;
450 }
451
452 return {
453 // eslint-disable-next-line object-shorthand
454 assetPath: /** @type {string} */ (assetPath),
455 assetInfo: { sourceFilename, ...assetInfo }
456 };
457 }
458
459 /**
460 * @param {NormalModule} module module for which the code should be generated
461 * @param {GenerateContext} generateContext context for generate
462 * @returns {Source | null} generated code
463 */
464 generate(module, generateContext) {
465 const {
466 type,
467 getData,
468 runtimeTemplate,
469 runtimeRequirements,
470 concatenationScope
471 } = generateContext;
472
473 let content;
474
475 const needContent = type === "javascript" || type === "css-url";
476
477 const data = getData ? getData() : undefined;
478
479 if (
480 /** @type {BuildInfo} */
481 (module.buildInfo).dataUrl &&
482 needContent
483 ) {
484 const encodedSource = this.generateDataUri(module);
485 content =
486 type === "javascript" ? JSON.stringify(encodedSource) : encodedSource;
487
488 if (data) {
489 data.set("url", { [type]: content, ...data.get("url") });
490 }
491 } else {
492 const hash = createHash(
493 /** @type {Algorithm} */
494 (runtimeTemplate.outputOptions.hashFunction)
495 );
496
497 if (runtimeTemplate.outputOptions.hashSalt) {
498 hash.update(runtimeTemplate.outputOptions.hashSalt);
499 }
500
501 hash.update(/** @type {Source} */ (module.originalSource()).buffer());
502
503 const fullHash =
504 /** @type {string} */
505 (hash.digest(runtimeTemplate.outputOptions.hashDigest));
506
507 if (data) {
508 data.set("fullContentHash", fullHash);
509 }
510
511 /** @type {BuildInfo} */
512 (module.buildInfo).fullContentHash = fullHash;
513
514 /** @type {string} */
515 const contentHash = nonNumericOnlyHash(
516 fullHash,
517 /** @type {number} */
518 (generateContext.runtimeTemplate.outputOptions.hashDigestLength)
519 );
520
521 if (data) {
522 data.set("contentHash", contentHash);
523 }
524
525 const { originalFilename, filename, assetInfo } =
526 this._getFilenameWithInfo(module, generateContext, contentHash);
527
528 if (data) {
529 data.set("filename", filename);
530 }
531
532 let { assetPath, assetInfo: newAssetInfo } = this._getAssetPathWithInfo(
533 module,
534 generateContext,
535 originalFilename,
536 assetInfo,
537 contentHash
538 );
539
540 if (data && (type === "javascript" || type === "css-url")) {
541 data.set("url", { [type]: assetPath, ...data.get("url") });
542 }
543
544 if (data && data.get("assetInfo")) {
545 newAssetInfo = mergeAssetInfo(data.get("assetInfo"), newAssetInfo);
546 }
547
548 if (data) {
549 data.set("assetInfo", newAssetInfo);
550 }
551
552 // Due to code generation caching module.buildInfo.XXX can't used to store such information
553 // It need to be stored in the code generation results instead, where it's cached too
554 // TODO webpack 6 For back-compat reasons we also store in on module.buildInfo
555 /** @type {BuildInfo} */
556 (module.buildInfo).filename = filename;
557
558 /** @type {BuildInfo} */
559 (module.buildInfo).assetInfo = newAssetInfo;
560
561 content = assetPath;
562 }
563
564 if (type === "javascript") {
565 if (concatenationScope) {
566 concatenationScope.registerNamespaceExport(
567 ConcatenationScope.NAMESPACE_OBJECT_EXPORT
568 );
569
570 return new RawSource(
571 `${runtimeTemplate.supportsConst() ? "const" : "var"} ${
572 ConcatenationScope.NAMESPACE_OBJECT_EXPORT
573 } = ${content};`
574 );
575 }
576
577 runtimeRequirements.add(RuntimeGlobals.module);
578
579 return new RawSource(`${RuntimeGlobals.module}.exports = ${content};`);
580 } else if (type === "css-url") {
581 return null;
582 }
583
584 return /** @type {Source} */ (module.originalSource());
585 }
586
587 /**
588 * @param {NormalModule} module fresh module
589 * @returns {SourceTypes} available types (do not mutate)
590 */
591 getTypes(module) {
592 const sourceTypes = new Set();
593 const connections = this._moduleGraph.getIncomingConnections(module);
594
595 for (const connection of connections) {
596 if (!connection.originModule) {
597 continue;
598 }
599
600 sourceTypes.add(connection.originModule.type.split("/")[0]);
601 }
602
603 if ((module.buildInfo && module.buildInfo.dataUrl) || this.emit === false) {
604 if (sourceTypes) {
605 if (sourceTypes.has("javascript") && sourceTypes.has("css")) {
606 return JS_AND_CSS_URL_TYPES;
607 } else if (sourceTypes.has("javascript")) {
608 return JS_TYPES;
609 } else if (sourceTypes.has("css")) {
610 return CSS_URL_TYPES;
611 }
612 }
613
614 return NO_TYPES;
615 }
616
617 if (sourceTypes) {
618 if (sourceTypes.has("javascript") && sourceTypes.has("css")) {
619 return ASSET_AND_JS_AND_CSS_URL_TYPES;
620 } else if (sourceTypes.has("javascript")) {
621 return ASSET_AND_JS_TYPES;
622 } else if (sourceTypes.has("css")) {
623 return ASSET_AND_CSS_URL_TYPES;
624 }
625 }
626
627 return ASSET_TYPES;
628 }
629
630 /**
631 * @param {NormalModule} module the module
632 * @param {string=} type source type
633 * @returns {number} estimate size of the module
634 */
635 getSize(module, type) {
636 switch (type) {
637 case ASSET_MODULE_TYPE: {
638 const originalSource = module.originalSource();
639
640 if (!originalSource) {
641 return 0;
642 }
643
644 return originalSource.size();
645 }
646 default:
647 if (module.buildInfo && module.buildInfo.dataUrl) {
648 const originalSource = module.originalSource();
649
650 if (!originalSource) {
651 return 0;
652 }
653
654 // roughly for data url
655 // Example: m.exports="data:image/png;base64,ag82/f+2=="
656 // 4/3 = base64 encoding
657 // 34 = ~ data url header + footer + rounding
658 return originalSource.size() * 1.34 + 36;
659 }
660 // it's only estimated so this number is probably fine
661 // Example: m.exports=r.p+"0123456789012345678901.ext"
662 return 42;
663 }
664 }
665
666 /**
667 * @param {Hash} hash hash that will be modified
668 * @param {UpdateHashContext} updateHashContext context for updating hash
669 */
670 updateHash(hash, updateHashContext) {
671 const { module } = updateHashContext;
672 if (
673 /** @type {BuildInfo} */
674 (module.buildInfo).dataUrl
675 ) {
676 hash.update("data-url");
677 // this.dataUrlOptions as function should be pure and only depend on input source and filename
678 // therefore it doesn't need to be hashed
679 if (typeof this.dataUrlOptions === "function") {
680 const ident = /** @type {{ ident?: string }} */ (this.dataUrlOptions)
681 .ident;
682 if (ident) hash.update(ident);
683 } else {
684 const dataUrlOptions =
685 /** @type {AssetGeneratorDataUrlOptions} */
686 (this.dataUrlOptions);
687 if (
688 dataUrlOptions.encoding &&
689 dataUrlOptions.encoding !== DEFAULT_ENCODING
690 ) {
691 hash.update(dataUrlOptions.encoding);
692 }
693 if (dataUrlOptions.mimetype) hash.update(dataUrlOptions.mimetype);
694 // computed mimetype depends only on module filename which is already part of the hash
695 }
696 } else {
697 hash.update("resource");
698
699 const { module, chunkGraph, runtime } = updateHashContext;
700 const runtimeTemplate =
701 /** @type {NonNullable<UpdateHashContext["runtimeTemplate"]>} */
702 (updateHashContext.runtimeTemplate);
703
704 const pathData = {
705 module,
706 runtime,
707 filename: this.getSourceFileName(module, runtimeTemplate),
708 chunkGraph,
709 contentHash: runtimeTemplate.contentHashReplacement
710 };
711
712 if (typeof this.publicPath === "function") {
713 hash.update("path");
714 const assetInfo = {};
715 hash.update(this.publicPath(pathData, assetInfo));
716 hash.update(JSON.stringify(assetInfo));
717 } else if (this.publicPath) {
718 hash.update("path");
719 hash.update(this.publicPath);
720 } else {
721 hash.update("no-path");
722 }
723
724 const assetModuleFilename =
725 this.filename ||
726 /** @type {AssetModuleFilename} */
727 (runtimeTemplate.outputOptions.assetModuleFilename);
728 const { path: filename, info } =
729 runtimeTemplate.compilation.getAssetPathWithInfo(
730 assetModuleFilename,
731 pathData
732 );
733 hash.update(filename);
734 hash.update(JSON.stringify(info));
735 }
736 }
737}
738
739module.exports = AssetGenerator;
Note: See TracBrowser for help on using the repository browser.