Source code
Revision control
Copy as Markdown
Other Tools
/*
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,
CSS_TYPE,
HTML_TYPE,
JAVASCRIPT_AND_ASSET_URL_TYPES,
JAVASCRIPT_TYPE,
JAVASCRIPT_TYPES,
NO_TYPES
} = require("../ModuleSourceTypeConstants");
const {
ASSET_MODULE_TYPE,
ASSET_MODULE_TYPE_WEBMANIFEST
} = require("../ModuleTypeConstants");
const RuntimeGlobals = require("../RuntimeGlobals");
const createHash = require("../util/createHash");
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"));
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../../declarations/WebpackOptions").AssetGeneratorDataUrlOptions} AssetGeneratorDataUrlOptions */
/** @typedef {import("../../declarations/WebpackOptions").AssetGeneratorOptions} AssetGeneratorOptions */
/** @typedef {import("../../declarations/WebpackOptions").AssetModuleFilename} AssetModuleFilename */
/** @typedef {import("../../declarations/WebpackOptions").AssetModuleOutputPath} AssetModuleOutputPath */
/** @typedef {import("../../declarations/WebpackOptions").AssetResourceGeneratorOptions} AssetResourceGeneratorOptions */
/** @typedef {import("../../declarations/WebpackOptions").RawPublicPath} RawPublicPath */
/** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../Compilation").AssetInfo} AssetInfo */
/** @typedef {import("../Generator").GenerateContext} GenerateContext */
/** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../Module").NameForCondition} NameForCondition */
/** @typedef {import("./AssetModule").AssetModuleBuildInfo} AssetModuleBuildInfo */
/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
/** @typedef {import("../Module").SourceType} SourceType */
/** @typedef {import("../Module").SourceTypes} SourceTypes */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
/** @typedef {import("../NormalModule")} NormalModule */
/** @typedef {import("../dependencies/URLDependency")} URLDependency */
/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
/** @typedef {import("../util/Hash")} Hash */
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
/** @typedef {(source: string | Buffer, context: { filename: string, module: Module }) => string} DataUrlFunction */
// slash separating a module type from its subtype (`css/auto` -> `css`)
const TYPE_SEPARATOR_CHAR_CODE = 47;
/**
* Whether a module type's prefix (the part before `/`) equals `prefix`, without
* allocating the substring `type.split("/")[0]` would produce.
* @param {string} type module type (e.g. `javascript/auto`)
* @param {string} prefix expected prefix (e.g. `javascript`)
* @returns {boolean} whether the type's prefix equals `prefix`
*/
const typePrefixEquals = (type, prefix) =>
type.startsWith(prefix) &&
(type.length === prefix.length ||
type.charCodeAt(prefix.length) === TYPE_SEPARATOR_CHAR_CODE);
/**
* 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 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;
const sourceFilename = AssetGenerator.getSourceFileName(
module,
runtimeTemplate
);
let { path: filename, info: assetInfo } =
runtimeTemplate.compilation.getAssetPathWithInfo(assetModuleFilename, {
module,
runtime,
filename: sourceFilename,
chunkGraph,
contentHash,
contentHashFull: fullContentHash
});
const originalFilename = filename;
if (generatorOptions.outputPath) {
const { path: outputPath, info } =
runtimeTemplate.compilation.getAssetPathWithInfo(
generatorOptions.outputPath,
{
module,
runtime,
filename: sourceFilename,
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
) {
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) {
// 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);
}
/**
* Generates data uri.
* @param {NormalModule} module module for which the code should be generated
* @returns {string} DataURI
*/
generateDataUri(module) {
const source = /** @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
) {
const encodedSource = this.generateDataUri(module);
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(
`throw new Error(${JSON.stringify(error.message)});`
);
}
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);
// Only ESM output can drop a bare `new URL()` to an analyzable 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.
const isModule = Boolean(
this._compilation && this._compilation.outputOptions.module
);
// 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;
const originType = originModule.type;
if (originType === ASSET_MODULE_TYPE_WEBMANIFEST) {
hasUrl = true;
} else if (typePrefixEquals(originType, JAVASCRIPT_TYPE)) {
// Only a bare `new URL()` becomes the analyzable literal; relative and
// prefetch/preload refs keep the runtime form and still need the wrapper.
const dependency = isModule && connection.dependency;
if (
dependency &&
dependency.type === "new URL()" &&
!(/** @type {URLDependency} */ (dependency).relative) &&
!(/** @type {URLDependency} */ (dependency).prefetch) &&
!(/** @type {URLDependency} */ (dependency).preload)
) {
hasUrlJs = true;
} else {
hasOtherJs = true;
}
} else if (
typePrefixEquals(originType, CSS_TYPE) ||
typePrefixEquals(originType, HTML_TYPE)
) {
hasUrl = true;
}
// Once a non-URL JS consumer and a url consumer coexist the result is fixed.
if (hasOtherJs && hasUrl) break;
}
const jsAsAssetUrl =
isModule && hasUrlJs && !hasOtherJs && this._hasAbsolutePublicPath();
// JS wrapper is needed for non-URL JS consumers, or URL consumers not dropped to asset-url.
const wantJs = hasOtherJs || (hasUrlJs && !jsAsAssetUrl);
const wantUrl = hasUrl || jsAsAssetUrl;
if (
(module.buildInfo &&
/** @type {AssetModuleBuildInfo} */ (module.buildInfo).dataUrl) ||
this.emit === false
) {
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;
}
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");
// 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;
const { path: filename, info } =
runtimeTemplate.compilation.getAssetPathWithInfo(
assetModuleFilename,
pathData
);
hash.update(filename);
hash.update(JSON.stringify(info));
}
}
}
module.exports = AssetGenerator;