Apply module bundling
[platform/framework/web/wrtjs.git] / node_modules / webpack / lib / asset / AssetGenerator.js
1 /*
2         MIT License http://www.opensource.org/licenses/mit-license.php
3         Author Sergey Melyukov @smelukov
4 */
5
6 "use strict";
7
8 const mimeTypes = require("mime-types");
9 const path = require("path");
10 const { RawSource } = require("webpack-sources");
11 const ConcatenationScope = require("../ConcatenationScope");
12 const Generator = require("../Generator");
13 const RuntimeGlobals = require("../RuntimeGlobals");
14 const createHash = require("../util/createHash");
15 const { makePathsRelative } = require("../util/identifier");
16 const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
17
18 /** @typedef {import("webpack-sources").Source} Source */
19 /** @typedef {import("../../declarations/WebpackOptions").AssetGeneratorOptions} AssetGeneratorOptions */
20 /** @typedef {import("../../declarations/WebpackOptions").AssetModuleOutputPath} AssetModuleOutputPath */
21 /** @typedef {import("../../declarations/WebpackOptions").RawPublicPath} RawPublicPath */
22 /** @typedef {import("../Compilation")} Compilation */
23 /** @typedef {import("../Compiler")} Compiler */
24 /** @typedef {import("../Generator").GenerateContext} GenerateContext */
25 /** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */
26 /** @typedef {import("../Module")} Module */
27 /** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
28 /** @typedef {import("../NormalModule")} NormalModule */
29 /** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
30 /** @typedef {import("../util/Hash")} Hash */
31
32 const mergeMaybeArrays = (a, b) => {
33         const set = new Set();
34         if (Array.isArray(a)) for (const item of a) set.add(item);
35         else set.add(a);
36         if (Array.isArray(b)) for (const item of b) set.add(item);
37         else set.add(b);
38         return Array.from(set);
39 };
40
41 const mergeAssetInfo = (a, b) => {
42         const result = { ...a, ...b };
43         for (const key of Object.keys(a)) {
44                 if (key in b) {
45                         if (a[key] === b[key]) continue;
46                         switch (key) {
47                                 case "fullhash":
48                                 case "chunkhash":
49                                 case "modulehash":
50                                 case "contenthash":
51                                         result[key] = mergeMaybeArrays(a[key], b[key]);
52                                         break;
53                                 case "immutable":
54                                 case "development":
55                                 case "hotModuleReplacement":
56                                 case "javascriptModule":
57                                         result[key] = a[key] || b[key];
58                                         break;
59                                 case "related":
60                                         result[key] = mergeRelatedInfo(a[key], b[key]);
61                                         break;
62                                 default:
63                                         throw new Error(`Can't handle conflicting asset info for ${key}`);
64                         }
65                 }
66         }
67         return result;
68 };
69
70 const mergeRelatedInfo = (a, b) => {
71         const result = { ...a, ...b };
72         for (const key of Object.keys(a)) {
73                 if (key in b) {
74                         if (a[key] === b[key]) continue;
75                         result[key] = mergeMaybeArrays(a[key], b[key]);
76                 }
77         }
78         return result;
79 };
80
81 const encodeDataUri = (encoding, source) => {
82         let encodedContent;
83
84         switch (encoding) {
85                 case "base64": {
86                         encodedContent = source.buffer().toString("base64");
87                         break;
88                 }
89                 case false: {
90                         const content = source.source();
91
92                         if (typeof content !== "string") {
93                                 encodedContent = content.toString("utf-8");
94                         }
95
96                         encodedContent = encodeURIComponent(encodedContent).replace(
97                                 /[!'()*]/g,
98                                 character => "%" + character.codePointAt(0).toString(16)
99                         );
100                         break;
101                 }
102                 default:
103                         throw new Error(`Unsupported encoding '${encoding}'`);
104         }
105
106         return encodedContent;
107 };
108
109 const decodeDataUriContent = (encoding, content) => {
110         const isBase64 = encoding === "base64";
111         return isBase64
112                 ? Buffer.from(content, "base64")
113                 : Buffer.from(decodeURIComponent(content), "ascii");
114 };
115
116 const JS_TYPES = new Set(["javascript"]);
117 const JS_AND_ASSET_TYPES = new Set(["javascript", "asset"]);
118 const DEFAULT_ENCODING = "base64";
119
120 class AssetGenerator extends Generator {
121         /**
122          * @param {AssetGeneratorOptions["dataUrl"]=} dataUrlOptions the options for the data url
123          * @param {string=} filename override for output.assetModuleFilename
124          * @param {RawPublicPath=} publicPath override for output.assetModulePublicPath
125          * @param {AssetModuleOutputPath=} outputPath the output path for the emitted file which is not included in the runtime import
126          * @param {boolean=} emit generate output asset
127          */
128         constructor(dataUrlOptions, filename, publicPath, outputPath, emit) {
129                 super();
130                 this.dataUrlOptions = dataUrlOptions;
131                 this.filename = filename;
132                 this.publicPath = publicPath;
133                 this.outputPath = outputPath;
134                 this.emit = emit;
135         }
136
137         /**
138          * @param {NormalModule} module module
139          * @param {RuntimeTemplate} runtimeTemplate runtime template
140          * @returns {string} source file name
141          */
142         getSourceFileName(module, runtimeTemplate) {
143                 return makePathsRelative(
144                         runtimeTemplate.compilation.compiler.context,
145                         module.matchResource || module.resource,
146                         runtimeTemplate.compilation.compiler.root
147                 ).replace(/^\.\//, "");
148         }
149
150         /**
151          * @param {NormalModule} module module for which the bailout reason should be determined
152          * @param {ConcatenationBailoutReasonContext} context context
153          * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
154          */
155         getConcatenationBailoutReason(module, context) {
156                 return undefined;
157         }
158
159         /**
160          * @param {NormalModule} module module
161          * @returns {string} mime type
162          */
163         getMimeType(module) {
164                 if (typeof this.dataUrlOptions === "function") {
165                         throw new Error(
166                                 "This method must not be called when dataUrlOptions is a function"
167                         );
168                 }
169
170                 let mimeType = this.dataUrlOptions.mimetype;
171                 if (mimeType === undefined) {
172                         const ext = path.extname(module.nameForCondition());
173                         if (
174                                 module.resourceResolveData &&
175                                 module.resourceResolveData.mimetype !== undefined
176                         ) {
177                                 mimeType =
178                                         module.resourceResolveData.mimetype +
179                                         module.resourceResolveData.parameters;
180                         } else if (ext) {
181                                 mimeType = mimeTypes.lookup(ext);
182
183                                 if (typeof mimeType !== "string") {
184                                         throw new Error(
185                                                 "DataUrl can't be generated automatically, " +
186                                                         `because there is no mimetype for "${ext}" in mimetype database. ` +
187                                                         'Either pass a mimetype via "generator.mimetype" or ' +
188                                                         'use type: "asset/resource" to create a resource file instead of a DataUrl'
189                                         );
190                                 }
191                         }
192                 }
193
194                 if (typeof mimeType !== "string") {
195                         throw new Error(
196                                 "DataUrl can't be generated automatically. " +
197                                         'Either pass a mimetype via "generator.mimetype" or ' +
198                                         'use type: "asset/resource" to create a resource file instead of a DataUrl'
199                         );
200                 }
201
202                 return mimeType;
203         }
204
205         /**
206          * @param {NormalModule} module module for which the code should be generated
207          * @param {GenerateContext} generateContext context for generate
208          * @returns {Source} generated code
209          */
210         generate(
211                 module,
212                 {
213                         runtime,
214                         concatenationScope,
215                         chunkGraph,
216                         runtimeTemplate,
217                         runtimeRequirements,
218                         type,
219                         getData
220                 }
221         ) {
222                 switch (type) {
223                         case "asset":
224                                 return module.originalSource();
225                         default: {
226                                 let content;
227                                 const originalSource = module.originalSource();
228                                 if (module.buildInfo.dataUrl) {
229                                         let encodedSource;
230                                         if (typeof this.dataUrlOptions === "function") {
231                                                 encodedSource = this.dataUrlOptions.call(
232                                                         null,
233                                                         originalSource.source(),
234                                                         {
235                                                                 filename: module.matchResource || module.resource,
236                                                                 module
237                                                         }
238                                                 );
239                                         } else {
240                                                 /** @type {string | false | undefined} */
241                                                 let encoding = this.dataUrlOptions.encoding;
242                                                 if (encoding === undefined) {
243                                                         if (
244                                                                 module.resourceResolveData &&
245                                                                 module.resourceResolveData.encoding !== undefined
246                                                         ) {
247                                                                 encoding = module.resourceResolveData.encoding;
248                                                         }
249                                                 }
250                                                 if (encoding === undefined) {
251                                                         encoding = DEFAULT_ENCODING;
252                                                 }
253                                                 const mimeType = this.getMimeType(module);
254
255                                                 let encodedContent;
256
257                                                 if (
258                                                         module.resourceResolveData &&
259                                                         module.resourceResolveData.encoding === encoding &&
260                                                         decodeDataUriContent(
261                                                                 module.resourceResolveData.encoding,
262                                                                 module.resourceResolveData.encodedContent
263                                                         ).equals(originalSource.buffer())
264                                                 ) {
265                                                         encodedContent = module.resourceResolveData.encodedContent;
266                                                 } else {
267                                                         encodedContent = encodeDataUri(encoding, originalSource);
268                                                 }
269
270                                                 encodedSource = `data:${mimeType}${
271                                                         encoding ? `;${encoding}` : ""
272                                                 },${encodedContent}`;
273                                         }
274                                         const data = getData();
275                                         data.set("url", Buffer.from(encodedSource));
276                                         content = JSON.stringify(encodedSource);
277                                 } else {
278                                         const assetModuleFilename =
279                                                 this.filename || runtimeTemplate.outputOptions.assetModuleFilename;
280                                         const hash = createHash(runtimeTemplate.outputOptions.hashFunction);
281                                         if (runtimeTemplate.outputOptions.hashSalt) {
282                                                 hash.update(runtimeTemplate.outputOptions.hashSalt);
283                                         }
284                                         hash.update(originalSource.buffer());
285                                         const fullHash = /** @type {string} */ (
286                                                 hash.digest(runtimeTemplate.outputOptions.hashDigest)
287                                         );
288                                         const contentHash = nonNumericOnlyHash(
289                                                 fullHash,
290                                                 runtimeTemplate.outputOptions.hashDigestLength
291                                         );
292                                         module.buildInfo.fullContentHash = fullHash;
293                                         const sourceFilename = this.getSourceFileName(
294                                                 module,
295                                                 runtimeTemplate
296                                         );
297                                         let { path: filename, info: assetInfo } =
298                                                 runtimeTemplate.compilation.getAssetPathWithInfo(
299                                                         assetModuleFilename,
300                                                         {
301                                                                 module,
302                                                                 runtime,
303                                                                 filename: sourceFilename,
304                                                                 chunkGraph,
305                                                                 contentHash
306                                                         }
307                                                 );
308                                         let assetPath;
309                                         if (this.publicPath !== undefined) {
310                                                 const { path, info } =
311                                                         runtimeTemplate.compilation.getAssetPathWithInfo(
312                                                                 this.publicPath,
313                                                                 {
314                                                                         module,
315                                                                         runtime,
316                                                                         filename: sourceFilename,
317                                                                         chunkGraph,
318                                                                         contentHash
319                                                                 }
320                                                         );
321                                                 assetInfo = mergeAssetInfo(assetInfo, info);
322                                                 assetPath = JSON.stringify(path + filename);
323                                         } else {
324                                                 runtimeRequirements.add(RuntimeGlobals.publicPath); // add __webpack_require__.p
325                                                 assetPath = runtimeTemplate.concatenation(
326                                                         { expr: RuntimeGlobals.publicPath },
327                                                         filename
328                                                 );
329                                         }
330                                         assetInfo = {
331                                                 sourceFilename,
332                                                 ...assetInfo
333                                         };
334                                         if (this.outputPath) {
335                                                 const { path: outputPath, info } =
336                                                         runtimeTemplate.compilation.getAssetPathWithInfo(
337                                                                 this.outputPath,
338                                                                 {
339                                                                         module,
340                                                                         runtime,
341                                                                         filename: sourceFilename,
342                                                                         chunkGraph,
343                                                                         contentHash
344                                                                 }
345                                                         );
346                                                 assetInfo = mergeAssetInfo(assetInfo, info);
347                                                 filename = path.posix.join(outputPath, filename);
348                                         }
349                                         module.buildInfo.filename = filename;
350                                         module.buildInfo.assetInfo = assetInfo;
351                                         if (getData) {
352                                                 // Due to code generation caching module.buildInfo.XXX can't used to store such information
353                                                 // It need to be stored in the code generation results instead, where it's cached too
354                                                 // TODO webpack 6 For back-compat reasons we also store in on module.buildInfo
355                                                 const data = getData();
356                                                 data.set("fullContentHash", fullHash);
357                                                 data.set("filename", filename);
358                                                 data.set("assetInfo", assetInfo);
359                                         }
360                                         content = assetPath;
361                                 }
362
363                                 if (concatenationScope) {
364                                         concatenationScope.registerNamespaceExport(
365                                                 ConcatenationScope.NAMESPACE_OBJECT_EXPORT
366                                         );
367                                         return new RawSource(
368                                                 `${runtimeTemplate.supportsConst() ? "const" : "var"} ${
369                                                         ConcatenationScope.NAMESPACE_OBJECT_EXPORT
370                                                 } = ${content};`
371                                         );
372                                 } else {
373                                         runtimeRequirements.add(RuntimeGlobals.module);
374                                         return new RawSource(
375                                                 `${RuntimeGlobals.module}.exports = ${content};`
376                                         );
377                                 }
378                         }
379                 }
380         }
381
382         /**
383          * @param {NormalModule} module fresh module
384          * @returns {Set<string>} available types (do not mutate)
385          */
386         getTypes(module) {
387                 if ((module.buildInfo && module.buildInfo.dataUrl) || this.emit === false) {
388                         return JS_TYPES;
389                 } else {
390                         return JS_AND_ASSET_TYPES;
391                 }
392         }
393
394         /**
395          * @param {NormalModule} module the module
396          * @param {string=} type source type
397          * @returns {number} estimate size of the module
398          */
399         getSize(module, type) {
400                 switch (type) {
401                         case "asset": {
402                                 const originalSource = module.originalSource();
403
404                                 if (!originalSource) {
405                                         return 0;
406                                 }
407
408                                 return originalSource.size();
409                         }
410                         default:
411                                 if (module.buildInfo && module.buildInfo.dataUrl) {
412                                         const originalSource = module.originalSource();
413
414                                         if (!originalSource) {
415                                                 return 0;
416                                         }
417
418                                         // roughly for data url
419                                         // Example: m.exports="data:image/png;base64,ag82/f+2=="
420                                         // 4/3 = base64 encoding
421                                         // 34 = ~ data url header + footer + rounding
422                                         return originalSource.size() * 1.34 + 36;
423                                 } else {
424                                         // it's only estimated so this number is probably fine
425                                         // Example: m.exports=r.p+"0123456789012345678901.ext"
426                                         return 42;
427                                 }
428                 }
429         }
430
431         /**
432          * @param {Hash} hash hash that will be modified
433          * @param {UpdateHashContext} updateHashContext context for updating hash
434          */
435         updateHash(hash, { module, runtime, runtimeTemplate, chunkGraph }) {
436                 if (module.buildInfo.dataUrl) {
437                         hash.update("data-url");
438                         // this.dataUrlOptions as function should be pure and only depend on input source and filename
439                         // therefore it doesn't need to be hashed
440                         if (typeof this.dataUrlOptions === "function") {
441                                 const ident = /** @type {{ ident?: string }} */ (this.dataUrlOptions)
442                                         .ident;
443                                 if (ident) hash.update(ident);
444                         } else {
445                                 if (
446                                         this.dataUrlOptions.encoding &&
447                                         this.dataUrlOptions.encoding !== DEFAULT_ENCODING
448                                 ) {
449                                         hash.update(this.dataUrlOptions.encoding);
450                                 }
451                                 if (this.dataUrlOptions.mimetype)
452                                         hash.update(this.dataUrlOptions.mimetype);
453                                 // computed mimetype depends only on module filename which is already part of the hash
454                         }
455                 } else {
456                         hash.update("resource");
457
458                         const pathData = {
459                                 module,
460                                 runtime,
461                                 filename: this.getSourceFileName(module, runtimeTemplate),
462                                 chunkGraph,
463                                 contentHash: runtimeTemplate.contentHashReplacement
464                         };
465
466                         if (typeof this.publicPath === "function") {
467                                 hash.update("path");
468                                 const assetInfo = {};
469                                 hash.update(this.publicPath(pathData, assetInfo));
470                                 hash.update(JSON.stringify(assetInfo));
471                         } else if (this.publicPath) {
472                                 hash.update("path");
473                                 hash.update(this.publicPath);
474                         } else {
475                                 hash.update("no-path");
476                         }
477
478                         const assetModuleFilename =
479                                 this.filename || runtimeTemplate.outputOptions.assetModuleFilename;
480                         const { path: filename, info } =
481                                 runtimeTemplate.compilation.getAssetPathWithInfo(
482                                         assetModuleFilename,
483                                         pathData
484                                 );
485                         hash.update(filename);
486                         hash.update(JSON.stringify(info));
487                 }
488         }
489 }
490
491 module.exports = AssetGenerator;