Merge pull request #11191 from webpack/bugfix/asset-hash

use content hashes for assets
This commit is contained in:
Tobias Koppers 2020-07-20 10:18:54 +02:00 committed by GitHub
commit 4786a7ec61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 131 additions and 53 deletions

View File

@ -2710,12 +2710,16 @@ Make sure to select an appropriate stage from Compilation.PROCESS_ASSETS_STAGE_*
let filenameTemplate;
/** @type {string} */
let file;
/** @type {AssetInfo} */
let assetInfo;
let inTry = true;
const errorAndCallback = err => {
const filename =
file ||
(typeof filenameTemplate === "string"
(typeof file === "string"
? file
: typeof filenameTemplate === "string"
? filenameTemplate
: "");
@ -2725,13 +2729,18 @@ Make sure to select an appropriate stage from Compilation.PROCESS_ASSETS_STAGE_*
};
try {
filenameTemplate = fileManifest.filenameTemplate;
const pathAndInfo = this.getPathWithInfo(
filenameTemplate,
fileManifest.pathOptions
);
file = pathAndInfo.path;
const assetInfo = pathAndInfo.info;
if ("filename" in fileManifest) {
file = fileManifest.filename;
assetInfo = fileManifest.info;
} else {
filenameTemplate = fileManifest.filenameTemplate;
const pathAndInfo = this.getPathWithInfo(
filenameTemplate,
fileManifest.pathOptions
);
file = pathAndInfo.path;
assetInfo = pathAndInfo.info;
}
if (err) {
return errorAndCallback(err);

View File

@ -28,6 +28,7 @@ const { find } = require("./util/SetHelpers");
const { compareModulesById } = require("./util/comparators");
/** @typedef {import("./Chunk")} Chunk */
/** @typedef {import("./Compilation").AssetInfo} AssetInfo */
/** @typedef {import("./Compiler")} Compiler */
/** @typedef {import("./Module")} Module */
@ -454,13 +455,22 @@ class HotModuleReplacementPlugin {
chunkGraph
});
for (const entry of renderManifest) {
const {
path: filename,
info: assetInfo
} = compilation.getPathWithInfo(
entry.filenameTemplate,
entry.pathOptions
);
/** @type {string} */
let filename;
/** @type {AssetInfo} */
let assetInfo;
if ("filename" in entry) {
filename = entry.filename;
assetInfo = entry.info;
} else {
({
path: filename,
info: assetInfo
} = compilation.getPathWithInfo(
entry.filenameTemplate,
entry.pathOptions
));
}
const source = entry.render();
compilation.additionalChunkAssets.push(filename);
compilation.emitAsset(filename, source, {

View File

@ -53,8 +53,10 @@ const MATCH_PADDED_HYPHENS_REPLACE_REGEX = /^-|-$/g;
* @property {ChunkGraph} chunkGraph
*/
/** @typedef {RenderManifestEntryTemplated | RenderManifestEntryStatic} RenderManifestEntry */
/**
* @typedef {Object} RenderManifestEntry
* @typedef {Object} RenderManifestEntryTemplated
* @property {function(): Source} render
* @property {string | function(PathData, AssetInfo=): string} filenameTemplate
* @property {PathData=} pathOptions
@ -63,6 +65,16 @@ const MATCH_PADDED_HYPHENS_REPLACE_REGEX = /^-|-$/g;
* @property {boolean=} auxiliary
*/
/**
* @typedef {Object} RenderManifestEntryStatic
* @property {function(): Source} render
* @property {string} filename
* @property {AssetInfo} info
* @property {string} identifier
* @property {string=} hash
* @property {boolean=} auxiliary
*/
/**
* @typedef {Object} HasId
* @property {number | string} id

View File

@ -225,7 +225,7 @@ const replacePathVariables = (path, data, assetInfo) => {
module instanceof Module ? chunkGraph.getModuleId(module) : module.id
)
);
const hashReplacer = hashLength(
const moduleHashReplacer = hashLength(
replacer(
module instanceof Module
? chunkGraph.getRenderedModuleHash(module)
@ -234,9 +234,19 @@ const replacePathVariables = (path, data, assetInfo) => {
"hashWithLength" in module ? module.hashWithLength : undefined,
assetInfo
);
const contentHashReplacer = hashLength(
replacer(data.contentHash),
undefined,
assetInfo
);
replacements.set("id", idReplacer);
replacements.set("hash", hashReplacer);
replacements.set("modulehash", moduleHashReplacer);
replacements.set("contenthash", contentHashReplacer);
replacements.set(
"hash",
data.contentHash ? contentHashReplacer : moduleHashReplacer
);
// Legacy
replacements.set(
"moduleid",
@ -246,14 +256,6 @@ const replacePathVariables = (path, data, assetInfo) => {
"DEP_WEBPACK_TEMPLATE_PATH_PLUGIN_REPLACE_PATH_VARIABLES_MODULE_ID"
)
);
replacements.set(
"modulehash",
deprecated(
hashReplacer,
"[modulehash] is now [hash]",
"DEP_WEBPACK_TEMPLATE_PATH_PLUGIN_REPLACE_PATH_VARIABLES_MODULE_HASH"
)
);
}
// Other things

View File

@ -10,6 +10,7 @@ const path = require("path");
const { RawSource } = require("webpack-sources");
const Generator = require("../Generator");
const RuntimeGlobals = require("../RuntimeGlobals");
const createHash = require("../util/createHash");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../../declarations/plugins/AssetModulesPluginGenerator").AssetModulesPluginGeneratorOptions} AssetModulesPluginGeneratorOptions */
@ -51,9 +52,8 @@ class AssetGenerator extends Generator {
default: {
runtimeRequirements.add(RuntimeGlobals.module);
const originalSource = module.originalSource();
if (module.buildInfo.dataUrl) {
const originalSource = module.originalSource();
let encodedSource;
if (typeof this.dataUrlOptions === "function") {
encodedSource = this.dataUrlOptions.call(
@ -108,21 +108,39 @@ class AssetGenerator extends Generator {
)};`
);
} else {
const filename = module.nameForCondition();
const assetModuleFilename =
this.filename || runtimeTemplate.outputOptions.assetModuleFilename;
const url = this.compilation.getAssetPath(assetModuleFilename, {
const hash = createHash(runtimeTemplate.outputOptions.hashFunction);
if (runtimeTemplate.outputOptions.hashSalt) {
hash.update(runtimeTemplate.outputOptions.hashSalt);
}
hash.update(originalSource.buffer());
const fullHash = /** @type {string} */ (hash.digest(
runtimeTemplate.outputOptions.hashDigest
));
const contentHash = fullHash.slice(
0,
runtimeTemplate.outputOptions.hashDigestLength
);
module.buildInfo.fullContentHash = fullHash;
const {
path: filename,
info
} = this.compilation.getAssetPathWithInfo(assetModuleFilename, {
module,
filename,
chunkGraph
filename: module.nameForCondition(),
chunkGraph,
contentHash
});
module.buildInfo.filename = filename;
module.buildInfo.assetInfo = info;
runtimeRequirements.add(RuntimeGlobals.publicPath); // add __webpack_require__.p
return new RawSource(
`${RuntimeGlobals.module}.exports = ${
RuntimeGlobals.publicPath
} + ${JSON.stringify(url)};`
} + ${JSON.stringify(filename)};`
);
}
}

View File

@ -148,9 +148,7 @@ class AssetModulesPlugin {
compilation.hooks.renderManifest.tap(plugin, (result, options) => {
const { chunkGraph } = compilation;
const { chunk, runtimeTemplate, codeGenerationResults } = options;
const { outputOptions } = runtimeTemplate;
const { chunk, codeGenerationResults } = options;
const modules = chunkGraph.getOrderedChunkModulesIterableBySourceType(
chunk,
@ -159,23 +157,14 @@ class AssetModulesPlugin {
);
if (modules) {
for (const module of modules) {
const filename = module.nameForCondition();
const filenameTemplate =
module.buildInfo.assetFilename ||
outputOptions.assetModuleFilename;
result.push({
render: () =>
codeGenerationResults.get(module).sources.get(type),
filenameTemplate,
pathOptions: {
module,
filename,
chunkGraph
},
filename: module.buildInfo.filename,
info: module.buildInfo.assetInfo,
auxiliary: true,
identifier: `assetModule${chunkGraph.getModuleId(module)}`,
hash: chunkGraph.getModuleHash(module)
hash: module.buildInfo.fullContentHash
});
}
}

View File

@ -135,10 +135,10 @@ exports[`StatsTestCases should print correct stats for asset 1`] = `
Time: X ms
Built at: 1970-04-20 12:42:42
Asset Size
89a353e9c515885abd8e.png 14.6 KiB [emitted] [immutable] [name: (main)]
bundle.js 10.9 KiB [emitted] [name: main]
d896afc62bcc5d86ee78.png 14.6 KiB [emitted] [immutable] [name: (main)]
static/file.html 12 bytes [emitted] [name: (main)]
Entrypoint main = bundle.js (d896afc62bcc5d86ee78.png static/file.html)
Entrypoint main = bundle.js (89a353e9c515885abd8e.png static/file.html)
./index.js 150 bytes [built]
./images/file.png 42 bytes (javascript) 14.6 KiB (asset) [built]
./images/file.svg 915 bytes [built]

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,6 @@
import a from "../_images/file.png";
import b from "../_images/file_copy.png";
it("should use a real content hash for assets", () => {
expect(a).toBe(b);
});

View File

@ -0,0 +1,19 @@
/** @type {import("../../../../").Configuration} */
module.exports = {
mode: "development",
output: {
publicPath: "assets/",
assetModuleFilename: "file[ext]"
},
module: {
rules: [
{
test: /\.png$/,
type: "asset/resource"
}
]
},
experiments: {
asset: true
}
};

19
types.d.ts vendored
View File

@ -1006,7 +1006,10 @@ declare class Compilation {
needAdditionalSeal: SyncBailHook<[], boolean>;
afterSeal: AsyncSeriesHook<[]>;
renderManifest: SyncWaterfallHook<
[RenderManifestEntry[], RenderManifestOptions]
[
(RenderManifestEntryTemplated | RenderManifestEntryStatic)[],
RenderManifestOptions
]
>;
fullHash: SyncHook<[Hash], void>;
chunkHash: SyncHook<[Chunk, Hash, ChunkHashContext], void>;
@ -1203,7 +1206,9 @@ declare class Compilation {
getAsset(name: string): Readonly<Asset>;
clearAssets(): void;
createModuleAssets(): void;
getRenderManifest(options: RenderManifestOptions): RenderManifestEntry[];
getRenderManifest(
options: RenderManifestOptions
): (RenderManifestEntryTemplated | RenderManifestEntryStatic)[];
createChunkAssets(callback: (err?: WebpackError) => void): void;
getPath(
filename: string | ((arg0: PathData, arg1: AssetInfo) => string),
@ -5849,7 +5854,15 @@ declare interface RenderContextObject {
*/
codeGenerationResults: Map<Module, CodeGenerationResult>;
}
declare interface RenderManifestEntry {
declare interface RenderManifestEntryStatic {
render: () => Source;
filename: string;
info: AssetInfo;
identifier: string;
hash?: string;
auxiliary?: boolean;
}
declare interface RenderManifestEntryTemplated {
render: () => Source;
filenameTemplate: string | ((arg0: PathData, arg1: AssetInfo) => string);
pathOptions?: PathData;