| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Sergey Melyukov @smelukov
- */
- "use strict";
- const path = require("path");
- const { RawSource } = require("webpack-sources");
- const ConcatenationScope = require("../ConcatenationScope");
- const Generator = require("../Generator");
- const {
- ASSET_AND_ASSET_URL_TYPES,
- ASSET_AND_JAVASCRIPT_AND_ASSET_URL_TYPES,
- ASSET_AND_JAVASCRIPT_TYPES,
- ASSET_TYPES,
- ASSET_URL_TYPE,
- ASSET_URL_TYPES,
- JAVASCRIPT_AND_ASSET_URL_TYPES,
- JAVASCRIPT_TYPE,
- JAVASCRIPT_TYPES,
- NO_TYPES
- } = require("../ModuleSourceTypeConstants");
- const { ASSET_MODULE_TYPE } = require("../ModuleTypeConstants");
- const RuntimeGlobals = require("../RuntimeGlobals");
- const { toTemplateSourceFileName } = require("../TemplatedPathPlugin");
- const URLDependency = require("../dependencies/URLDependency");
- const { getScheme } = require("../util/URLAbsoluteSpecifier");
- const createHash = require("../util/createHash");
- const { languageOfMediaType } = require("../util/dataURL");
- const { makePathsRelative } = require("../util/identifier");
- const memoize = require("../util/memoize");
- const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
- const {
- PUBLIC_PATH_AUTO,
- PUBLIC_PATH_FULL_HASH
- } = require("../util/publicPathPlaceholder");
- const { updateHashFromSource } = require("../util/source");
- const getMimeTypes = memoize(() => require("../util/mimeTypes"));
- /** @import { Source } from "webpack-sources" */
- /**
- * @import {
- * AssetGeneratorDataUrlOptions,
- * AssetGeneratorOptions,
- * AssetModuleFilename,
- * AssetModuleOutputPath,
- * AssetResourceGeneratorOptions,
- * RawPublicPath
- * } from "../../declarations/WebpackOptions"
- */
- /** @import ChunkGraph from "../ChunkGraph" */
- /** @import Compilation, { AssetInfo } from "../Compilation" */
- /** @import { GenerateContext, UpdateHashContext } from "../Generator" */
- /**
- * @import Module, {
- * NameForCondition,
- * ConcatenationBailoutReasonContext,
- * SourceType,
- * SourceTypes
- * } from "../Module"
- */
- /** @import { AssetModuleBuildInfo } from "./AssetModule" */
- /** @import ModuleGraph from "../ModuleGraph" */
- /** @import NormalModule from "../NormalModule" */
- /** @import RuntimeTemplate from "../RuntimeTemplate" */
- /** @import Hash from "../util/Hash" */
- /** @import { RuntimeSpec } from "../util/runtime" */
- /** @typedef {(source: string | Buffer, context: { filename: string, module: Module }) => string} DataUrlFunction */
- /**
- * Merges maybe arrays.
- * @template T
- * @template U
- * @param {null | string | T[] | Set<T> | undefined} a a
- * @param {null | string | U[] | Set<U> | undefined} b b
- * @returns {T[] & U[]} array
- */
- const mergeMaybeArrays = (a, b) => {
- /** @type {Set<T | U | null | undefined | string | Set<T> | Set<U>>} */
- const set = new Set();
- if (Array.isArray(a)) for (const item of a) set.add(item);
- else set.add(a);
- if (Array.isArray(b)) for (const item of b) set.add(item);
- else set.add(b);
- return /** @type {T[] & U[]} */ ([.../** @type {Set<T | U>} */ (set)]);
- };
- /**
- * Merges the provided values into a single result.
- * @param {AssetInfo} a a
- * @param {AssetInfo} b b
- * @returns {AssetInfo} object
- */
- const mergeAssetInfo = (a, b) => {
- /** @type {AssetInfo} */
- const result = { ...a, ...b };
- for (const key of Object.keys(a)) {
- if (key in b) {
- if (a[key] === b[key]) continue;
- switch (key) {
- case "fullhash":
- case "chunkhash":
- case "modulehash":
- case "contenthash":
- result[key] = mergeMaybeArrays(a[key], b[key]);
- break;
- case "immutable":
- case "development":
- case "hotModuleReplacement":
- case "javascriptModule":
- result[key] = a[key] || b[key];
- break;
- case "related":
- result[key] = mergeRelatedInfo(
- /** @type {NonNullable<AssetInfo["related"]>} */
- (a[key]),
- /** @type {NonNullable<AssetInfo["related"]>} */
- (b[key])
- );
- break;
- default:
- throw new Error(`Can't handle conflicting asset info for ${key}`);
- }
- }
- }
- return result;
- };
- /**
- * Merges related info.
- * @param {NonNullable<AssetInfo["related"]>} a a
- * @param {NonNullable<AssetInfo["related"]>} b b
- * @returns {NonNullable<AssetInfo["related"]>} object
- */
- const mergeRelatedInfo = (a, b) => {
- const result = { ...a, ...b };
- for (const key of Object.keys(a)) {
- if (key in b) {
- if (a[key] === b[key]) continue;
- result[key] = mergeMaybeArrays(a[key], b[key]);
- }
- }
- return result;
- };
- /**
- * Encodes the provided encoding.
- * @param {"base64" | false} encoding encoding
- * @param {Source} source source
- * @returns {string} encoded data
- */
- const encodeDataUri = (encoding, source) => {
- /** @type {string | undefined} */
- let encodedContent;
- switch (encoding) {
- case "base64": {
- encodedContent = source.buffer().toString("base64");
- break;
- }
- case false: {
- const content = source.source();
- encodedContent =
- typeof content === "string" ? content : content.toString("utf8");
- encodedContent = encodeURIComponent(encodedContent).replace(
- /[!'()*]/g,
- (character) =>
- `%${/** @type {number} */ (character.codePointAt(0)).toString(16)}`
- );
- break;
- }
- default:
- throw new Error(`Unsupported encoding '${encoding}'`);
- }
- return encodedContent;
- };
- /**
- * Decodes data uri content.
- * @param {"base64" | false} encoding encoding
- * @param {string} content content
- * @returns {Buffer} decoded content
- */
- const decodeDataUriContent = (encoding, content) => {
- const isBase64 = encoding === "base64";
- if (isBase64) {
- return Buffer.from(content, "base64");
- }
- // If we can't decode return the original body
- try {
- return Buffer.from(decodeURIComponent(content), "utf8");
- } catch (_) {
- return Buffer.from(content, "utf8");
- }
- };
- const DEFAULT_ENCODING = "base64";
- class AssetGenerator extends Generator {
- /**
- * Creates an instance of AssetGenerator.
- * @param {ModuleGraph} moduleGraph the module graph
- * @param {AssetGeneratorOptions["dataUrl"]=} dataUrlOptions the options for the data url
- * @param {AssetModuleFilename=} filename override for output.assetModuleFilename
- * @param {RawPublicPath=} publicPath override for output.assetModulePublicPath
- * @param {AssetModuleOutputPath=} outputPath the output path for the emitted file which is not included in the runtime import
- * @param {boolean=} emit generate output asset
- * @param {Compilation=} compilation the compilation (`getTypes` has no generate context, so the analyzable-output decision reads `output.module`/`output.publicPath` from here)
- */
- constructor(
- moduleGraph,
- dataUrlOptions,
- filename,
- publicPath,
- outputPath,
- emit,
- compilation
- ) {
- super();
- /** @type {AssetGeneratorOptions["dataUrl"] | undefined} */
- this.dataUrlOptions = dataUrlOptions;
- /** @type {AssetModuleFilename | undefined} */
- this.filename = filename;
- /** @type {RawPublicPath | undefined} */
- this.publicPath = publicPath;
- /** @type {AssetModuleOutputPath | undefined} */
- this.outputPath = outputPath;
- /** @type {boolean | undefined} */
- this.emit = emit;
- /** @type {Compilation | undefined} */
- this._compilation = compilation;
- /** @type {ModuleGraph} */
- this._moduleGraph = moduleGraph;
- }
- /**
- * Gets source file name.
- * @param {NormalModule} module module
- * @param {RuntimeTemplate} runtimeTemplate runtime template
- * @returns {string} source file name
- */
- static getSourceFileName(module, runtimeTemplate) {
- return makePathsRelative(
- runtimeTemplate.compilation.compiler.context,
- /** @type {string} */
- (module.getResource()),
- runtimeTemplate.compilation.compiler.root
- ).replace(/^\.\//, "");
- }
- /**
- * Gets the source file name as seen by a filename template. With
- * `experiments.futureDefaults` (webpack 6 default) `[path]` and `[file]` of a
- * module outside of `context` behave like `[containedpath]`/`[containedfile]`,
- * so they can't emit the asset outside of `output.path`.
- * @param {NormalModule} module module
- * @param {RuntimeTemplate} runtimeTemplate runtime template
- * @returns {string} source file name for the filename template
- */
- static getTemplateSourceFileName(module, runtimeTemplate) {
- return toTemplateSourceFileName(
- AssetGenerator.getSourceFileName(module, runtimeTemplate),
- runtimeTemplate.compilation
- );
- }
- /**
- * Gets full content hash.
- * @param {NormalModule} module module
- * @param {RuntimeTemplate} runtimeTemplate runtime template
- * @returns {[string, string]} return full hash and non-numeric full hash
- */
- static getFullContentHash(module, runtimeTemplate) {
- const hash = createHash(runtimeTemplate.outputOptions.hashFunction);
- if (runtimeTemplate.outputOptions.hashSalt) {
- hash.update(runtimeTemplate.outputOptions.hashSalt);
- }
- const source = module.originalSource();
- if (source) {
- updateHashFromSource(hash, source);
- }
- if (module.error) {
- hash.update(module.error.toString());
- }
- const fullContentHash = hash.digest(
- runtimeTemplate.outputOptions.hashDigest
- );
- const contentHash = nonNumericOnlyHash(
- fullContentHash,
- runtimeTemplate.outputOptions.hashDigestLength
- );
- return [fullContentHash, contentHash];
- }
- /**
- * Gets filename with info.
- * @param {NormalModule} module module for which the code should be generated
- * @param {Pick<AssetResourceGeneratorOptions, "filename" | "outputPath">} generatorOptions generator options
- * @param {{ runtime: RuntimeSpec, runtimeTemplate: RuntimeTemplate, chunkGraph: ChunkGraph }} generateContext context for generate
- * @param {string} contentHash the content hash
- * @param {string=} fullContentHash untruncated content hash, so `[contenthash:<digest>]` re-encodes from full entropy
- * @returns {{ filename: string, originalFilename: string, assetInfo: AssetInfo }} info
- */
- static getFilenameWithInfo(
- module,
- generatorOptions,
- { runtime, runtimeTemplate, chunkGraph },
- contentHash,
- fullContentHash
- ) {
- const assetModuleFilename =
- generatorOptions.filename ||
- runtimeTemplate.outputOptions.assetModuleFilename;
- // Both templates decide where the asset is written, so they see the
- // contained path.
- const templateSourceFilename = AssetGenerator.getTemplateSourceFileName(
- module,
- runtimeTemplate
- );
- let { path: filename, info: assetInfo } =
- runtimeTemplate.compilation.getAssetPathWithInfo(assetModuleFilename, {
- module,
- runtime,
- filename: templateSourceFilename,
- chunkGraph,
- contentHash,
- contentHashFull: fullContentHash
- });
- const originalFilename = filename;
- if (generatorOptions.outputPath) {
- const { path: outputPath, info } =
- runtimeTemplate.compilation.getAssetPathWithInfo(
- generatorOptions.outputPath,
- {
- module,
- runtime,
- filename: templateSourceFilename,
- chunkGraph,
- contentHash,
- contentHashFull: fullContentHash
- }
- );
- filename = path.posix.join(outputPath, filename);
- assetInfo = mergeAssetInfo(assetInfo, info);
- }
- return { originalFilename, filename, assetInfo };
- }
- /**
- * Gets asset path with info.
- * @param {NormalModule} module module for which the code should be generated
- * @param {Pick<AssetResourceGeneratorOptions, "publicPath">} generatorOptions generator options
- * @param {GenerateContext} generateContext context for generate
- * @param {string} filename the filename
- * @param {AssetInfo} assetInfo the asset info
- * @param {string} contentHash the content hash
- * @param {string=} fullContentHash untruncated content hash, so `[contenthash:<digest>]` re-encodes from full entropy
- * @returns {{ assetPath: string, assetInfo: AssetInfo }} asset path and info
- */
- static getAssetPathWithInfo(
- module,
- generatorOptions,
- { runtime, runtimeTemplate, type, chunkGraph, runtimeRequirements },
- filename,
- assetInfo,
- contentHash,
- fullContentHash
- ) {
- // A public path is a url prefix, it doesn't decide where the asset is
- // written, so `[path]` stays the real path of the source file here.
- const sourceFilename = AssetGenerator.getSourceFileName(
- module,
- runtimeTemplate
- );
- /** @type {undefined | string} */
- let assetPath;
- if (generatorOptions.publicPath !== undefined && type === JAVASCRIPT_TYPE) {
- const { path, info } = runtimeTemplate.compilation.getAssetPathWithInfo(
- generatorOptions.publicPath,
- {
- module,
- runtime,
- filename: sourceFilename,
- chunkGraph,
- contentHash,
- contentHashFull: fullContentHash
- }
- );
- assetInfo = mergeAssetInfo(assetInfo, info);
- assetPath = JSON.stringify(path + filename);
- } else if (
- generatorOptions.publicPath !== undefined &&
- type === ASSET_URL_TYPE
- ) {
- const { path, info } = runtimeTemplate.compilation.getAssetPathWithInfo(
- generatorOptions.publicPath,
- {
- module,
- runtime,
- filename: sourceFilename,
- chunkGraph,
- contentHash,
- contentHashFull: fullContentHash
- }
- );
- assetInfo = mergeAssetInfo(assetInfo, info);
- assetPath = path + filename;
- } else if (type === JAVASCRIPT_TYPE) {
- // `auto` reads `.p` as this asset's own directory url, which the absolute
- // `href` spells too; a complete url of its own is already the whole string.
- const constant = runtimeTemplate.constantPublicPath();
- const whole =
- constant !== undefined &&
- (constant.startsWith("//") || getScheme(constant) !== undefined);
- const analyzable =
- (runtimeTemplate.outputOptions.publicPath === "auto" || whole) &&
- runtimeTemplate.supportsAnalyzable("url", chunkGraph, module)
- ? runtimeTemplate.getAnalyzableAssetUrl(
- module,
- chunkGraph,
- filename,
- runtime
- )
- : null;
- if (analyzable !== null) {
- assetPath = whole
- ? analyzable
- : `${runtimeTemplate.importMetaUrl(analyzable)}.href`;
- } else {
- // add __webpack_require__.p
- runtimeRequirements.add(RuntimeGlobals.publicPath);
- assetPath = runtimeTemplate.concatenation(
- { expr: RuntimeGlobals.publicPath },
- filename
- );
- }
- } else if (type === ASSET_URL_TYPE) {
- const compilation = runtimeTemplate.compilation;
- const path =
- compilation.outputOptions.publicPath === "auto"
- ? PUBLIC_PATH_AUTO
- : compilation.getAssetPath(compilation.outputOptions.publicPath, {
- hash: compilation.hash || `${PUBLIC_PATH_FULL_HASH}0__`,
- hashWithLength: (length) =>
- compilation.hash
- ? compilation.hash.slice(0, length)
- : `${PUBLIC_PATH_FULL_HASH}${length}__`
- });
- assetPath = path + filename;
- }
- return {
- assetPath: /** @type {string} */ (assetPath),
- assetInfo: { sourceFilename, ...assetInfo }
- };
- }
- /**
- * Returns the reason this module cannot be concatenated, when one exists.
- * @param {NormalModule} module module for which the bailout reason should be determined
- * @param {ConcatenationBailoutReasonContext} context context
- * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
- */
- getConcatenationBailoutReason(module, context) {
- return undefined;
- }
- /**
- * Returns mime type.
- * @param {NormalModule} module module
- * @returns {string} mime type
- */
- getMimeType(module) {
- if (typeof this.dataUrlOptions === "function") {
- throw new Error(
- "This method must not be called when dataUrlOptions is a function"
- );
- }
- /** @type {string | undefined} */
- let mimeType =
- /** @type {AssetGeneratorDataUrlOptions} */
- (this.dataUrlOptions).mimetype;
- if (mimeType === undefined) {
- const ext = path.extname(
- /** @type {NameForCondition} */
- (module.nameForCondition())
- );
- if (
- module.resourceResolveData &&
- module.resourceResolveData.mimetype !== undefined
- ) {
- mimeType =
- module.resourceResolveData.mimetype +
- module.resourceResolveData.parameters;
- } else if (ext) {
- mimeType = getMimeTypes().lookup(ext);
- if (typeof mimeType !== "string") {
- throw new Error(
- "DataUrl can't be generated automatically, " +
- `because there is no mimetype for "${ext}" in mimetype database. ` +
- 'Either pass a mimetype via "generator.mimetype" or ' +
- 'use type: "asset/resource" to create a resource file instead of a DataUrl'
- );
- }
- }
- }
- if (typeof mimeType !== "string") {
- throw new Error(
- "DataUrl can't be generated automatically. " +
- 'Either pass a mimetype via "generator.mimetype" or ' +
- 'use type: "asset/resource" to create a resource file instead of a DataUrl'
- );
- }
- return /** @type {string} */ (mimeType);
- }
- /**
- * Offer the payload to `renderEmbeddedSource` before it is encoded, so the
- * encoding covers what came back. Declines what names no language, and a
- * `dataUrl` function, which is handed the source itself.
- * @param {NormalModule} module the module holding the payload
- * @param {GenerateContext["runtimeTemplate"]} runtimeTemplate runtime template
- * @returns {Source | undefined} the rendered payload, when one came back
- */
- _renderEmbedded(module, runtimeTemplate) {
- if (!runtimeTemplate || typeof this.dataUrlOptions === "function") {
- return undefined;
- }
- // Untapped, nothing can change: no mime lookup and no copy of the payload.
- const { compilation } = runtimeTemplate;
- if (compilation.hooks.renderEmbeddedSource.taps.length === 0) {
- return undefined;
- }
- const type = languageOfMediaType(this.getMimeType(module));
- if (type === undefined) return undefined;
- // An asset's source holds bytes; the hook is offered text, and the language
- // above is only ever one written as text.
- const content = /** @type {Source} */ (module.originalSource()).source();
- return compilation._resolveEmbeddedSource(
- new RawSource(
- typeof content === "string" ? content : content.toString("utf8")
- ),
- { type, hostType: JAVASCRIPT_TYPE, module }
- );
- }
- /**
- * Generates data uri.
- * @param {NormalModule} module module for which the code should be generated
- * @param {Source=} renderedSource the payload to encode, when a hook rendered it
- * @returns {string} DataURI
- */
- generateDataUri(module, renderedSource) {
- const source =
- renderedSource || /** @type {Source} */ (module.originalSource());
- /** @type {string} */
- let encodedSource;
- if (typeof this.dataUrlOptions === "function") {
- encodedSource = this.dataUrlOptions.call(null, source.source(), {
- filename: /** @type {string} */ (module.getResource()),
- module
- });
- } else {
- let encoding =
- /** @type {AssetGeneratorDataUrlOptions} */
- (this.dataUrlOptions).encoding;
- if (
- encoding === undefined &&
- module.resourceResolveData &&
- module.resourceResolveData.encoding !== undefined
- ) {
- encoding = module.resourceResolveData.encoding;
- }
- if (encoding === undefined) {
- encoding = DEFAULT_ENCODING;
- }
- const mimeType = this.getMimeType(module);
- /** @type {string} */
- let encodedContent;
- if (
- module.resourceResolveData &&
- module.resourceResolveData.encoding === encoding &&
- decodeDataUriContent(
- module.resourceResolveData.encoding,
- /** @type {string} */ (module.resourceResolveData.encodedContent)
- ).equals(source.buffer())
- ) {
- encodedContent =
- /** @type {string} */
- (module.resourceResolveData.encodedContent);
- } else {
- encodedContent = encodeDataUri(
- /** @type {"base64" | false} */ (encoding),
- source
- );
- }
- encodedSource = `data:${mimeType}${
- encoding ? `;${encoding}` : ""
- },${encodedContent}`;
- }
- return encodedSource;
- }
- /**
- * Generates generated code for this runtime module.
- * @param {NormalModule} module module for which the code should be generated
- * @param {GenerateContext} generateContext context for generate
- * @returns {Source | null} generated code
- */
- generate(module, generateContext) {
- const {
- type,
- getData,
- runtimeTemplate,
- runtimeRequirements,
- concatenationScope
- } = generateContext;
- /** @type {string} */
- let content;
- const needContent = type === JAVASCRIPT_TYPE || type === ASSET_URL_TYPE;
- const data = getData ? getData() : undefined;
- if (
- /** @type {AssetModuleBuildInfo} */
- (module.buildInfo).dataUrl &&
- needContent
- ) {
- // A payload bound for CSS or HTML is offered by their serializers, which
- // see the `url()` it ends up in; only the JavaScript one is ours.
- const encodedSource = this.generateDataUri(
- module,
- type === JAVASCRIPT_TYPE
- ? this._renderEmbedded(module, runtimeTemplate)
- : undefined
- );
- content =
- type === JAVASCRIPT_TYPE
- ? JSON.stringify(encodedSource)
- : encodedSource;
- if (data) {
- data.set("url", { ...data.get("url"), [type]: content });
- }
- } else {
- const [fullContentHash, contentHash] = AssetGenerator.getFullContentHash(
- module,
- runtimeTemplate
- );
- if (data) {
- data.set("fullContentHash", fullContentHash);
- data.set("contentHash", contentHash);
- }
- /** @type {AssetModuleBuildInfo} */
- (module.buildInfo).fullContentHash = fullContentHash;
- const { originalFilename, filename, assetInfo } =
- AssetGenerator.getFilenameWithInfo(
- module,
- { filename: this.filename, outputPath: this.outputPath },
- generateContext,
- contentHash,
- fullContentHash
- );
- if (data) {
- data.set("filename", filename);
- }
- let { assetPath, assetInfo: newAssetInfo } =
- AssetGenerator.getAssetPathWithInfo(
- module,
- { publicPath: this.publicPath },
- generateContext,
- originalFilename,
- assetInfo,
- contentHash,
- fullContentHash
- );
- if (data && (type === JAVASCRIPT_TYPE || type === ASSET_URL_TYPE)) {
- data.set("url", { ...data.get("url"), [type]: assetPath });
- }
- if (data) {
- const oldAssetInfo = data.get("assetInfo");
- if (oldAssetInfo) {
- newAssetInfo = mergeAssetInfo(oldAssetInfo, newAssetInfo);
- }
- }
- if (data) {
- data.set("assetInfo", newAssetInfo);
- }
- // Due to code generation caching module.buildInfo.XXX can't used to store such information
- // It need to be stored in the code generation results instead, where it's cached too
- // TODO webpack 6 For back-compat reasons we also store in on module.buildInfo
- /** @type {AssetModuleBuildInfo} */
- (module.buildInfo).filename = filename;
- /** @type {AssetModuleBuildInfo} */
- (module.buildInfo).assetInfo = newAssetInfo;
- content = assetPath;
- }
- if (type === JAVASCRIPT_TYPE) {
- if (concatenationScope) {
- concatenationScope.registerNamespaceExport(
- ConcatenationScope.NAMESPACE_OBJECT_EXPORT
- );
- return new RawSource(
- `${runtimeTemplate.renderConst()} ${
- ConcatenationScope.NAMESPACE_OBJECT_EXPORT
- } = ${content};`
- );
- }
- runtimeRequirements.add(RuntimeGlobals.module);
- return new RawSource(`${module.moduleArgument}.exports = ${content};`);
- } else if (type === ASSET_URL_TYPE) {
- return null;
- }
- return /** @type {Source} */ (module.originalSource());
- }
- /**
- * Generates fallback output for the provided error condition.
- * @param {Error} error the error
- * @param {NormalModule} module module for which the code should be generated
- * @param {GenerateContext} generateContext context for generate
- * @returns {Source | null} generated code
- */
- generateError(error, module, generateContext) {
- switch (generateContext.type) {
- case "asset": {
- return new RawSource(error.message);
- }
- case JAVASCRIPT_TYPE: {
- return new RawSource(Generator.throwBuildErrorCode(error));
- }
- default:
- return null;
- }
- }
- /**
- * Returns whether the effective publicPath yields a chunk-independent absolute
- * url (scheme-relative, protocol-absolute, or root-absolute) with no hash tokens.
- * Only then is a `new URL` literal correct for every chunk the asset is shared by,
- * so the `module.exports` wrapper can be dropped in favor of the `asset-url` type.
- * @returns {boolean} true when the publicPath is a token-free absolute string
- */
- _hasAbsolutePublicPath() {
- const publicPath =
- this.publicPath !== undefined
- ? this.publicPath
- : this._compilation && this._compilation.outputOptions.publicPath;
- return (
- typeof publicPath === "string" &&
- !publicPath.includes("[") &&
- (publicPath.startsWith("/") ||
- /^[a-z][a-z\d+\-.]*:\/\//i.test(publicPath))
- );
- }
- /**
- * Returns the source types available for this module.
- * @param {NormalModule} module fresh module
- * @returns {SourceTypes} available types (do not mutate)
- */
- getTypes(module) {
- const connections = this._moduleGraph.getIncomingConnections(module);
- // One that emits nothing has no name to build a literal from, so expose its url.
- const emits =
- !(
- module.buildInfo &&
- /** @type {AssetModuleBuildInfo} */ (module.buildInfo).dataUrl
- ) && this.emit !== false;
- // Only analyzable ESM output can drop a bare `new URL()` to an asset-url; without
- // it every JS consumer needs the wrapper, so skip the per-connection dependency
- // probe entirely and keep the pre-existing cheap path. Must match the condition
- // `URLDependency` uses to emit the literal, or the wrapper is dropped while the
- // call site still requires the module — except where the fallback spells the name
- // itself (`.p + <file>`), which needs no wrapper to read it out of.
- // Build-time execution instead emulates the wrapper (`AssetModulesPlugin`).
- const isModule = Boolean(
- this._compilation &&
- this._compilation.runtimeTemplate.supportsAnalyzable(
- emits ? "url-inline" : "url"
- )
- );
- // Collapse the incoming origin types into flags instead of a Set of prefixes:
- // on assets shared by many modules this loop runs millions of times, so avoid
- // the per-connection `split("/")` allocation and stop early when the result is fixed.
- let hasOrigin = false;
- // JS origin that needs the `module.exports = …` wrapper (not a `new URL` ref).
- let hasOtherJs = false;
- // JS origin via `new URL(…, import.meta.url)` — droppable to `asset-url` in ESM.
- let hasUrlJs = false;
- // A manifest embeds a bare URL string (like css/html), so its icons must
- // resolve to `ASSET_URL_TYPE` rather than the JS runtime form.
- let hasUrl = false;
- for (const connection of connections) {
- const originModule = connection.originModule;
- if (!originModule) {
- continue;
- }
- hasOrigin = true;
- // The dependency says how the asset is read, so a reference added later answers
- // for itself; concatenation re-points the origin module, so it cannot.
- const dependency = connection.dependency;
- if (dependency && dependency.referencedSourceType === ASSET_URL_TYPE) {
- hasUrl = true;
- } else if (
- // Only a bare `new URL()` becomes the analyzable literal; a relative ref
- // keeps the runtime form and still needs the wrapper. Prefetch/preload
- // refs don't: their `<link>` is emitted at chunk startup, which resolves
- // the href from the asset filename when there is no wrapper.
- isModule &&
- dependency instanceof URLDependency &&
- !dependency.relative
- ) {
- hasUrlJs = true;
- } else {
- hasOtherJs = true;
- }
- // Once a non-URL JS consumer and a url consumer coexist the result is fixed.
- if (hasOtherJs && hasUrl) break;
- }
- // Every javascript consumer names the file itself, so nothing reads the wrapper.
- // Only an absolute public path settles one url for all of them to share.
- const jsWrapperUnused = isModule && hasUrlJs && !hasOtherJs;
- const jsAsAssetUrl =
- jsWrapperUnused && (!emits || this._hasAbsolutePublicPath());
- // JS wrapper is needed for non-URL JS consumers, or URL consumers still reading it.
- const wantJs = hasOtherJs || (hasUrlJs && !jsWrapperUnused);
- const wantUrl = hasUrl || jsAsAssetUrl;
- if (!emits) {
- if (hasOrigin) {
- if (wantJs && wantUrl) {
- return JAVASCRIPT_AND_ASSET_URL_TYPES;
- } else if (wantUrl) {
- return ASSET_URL_TYPES;
- }
- return JAVASCRIPT_TYPES;
- }
- return NO_TYPES;
- }
- if (hasOrigin) {
- if (wantJs && wantUrl) {
- return ASSET_AND_JAVASCRIPT_AND_ASSET_URL_TYPES;
- } else if (wantUrl) {
- return ASSET_AND_ASSET_URL_TYPES;
- } else if (jsWrapperUnused) {
- // Every javascript consumer names the file itself, so nothing reads it.
- return ASSET_TYPES;
- }
- return ASSET_AND_JAVASCRIPT_TYPES;
- }
- return ASSET_TYPES;
- }
- /**
- * @returns {boolean} whether getTypes() depends on the module's incoming connections
- */
- getTypesDependOnIncomingConnections() {
- return true;
- }
- /**
- * Returns the estimated size for the requested source type.
- * @param {NormalModule} module the module
- * @param {SourceType=} type source type
- * @returns {number} estimate size of the module
- */
- getSize(module, type) {
- switch (type) {
- case ASSET_MODULE_TYPE: {
- const originalSource = module.originalSource();
- if (!originalSource) {
- return 0;
- }
- return originalSource.size();
- }
- default:
- if (
- module.buildInfo &&
- /** @type {AssetModuleBuildInfo} */ (module.buildInfo).dataUrl
- ) {
- const originalSource = module.originalSource();
- if (!originalSource) {
- return 0;
- }
- // roughly for data url
- // Example: m.exports="data:image/png;base64,ag82/f+2=="
- // 4/3 = base64 encoding
- // 34 = ~ data url header + footer + rounding
- return originalSource.size() * 1.34 + 36;
- }
- // it's only estimated so this number is probably fine
- // Example: m.exports=r.p+"0123456789012345678901.ext"
- return 42;
- }
- }
- /**
- * Updates the hash with the data contributed by this instance.
- * @param {Hash} hash hash that will be modified
- * @param {UpdateHashContext} updateHashContext context for updating hash
- */
- updateHash(hash, updateHashContext) {
- const { module } = updateHashContext;
- if (
- /** @type {AssetModuleBuildInfo} */
- (module.buildInfo).dataUrl
- ) {
- hash.update("data-url");
- // What a tap varies on has to reach the key, or a changed option replays.
- const { runtimeTemplate } = updateHashContext;
- if (
- runtimeTemplate &&
- runtimeTemplate.compilation.hooks.renderEmbeddedSource.taps.length > 0
- ) {
- runtimeTemplate.compilation.hooks.embeddedSourceHash.call(module, hash);
- }
- // this.dataUrlOptions as function should be pure and only depend on input source and filename
- // therefore it doesn't need to be hashed
- if (typeof this.dataUrlOptions === "function") {
- const ident = /** @type {{ ident?: string }} */ (this.dataUrlOptions)
- .ident;
- if (ident) hash.update(ident);
- } else {
- const dataUrlOptions =
- /** @type {AssetGeneratorDataUrlOptions} */
- (this.dataUrlOptions);
- if (
- dataUrlOptions.encoding &&
- dataUrlOptions.encoding !== DEFAULT_ENCODING
- ) {
- hash.update(dataUrlOptions.encoding);
- }
- if (dataUrlOptions.mimetype) hash.update(dataUrlOptions.mimetype);
- // computed mimetype depends only on module filename which is already part of the hash
- }
- } else {
- hash.update("resource");
- const { module, chunkGraph, runtime } = updateHashContext;
- const runtimeTemplate =
- /** @type {NonNullable<UpdateHashContext["runtimeTemplate"]>} */
- (updateHashContext.runtimeTemplate);
- const pathData = {
- module,
- runtime,
- filename: AssetGenerator.getSourceFileName(module, runtimeTemplate),
- chunkGraph,
- contentHash: runtimeTemplate.contentHashReplacement
- };
- if (typeof this.publicPath === "function") {
- hash.update("path");
- const assetInfo = {};
- hash.update(this.publicPath(pathData, assetInfo));
- hash.update(JSON.stringify(assetInfo));
- } else if (this.publicPath) {
- hash.update("path");
- hash.update(this.publicPath);
- } else {
- hash.update("no-path");
- }
- const assetModuleFilename =
- this.filename || runtimeTemplate.outputOptions.assetModuleFilename;
- // Hash what generation emits: the filename template sees the contained
- // path, unlike the public path above.
- const { path: filename, info } =
- runtimeTemplate.compilation.getAssetPathWithInfo(assetModuleFilename, {
- module,
- runtime,
- filename: AssetGenerator.getTemplateSourceFileName(
- module,
- runtimeTemplate
- ),
- chunkGraph,
- contentHash: runtimeTemplate.contentHashReplacement
- });
- hash.update(filename);
- hash.update(JSON.stringify(info));
- }
- }
- }
- module.exports = AssetGenerator;
|