fix asset module hash

This commit is contained in:
Ivan Kopeykin 2021-11-29 15:37:38 +03:00
parent f1e9221da6
commit 8d8e2e3fa4
11 changed files with 270 additions and 42 deletions

View File

@ -36,6 +36,7 @@
* @property {NormalModule} module the module
* @property {ChunkGraph} chunkGraph
* @property {RuntimeSpec} runtime
* @property {RuntimeTemplate=} runtimeTemplate
*/
/**

View File

@ -82,6 +82,7 @@ class RuntimeTemplate {
this.outputOptions = outputOptions || {};
this.requestShortener = requestShortener;
this.globalObject = getGlobalObject(outputOptions.globalObject);
this.contentHashReplacement = "X".repeat(outputOptions.hashDigestLength);
}
isIIFE() {

View File

@ -76,6 +76,7 @@ const mergeRelatedInfo = (a, b) => {
const JS_TYPES = new Set(["javascript"]);
const JS_AND_ASSET_TYPES = new Set(["javascript", "asset"]);
const DEFAULT_ENCODING = "base64";
class AssetGenerator extends Generator {
/**
@ -92,6 +93,44 @@ class AssetGenerator extends Generator {
this.emit = emit;
}
/**
* @param {NormalModule} module module
* @param {RuntimeTemplate} runtimeTemplate runtime template
* @returns {string} source file name
*/
getSourceFileName(module, runtimeTemplate) {
return makePathsRelative(
runtimeTemplate.compilation.compiler.context,
module.matchResource || module.resource,
runtimeTemplate.compilation.compiler.root
).replace(/^\.\//, "");
}
/**
* @param {NormalModule} module module
* @returns {string|undefined} mime type
*/
getMimeType(module) {
if (typeof this.dataUrlOptions === "function") return;
let mimeType = this.dataUrlOptions.mimetype;
if (mimeType === undefined) {
let ext = path.extname(module.nameForCondition());
if (
module.resourceResolveData &&
module.resourceResolveData.mimetype !== undefined
) {
mimeType =
module.resourceResolveData.mimetype +
module.resourceResolveData.parameters;
} else if (ext) {
mimeType = mimeTypes.lookup(ext);
}
}
return mimeType;
}
/**
* @param {NormalModule} module module for which the code should be generated
* @param {GenerateContext} generateContext context for generate
@ -131,27 +170,15 @@ class AssetGenerator extends Generator {
}
}
if (encoding === undefined) {
encoding = "base64";
}
let ext;
let mimeType = this.dataUrlOptions.mimetype;
if (mimeType === undefined) {
ext = path.extname(module.nameForCondition());
if (
module.resourceResolveData &&
module.resourceResolveData.mimetype !== undefined
) {
mimeType =
module.resourceResolveData.mimetype +
module.resourceResolveData.parameters;
} else if (ext) {
mimeType = mimeTypes.lookup(ext);
}
encoding = DEFAULT_ENCODING;
}
const mimeType = this.getMimeType(module);
if (typeof mimeType !== "string") {
throw new Error(
"DataUrl can't be generated automatically, " +
`because there is no mimetype for "${ext}" in mimetype database. ` +
`because there is no mimetype for "${path.extname(
module.nameForCondition()
)}" in mimetype database. ` +
'Either pass a mimetype via "generator.mimetype" or ' +
'use type: "asset/resource" to create a resource file instead of a DataUrl'
);
@ -212,11 +239,10 @@ class AssetGenerator extends Generator {
runtimeTemplate.outputOptions.hashDigestLength
);
module.buildInfo.fullContentHash = fullHash;
const sourceFilename = makePathsRelative(
runtimeTemplate.compilation.compiler.context,
module.matchResource || module.resource,
runtimeTemplate.compilation.compiler.root
).replace(/^\.\//, "");
const sourceFilename = this.getSourceFileName(
module,
runtimeTemplate
);
let { path: filename, info: assetInfo } =
runtimeTemplate.compilation.getAssetPathWithInfo(
assetModuleFilename,
@ -327,8 +353,44 @@ class AssetGenerator extends Generator {
* @param {Hash} hash hash that will be modified
* @param {UpdateHashContext} updateHashContext context for updating hash
*/
updateHash(hash, { module }) {
hash.update(module.buildInfo.dataUrl ? "data-url" : "resource");
updateHash(hash, { module, runtime, runtimeTemplate, chunkGraph }) {
if (module.buildInfo.dataUrl) {
hash.update("data-url");
if (typeof this.dataUrlOptions === "function") {
hash.update("unknown-encoding");
hash.update("unknown-mimetype");
} else {
hash.update(this.dataUrlOptions.encoding || DEFAULT_ENCODING);
hash.update(this.getMimeType(module) || "unknown-mimetype");
}
} else {
hash.update("resource");
const pathData = {
module,
runtime,
filename: this.getSourceFileName(module, runtimeTemplate),
chunkGraph,
contentHash: runtimeTemplate.contentHashReplacement
};
if (typeof this.publicPath === "function") {
hash.update(`path:${this.publicPath(pathData, {})}`);
} else if (this.publicPath) {
hash.update(`path:${this.publicPath}`);
} else {
hash.update("no-path");
}
const assetModuleFilename =
this.filename || runtimeTemplate.outputOptions.assetModuleFilename;
const { path: filename } =
runtimeTemplate.compilation.getAssetPathWithInfo(
assetModuleFilename,
pathData
);
hash.update(filename);
}
}
}

View File

@ -2493,7 +2493,7 @@ exports[`StatsTestCases should print correct stats for real-content-hash 1`] = `
b-normal:
assets by path *.js 3.23 KiB
asset 415ca114e6dbdbc5f84c-415ca1.js 2.75 KiB [emitted] [immutable] [minimized] (name: runtime)
asset e1e88dc9cfc1b47c4954-e1e88d.js 2.75 KiB [emitted] [immutable] [minimized] (name: runtime)
asset a6d438a0676f93383d79-a6d438.js 262 bytes [emitted] [immutable] [minimized] (name: lazy)
asset cbb9c74e42f00ada40f7-cbb9c7.js 212 bytes [emitted] [immutable] [minimized] (name: index)
asset 666f2b8847021ccc7608-666f2b.js 21 bytes [emitted] [immutable] [minimized] (name: a, b)
@ -2501,7 +2501,7 @@ b-normal:
asset 89a353e9c515885abd8e.png 14.6 KiB [emitted] [immutable] [from: file.png] (auxiliary name: lazy)
asset 7382fad5b015914e0811.jpg?query 5.89 KiB [cached] [immutable] [from: file.jpg?query] (auxiliary name: lazy)
asset 7382fad5b015914e0811.jpg 5.89 KiB [emitted] [immutable] [from: file.jpg] (auxiliary name: index)
Entrypoint index 2.96 KiB (5.89 KiB) = 415ca114e6dbdbc5f84c-415ca1.js 2.75 KiB cbb9c74e42f00ada40f7-cbb9c7.js 212 bytes 1 auxiliary asset
Entrypoint index 2.96 KiB (5.89 KiB) = e1e88dc9cfc1b47c4954-e1e88d.js 2.75 KiB cbb9c74e42f00ada40f7-cbb9c7.js 212 bytes 1 auxiliary asset
Entrypoint a 21 bytes = 666f2b8847021ccc7608-666f2b.js
Entrypoint b 21 bytes = 666f2b8847021ccc7608-666f2b.js
runtime modules 7.29 KiB 9 modules
@ -2520,8 +2520,8 @@ b-normal:
a-source-map:
assets by path *.js 3.45 KiB
asset d618b31bb631bdbaee8e-d618b3.js 2.81 KiB [emitted] [immutable] [minimized] (name: runtime)
sourceMap d618b31bb631bdbaee8e-d618b3.js.map 14.5 KiB [emitted] [dev] (auxiliary name: runtime)
asset 3d558d1fe89c5b37fabe-3d558d.js 2.81 KiB [emitted] [immutable] [minimized] (name: runtime)
sourceMap 3d558d1fe89c5b37fabe-3d558d.js.map 14.5 KiB [emitted] [dev] (auxiliary name: runtime)
asset da6ceedb86c86e79a49a-da6cee.js 318 bytes [emitted] [immutable] [minimized] (name: lazy)
sourceMap da6ceedb86c86e79a49a-da6cee.js.map 401 bytes [emitted] [dev] (auxiliary name: lazy)
asset 9e0ae6ff74fb2c3c821b-9e0ae6.js 268 bytes [emitted] [immutable] [minimized] (name: index)
@ -2532,7 +2532,7 @@ a-source-map:
asset 89a353e9c515885abd8e.png 14.6 KiB [emitted] [immutable] [from: file.png] (auxiliary name: lazy)
asset 7382fad5b015914e0811.jpg?query 5.89 KiB [cached] [immutable] [from: file.jpg?query] (auxiliary name: lazy)
asset 7382fad5b015914e0811.jpg 5.89 KiB [emitted] [immutable] [from: file.jpg] (auxiliary name: index)
Entrypoint index 3.07 KiB (20.7 KiB) = d618b31bb631bdbaee8e-d618b3.js 2.81 KiB 9e0ae6ff74fb2c3c821b-9e0ae6.js 268 bytes 3 auxiliary assets
Entrypoint index 3.07 KiB (20.7 KiB) = 3d558d1fe89c5b37fabe-3d558d.js 2.81 KiB 9e0ae6ff74fb2c3c821b-9e0ae6.js 268 bytes 3 auxiliary assets
Entrypoint a 77 bytes (254 bytes) = 222c2acc68675174e6b2-222c2a.js 1 auxiliary asset
Entrypoint b 77 bytes (254 bytes) = 222c2acc68675174e6b2-222c2a.js 1 auxiliary asset
runtime modules 7.29 KiB 9 modules

View File

@ -0,0 +1,5 @@
import img from "./1.jpg";
it("should compile", () => {
expect(typeof img).toBe("string");
});

View File

@ -0,0 +1,31 @@
const findOutputFiles = require("../../../helpers/findOutputFiles");
const allAssets = new Set();
const allBundles = new Set();
module.exports = {
findBundle: function(i, options) {
const bundle = findOutputFiles(options, new RegExp(`^bundle${i}`))[0];
allBundles.add(/\.([^.]+)\./.exec(bundle)[1]);
let asset;
switch (i) {
case 0:
asset = findOutputFiles(options, /^1\.[^\.]*\.jpg$/, 'img')[0];
break;
case 1:
case 5:
asset = findOutputFiles(options, /^1\.[^\.]*\.jpg$/, 'asset')[0];
break;
}
if (asset) allAssets.add(asset);
return `./${bundle}`;
},
afterExecute: () => {
expect(allBundles.size).toBe(6);
expect(allAssets.size).toBe(1);
}
};

View File

@ -0,0 +1,118 @@
/** @type {import("../../../../").Configuration[]} */
module.exports = [
{
output: {
filename: "bundle0.[contenthash].js",
assetModuleFilename: "img/[name].[contenthash][ext]"
},
module: {
rules: [
{
test: /\.jpg$/,
type: "asset/resource"
}
]
}
},
{
output: {
filename: "bundle1.[contenthash].js",
assetModuleFilename: "asset/[name].[contenthash][ext]"
},
module: {
rules: [
{
test: /\.jpg$/,
type: "asset/resource"
}
]
}
},
{
output: {
filename: "bundle2.[contenthash].js",
assetModuleFilename: "asset/[name].[contenthash][ext]"
},
module: {
rules: [
{
test: /\.jpg$/,
type: "asset/resource",
generator: {
publicPath: "/public/"
}
}
]
}
},
{
output: {
filename: "bundle3.[contenthash].js",
assetModuleFilename: "asset/[name].[contenthash][ext]"
},
module: {
rules: [
{
test: /\.jpg$/,
type: "asset/inline"
}
]
}
},
{
output: {
filename: "bundle4.[contenthash].js",
assetModuleFilename: "asset/[name].[contenthash][ext]"
},
module: {
rules: [
{
test: /\.jpg$/,
type: "asset/inline",
generator: {
dataUrl: {
encoding: false
}
}
}
]
}
},
{
output: {
filename: "bundle5.[contenthash].js",
assetModuleFilename: "asset/[name].[contenthash][ext]"
},
module: {
rules: [
{
test: /\.jpg$/,
type: "asset/source",
generator: {
dataUrl: {
mimetype: "text/plain"
}
}
}
]
}
},
{
output: {
filename: "bundle6.[contenthash].js",
assetModuleFilename: "asset/[name].[contenthash][ext]"
},
module: {
rules: [
{
test: /\.jpg$/,
type: "asset/resource",
generator: {
// should result in same hash as bundle2
publicPath: () => "/public/"
}
}
]
}
}
];

View File

@ -1,25 +1,15 @@
var fs = require("fs");
var findFile = function(files, regex) {
return files.find(function(file) {
if (regex.test(file)) {
return true;
}
});
};
const findOutputFiles = require("../../../helpers/findOutputFiles");
const allFilenameHashes = new Set();
const allChunkHashes = new Set();
module.exports = {
findBundle: function(i, options) {
var files = fs.readdirSync(options.output.path);
const filename = findFile(files, new RegExp(`^bundle${i}`));
const filename = findOutputFiles(options, new RegExp(`^bundle${i}`))[0];
const filenameHash = /\.([a-f0-9]+)\.js$/.exec(filename)[1];
allFilenameHashes.add(filenameHash);
const chunk = findFile(files, new RegExp(`^chunk${i}`));
const chunk = findOutputFiles(options, new RegExp(`^chunk${i}`))[0];
const chunkHash = /\.([a-f0-9]+)\.js$/.exec(chunk)[1];
allChunkHashes.add(chunkHash);

View File

@ -0,0 +1,18 @@
"use strict";
const fs = require("fs");
const path = require("path");
/**
* @param {{output: {path: string}}} options options
* @param {RegExp} regexp regexp
* @param {string=} subpath path in output directory
* @returns {string[]} files
*/
module.exports = function findOutputFiles(options, regexp, subpath) {
const files = fs.readdirSync(
subpath ? path.join(options.output.path, subpath) : options.output.path
);
return files.filter(file => regexp.test(file));
};

2
types.d.ts vendored
View File

@ -10023,6 +10023,7 @@ declare abstract class RuntimeTemplate {
outputOptions: OutputNormalized;
requestShortener: RequestShortener;
globalObject: string;
contentHashReplacement: string;
isIIFE(): undefined | boolean;
isModule(): undefined | boolean;
supportsConst(): undefined | boolean;
@ -11506,6 +11507,7 @@ declare interface UpdateHashContextGenerator {
module: NormalModule;
chunkGraph: ChunkGraph;
runtime: RuntimeSpec;
runtimeTemplate?: RuntimeTemplate;
}
type UsageStateType = 0 | 1 | 2 | 3 | 4;
declare interface UserResolveOptions {