mirror of https://github.com/webpack/webpack.git
fix: use a valid output path for errored asset modules (#19281)
This commit is contained in:
parent
05f2862b32
commit
98221f239b
|
@ -32,7 +32,9 @@ const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
|
|||
/** @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("../Compilation").InterpolatedPathAndAssetInfo} InterpolatedPathAndAssetInfo */
|
||||
|
@ -50,6 +52,7 @@ const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
|
|||
/** @typedef {import("../TemplatedPathPlugin").TemplatePath} TemplatePath */
|
||||
/** @typedef {import("../util/Hash")} Hash */
|
||||
/** @typedef {import("../util/createHash").Algorithm} Algorithm */
|
||||
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
|
||||
|
||||
/**
|
||||
* @template T
|
||||
|
@ -212,7 +215,7 @@ class AssetGenerator extends Generator {
|
|||
* @param {RuntimeTemplate} runtimeTemplate runtime template
|
||||
* @returns {string} source file name
|
||||
*/
|
||||
getSourceFileName(module, runtimeTemplate) {
|
||||
static getSourceFileName(module, runtimeTemplate) {
|
||||
return makePathsRelative(
|
||||
runtimeTemplate.compilation.compiler.context,
|
||||
module.matchResource || module.resource,
|
||||
|
@ -220,6 +223,176 @@ class AssetGenerator extends Generator {
|
|||
).replace(/^\.\//, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(
|
||||
/** @type {Algorithm} */
|
||||
(runtimeTemplate.outputOptions.hashFunction)
|
||||
);
|
||||
|
||||
if (runtimeTemplate.outputOptions.hashSalt) {
|
||||
hash.update(runtimeTemplate.outputOptions.hashSalt);
|
||||
}
|
||||
|
||||
const source = module.originalSource();
|
||||
|
||||
if (source) {
|
||||
hash.update(source.buffer());
|
||||
}
|
||||
|
||||
const fullContentHash = /** @type {string} */ (
|
||||
hash.digest(runtimeTemplate.outputOptions.hashDigest)
|
||||
);
|
||||
|
||||
/** @type {string} */
|
||||
const contentHash = nonNumericOnlyHash(
|
||||
fullContentHash,
|
||||
/** @type {number} */
|
||||
(runtimeTemplate.outputOptions.hashDigestLength)
|
||||
);
|
||||
|
||||
return [fullContentHash, contentHash];
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @returns {{ filename: string, originalFilename: string, assetInfo: AssetInfo }} info
|
||||
*/
|
||||
static getFilenameWithInfo(
|
||||
module,
|
||||
generatorOptions,
|
||||
{ runtime, runtimeTemplate, chunkGraph },
|
||||
contentHash
|
||||
) {
|
||||
const assetModuleFilename =
|
||||
generatorOptions.filename ||
|
||||
/** @type {AssetModuleFilename} */
|
||||
(runtimeTemplate.outputOptions.assetModuleFilename);
|
||||
|
||||
const sourceFilename = AssetGenerator.getSourceFileName(
|
||||
module,
|
||||
runtimeTemplate
|
||||
);
|
||||
let { path: filename, info: assetInfo } =
|
||||
runtimeTemplate.compilation.getAssetPathWithInfo(assetModuleFilename, {
|
||||
module,
|
||||
runtime,
|
||||
filename: sourceFilename,
|
||||
chunkGraph,
|
||||
contentHash
|
||||
});
|
||||
|
||||
const originalFilename = filename;
|
||||
|
||||
if (generatorOptions.outputPath) {
|
||||
const { path: outputPath, info } =
|
||||
runtimeTemplate.compilation.getAssetPathWithInfo(
|
||||
generatorOptions.outputPath,
|
||||
{
|
||||
module,
|
||||
runtime,
|
||||
filename: sourceFilename,
|
||||
chunkGraph,
|
||||
contentHash
|
||||
}
|
||||
);
|
||||
filename = path.posix.join(outputPath, filename);
|
||||
assetInfo = mergeAssetInfo(assetInfo, info);
|
||||
}
|
||||
|
||||
return { originalFilename, filename, assetInfo };
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @returns {{ assetPath: string, assetInfo: AssetInfo }} asset path and info
|
||||
*/
|
||||
static getAssetPathWithInfo(
|
||||
module,
|
||||
generatorOptions,
|
||||
{ runtime, runtimeTemplate, type, chunkGraph, runtimeRequirements },
|
||||
filename,
|
||||
assetInfo,
|
||||
contentHash
|
||||
) {
|
||||
const sourceFilename = AssetGenerator.getSourceFileName(
|
||||
module,
|
||||
runtimeTemplate
|
||||
);
|
||||
|
||||
let assetPath;
|
||||
|
||||
if (generatorOptions.publicPath !== undefined && type === "javascript") {
|
||||
const { path, info } = runtimeTemplate.compilation.getAssetPathWithInfo(
|
||||
generatorOptions.publicPath,
|
||||
{
|
||||
module,
|
||||
runtime,
|
||||
filename: sourceFilename,
|
||||
chunkGraph,
|
||||
contentHash
|
||||
}
|
||||
);
|
||||
assetInfo = mergeAssetInfo(assetInfo, info);
|
||||
assetPath = JSON.stringify(path + filename);
|
||||
} else if (
|
||||
generatorOptions.publicPath !== undefined &&
|
||||
type === "css-url"
|
||||
) {
|
||||
const { path, info } = runtimeTemplate.compilation.getAssetPathWithInfo(
|
||||
generatorOptions.publicPath,
|
||||
{
|
||||
module,
|
||||
runtime,
|
||||
filename: sourceFilename,
|
||||
chunkGraph,
|
||||
contentHash
|
||||
}
|
||||
);
|
||||
assetInfo = mergeAssetInfo(assetInfo, info);
|
||||
assetPath = path + filename;
|
||||
} else if (type === "javascript") {
|
||||
// add __webpack_require__.p
|
||||
runtimeRequirements.add(RuntimeGlobals.publicPath);
|
||||
assetPath = runtimeTemplate.concatenation(
|
||||
{ expr: RuntimeGlobals.publicPath },
|
||||
filename
|
||||
);
|
||||
} else if (type === "css-url") {
|
||||
const compilation = runtimeTemplate.compilation;
|
||||
const path =
|
||||
compilation.outputOptions.publicPath === "auto"
|
||||
? CssUrlDependency.PUBLIC_PATH_AUTO
|
||||
: compilation.getAssetPath(
|
||||
/** @type {TemplatePath} */
|
||||
(compilation.outputOptions.publicPath),
|
||||
{
|
||||
hash: compilation.hash
|
||||
}
|
||||
);
|
||||
|
||||
assetPath = path + filename;
|
||||
}
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line object-shorthand
|
||||
assetPath: /** @type {string} */ (assetPath),
|
||||
assetInfo: { sourceFilename, ...assetInfo }
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {NormalModule} module module for which the bailout reason should be determined
|
||||
* @param {ConcatenationBailoutReasonContext} context context
|
||||
|
@ -335,127 +508,6 @@ class AssetGenerator extends Generator {
|
|||
return encodedSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {NormalModule} module module for which the code should be generated
|
||||
* @param {GenerateContext} generateContext context for generate
|
||||
* @param {string} contentHash the content hash
|
||||
* @returns {{ filename: string, originalFilename: string, assetInfo: AssetInfo }} info
|
||||
*/
|
||||
_getFilenameWithInfo(
|
||||
module,
|
||||
{ runtime, runtimeTemplate, chunkGraph },
|
||||
contentHash
|
||||
) {
|
||||
const assetModuleFilename =
|
||||
this.filename ||
|
||||
/** @type {AssetModuleFilename} */
|
||||
(runtimeTemplate.outputOptions.assetModuleFilename);
|
||||
|
||||
const sourceFilename = this.getSourceFileName(module, runtimeTemplate);
|
||||
let { path: filename, info: assetInfo } =
|
||||
runtimeTemplate.compilation.getAssetPathWithInfo(assetModuleFilename, {
|
||||
module,
|
||||
runtime,
|
||||
filename: sourceFilename,
|
||||
chunkGraph,
|
||||
contentHash
|
||||
});
|
||||
|
||||
const originalFilename = filename;
|
||||
|
||||
if (this.outputPath) {
|
||||
const { path: outputPath, info } =
|
||||
runtimeTemplate.compilation.getAssetPathWithInfo(this.outputPath, {
|
||||
module,
|
||||
runtime,
|
||||
filename: sourceFilename,
|
||||
chunkGraph,
|
||||
contentHash
|
||||
});
|
||||
filename = path.posix.join(outputPath, filename);
|
||||
assetInfo = mergeAssetInfo(assetInfo, info);
|
||||
}
|
||||
|
||||
return { originalFilename, filename, assetInfo };
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {NormalModule} module module for which the code should be generated
|
||||
* @param {GenerateContext} generateContext context for generate
|
||||
* @param {string} filename the filename
|
||||
* @param {AssetInfo} assetInfo the asset info
|
||||
* @param {string} contentHash the content hash
|
||||
* @returns {{ assetPath: string, assetInfo: AssetInfo }} asset path and info
|
||||
*/
|
||||
_getAssetPathWithInfo(
|
||||
module,
|
||||
{ runtimeTemplate, runtime, chunkGraph, type, runtimeRequirements },
|
||||
filename,
|
||||
assetInfo,
|
||||
contentHash
|
||||
) {
|
||||
const sourceFilename = this.getSourceFileName(module, runtimeTemplate);
|
||||
|
||||
let assetPath;
|
||||
|
||||
if (this.publicPath !== undefined && type === "javascript") {
|
||||
const { path, info } = runtimeTemplate.compilation.getAssetPathWithInfo(
|
||||
this.publicPath,
|
||||
{
|
||||
module,
|
||||
runtime,
|
||||
filename: sourceFilename,
|
||||
chunkGraph,
|
||||
contentHash
|
||||
}
|
||||
);
|
||||
assetInfo = mergeAssetInfo(assetInfo, info);
|
||||
assetPath = JSON.stringify(path + filename);
|
||||
} else if (this.publicPath !== undefined && type === "css-url") {
|
||||
const { path, info } = runtimeTemplate.compilation.getAssetPathWithInfo(
|
||||
this.publicPath,
|
||||
{
|
||||
module,
|
||||
runtime,
|
||||
filename: sourceFilename,
|
||||
chunkGraph,
|
||||
contentHash
|
||||
}
|
||||
);
|
||||
assetInfo = mergeAssetInfo(assetInfo, info);
|
||||
assetPath = path + filename;
|
||||
} else if (type === "javascript") {
|
||||
// add __webpack_require__.p
|
||||
runtimeRequirements.add(RuntimeGlobals.publicPath);
|
||||
assetPath = runtimeTemplate.concatenation(
|
||||
{ expr: RuntimeGlobals.publicPath },
|
||||
filename
|
||||
);
|
||||
} else if (type === "css-url") {
|
||||
const compilation = runtimeTemplate.compilation;
|
||||
const path =
|
||||
compilation.outputOptions.publicPath === "auto"
|
||||
? CssUrlDependency.PUBLIC_PATH_AUTO
|
||||
: compilation.getAssetPath(
|
||||
/** @type {TemplatePath} */
|
||||
(compilation.outputOptions.publicPath),
|
||||
{
|
||||
hash: compilation.hash
|
||||
}
|
||||
);
|
||||
|
||||
assetPath = path + filename;
|
||||
}
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line object-shorthand
|
||||
assetPath: /** @type {string} */ (assetPath),
|
||||
assetInfo: { sourceFilename, ...assetInfo }
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {NormalModule} module module for which the code should be generated
|
||||
* @param {GenerateContext} generateContext context for generate
|
||||
|
@ -489,53 +541,40 @@ class AssetGenerator extends Generator {
|
|||
data.set("url", { [type]: content, ...data.get("url") });
|
||||
}
|
||||
} else {
|
||||
const hash = createHash(
|
||||
/** @type {Algorithm} */
|
||||
(runtimeTemplate.outputOptions.hashFunction)
|
||||
);
|
||||
|
||||
if (runtimeTemplate.outputOptions.hashSalt) {
|
||||
hash.update(runtimeTemplate.outputOptions.hashSalt);
|
||||
}
|
||||
|
||||
hash.update(/** @type {Source} */ (module.originalSource()).buffer());
|
||||
|
||||
const fullHash =
|
||||
/** @type {string} */
|
||||
(hash.digest(runtimeTemplate.outputOptions.hashDigest));
|
||||
|
||||
if (data) {
|
||||
data.set("fullContentHash", fullHash);
|
||||
}
|
||||
|
||||
/** @type {BuildInfo} */
|
||||
(module.buildInfo).fullContentHash = fullHash;
|
||||
|
||||
/** @type {string} */
|
||||
const contentHash = nonNumericOnlyHash(
|
||||
fullHash,
|
||||
/** @type {number} */
|
||||
(generateContext.runtimeTemplate.outputOptions.hashDigestLength)
|
||||
const [fullContentHash, contentHash] = AssetGenerator.getFullContentHash(
|
||||
module,
|
||||
runtimeTemplate
|
||||
);
|
||||
|
||||
if (data) {
|
||||
data.set("fullContentHash", fullContentHash);
|
||||
data.set("contentHash", contentHash);
|
||||
}
|
||||
|
||||
/** @type {BuildInfo} */
|
||||
(module.buildInfo).fullContentHash = fullContentHash;
|
||||
|
||||
const { originalFilename, filename, assetInfo } =
|
||||
this._getFilenameWithInfo(module, generateContext, contentHash);
|
||||
AssetGenerator.getFilenameWithInfo(
|
||||
module,
|
||||
{ filename: this.filename, outputPath: this.outputPath },
|
||||
generateContext,
|
||||
contentHash
|
||||
);
|
||||
|
||||
if (data) {
|
||||
data.set("filename", filename);
|
||||
}
|
||||
|
||||
let { assetPath, assetInfo: newAssetInfo } = this._getAssetPathWithInfo(
|
||||
module,
|
||||
generateContext,
|
||||
originalFilename,
|
||||
assetInfo,
|
||||
contentHash
|
||||
);
|
||||
let { assetPath, assetInfo: newAssetInfo } =
|
||||
AssetGenerator.getAssetPathWithInfo(
|
||||
module,
|
||||
{ publicPath: this.publicPath },
|
||||
generateContext,
|
||||
originalFilename,
|
||||
assetInfo,
|
||||
contentHash
|
||||
);
|
||||
|
||||
if (data && (type === "javascript" || type === "css-url")) {
|
||||
data.set("url", { [type]: assetPath, ...data.get("url") });
|
||||
|
@ -704,7 +743,7 @@ class AssetGenerator extends Generator {
|
|||
const pathData = {
|
||||
module,
|
||||
runtime,
|
||||
filename: this.getSourceFileName(module, runtimeTemplate),
|
||||
filename: AssetGenerator.getSourceFileName(module, runtimeTemplate),
|
||||
chunkGraph,
|
||||
contentHash: runtimeTemplate.contentHashReplacement
|
||||
};
|
||||
|
|
|
@ -19,10 +19,12 @@ const memoize = require("../util/memoize");
|
|||
/** @typedef {import("webpack-sources").Source} Source */
|
||||
/** @typedef {import("../../declarations/WebpackOptions").AssetParserOptions} AssetParserOptions */
|
||||
/** @typedef {import("../Chunk")} Chunk */
|
||||
/** @typedef {import("../Compilation").AssetInfo} AssetInfo */
|
||||
/** @typedef {import("../Compiler")} Compiler */
|
||||
/** @typedef {import("../Module")} Module */
|
||||
/** @typedef {import("../Module").BuildInfo} BuildInfo */
|
||||
/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */
|
||||
/** @typedef {import("../NormalModule")} NormalModule */
|
||||
|
||||
/**
|
||||
* @param {string} name name of definitions
|
||||
|
@ -184,7 +186,7 @@ class AssetModulesPlugin {
|
|||
|
||||
compilation.hooks.renderManifest.tap(plugin, (result, options) => {
|
||||
const { chunkGraph } = compilation;
|
||||
const { chunk, codeGenerationResults } = options;
|
||||
const { chunk, codeGenerationResults, runtimeTemplate } = options;
|
||||
|
||||
const modules = chunkGraph.getOrderedChunkModulesIterableBySourceType(
|
||||
chunk,
|
||||
|
@ -203,18 +205,58 @@ class AssetModulesPlugin {
|
|||
/** @type {NonNullable<CodeGenerationResult["data"]>} */
|
||||
(codeGenResult.data);
|
||||
const errored = module.getNumberOfErrors() > 0;
|
||||
|
||||
/** @type {string} */
|
||||
let entryFilename;
|
||||
/** @type {AssetInfo} */
|
||||
let entryInfo;
|
||||
/** @type {string} */
|
||||
let entryHash;
|
||||
|
||||
if (errored) {
|
||||
const erroredModule = /** @type {NormalModule} */ (module);
|
||||
const AssetGenerator = getAssetGenerator();
|
||||
const [fullContentHash, contentHash] =
|
||||
AssetGenerator.getFullContentHash(
|
||||
erroredModule,
|
||||
runtimeTemplate
|
||||
);
|
||||
const { filename, assetInfo } =
|
||||
AssetGenerator.getFilenameWithInfo(
|
||||
erroredModule,
|
||||
{
|
||||
filename:
|
||||
erroredModule.generatorOptions &&
|
||||
erroredModule.generatorOptions.filename,
|
||||
outputPath:
|
||||
erroredModule.generatorOptions &&
|
||||
erroredModule.generatorOptions.outputPath
|
||||
},
|
||||
{
|
||||
runtime: chunk.runtime,
|
||||
runtimeTemplate,
|
||||
chunkGraph
|
||||
},
|
||||
contentHash
|
||||
);
|
||||
entryFilename = filename;
|
||||
entryInfo = assetInfo;
|
||||
entryHash = fullContentHash;
|
||||
} else {
|
||||
entryFilename = buildInfo.filename || data.get("filename");
|
||||
entryInfo = buildInfo.assetInfo || data.get("assetInfo");
|
||||
entryHash =
|
||||
buildInfo.fullContentHash || data.get("fullContentHash");
|
||||
}
|
||||
|
||||
result.push({
|
||||
render: () =>
|
||||
/** @type {Source} */ (codeGenResult.sources.get(type)),
|
||||
filename: errored
|
||||
? module.nameForCondition()
|
||||
: buildInfo.filename || data.get("filename"),
|
||||
info: buildInfo.assetInfo || data.get("assetInfo"),
|
||||
filename: entryFilename,
|
||||
info: entryInfo,
|
||||
auxiliary: true,
|
||||
identifier: `assetModule${chunkGraph.getModuleId(module)}`,
|
||||
hash: errored
|
||||
? chunkGraph.getModuleHash(module, chunk.runtime)
|
||||
: buildInfo.fullContentHash || data.get("fullContentHash")
|
||||
hash: entryHash
|
||||
});
|
||||
} catch (err) {
|
||||
/** @type {Error} */ (err).message +=
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
module.exports = [/Error from loader/];
|
|
@ -0,0 +1,7 @@
|
|||
it("should use a valid output path", () => {
|
||||
try {
|
||||
new URL("./style.css", import.meta.url);
|
||||
} catch (e) {
|
||||
// Nothing
|
||||
}
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
module.exports = options => {
|
||||
if (options.cache && options.cache.type === "filesystem") {
|
||||
return [/Pack got invalid because of write to/];
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = function loader() {
|
||||
throw new Error("Error from loader");
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
a {
|
||||
color: red;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
afterExecute(options) {
|
||||
const files = fs.readdirSync(path.resolve(options.output.path, "./css"));
|
||||
|
||||
if (!/style\.[0-9a-f]{8}\.css/.test(files[0])) {
|
||||
throw new Error(`Invalid path for ${files.join(",")} files.`);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
/** @type {import("../../../../").Configuration} */
|
||||
module.exports = {
|
||||
mode: "development",
|
||||
output: {
|
||||
hashDigestLength: 8
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/i,
|
||||
type: "asset/resource",
|
||||
generator: {
|
||||
filename: () => `css/style.[contenthash].css`
|
||||
},
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve("./loader")
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue