diff --git a/declarations/WebpackOptions.d.ts b/declarations/WebpackOptions.d.ts index 9bcbf5741..041cf5c0b 100644 --- a/declarations/WebpackOptions.d.ts +++ b/declarations/WebpackOptions.d.ts @@ -681,7 +681,7 @@ export type AssetGeneratorDataUrl = | AssetGeneratorDataUrlOptions | AssetGeneratorDataUrlFunction; /** - * Function that executes for module and should return an DataUrl string. + * Function that executes for module and should return an DataUrl string. It can have a string as 'ident' property which contributes to the module hash. */ export type AssetGeneratorDataUrlFunction = ( source: string | Buffer, diff --git a/lib/Compilation.js b/lib/Compilation.js index 066b8c1f2..21b9d992e 100644 --- a/lib/Compilation.js +++ b/lib/Compilation.js @@ -43,6 +43,7 @@ const Module = require("./Module"); const ModuleDependencyError = require("./ModuleDependencyError"); const ModuleDependencyWarning = require("./ModuleDependencyWarning"); const ModuleGraph = require("./ModuleGraph"); +const ModuleHashingError = require("./ModuleHashingError"); const ModuleNotFoundError = require("./ModuleNotFoundError"); const ModuleProfile = require("./ModuleProfile"); const ModuleRestoreError = require("./ModuleRestoreError"); @@ -3883,6 +3884,7 @@ Or do you want to use the entrypoints '${name}' and '${runtime}' independently o let statModulesFromCache = 0; const { chunkGraph, runtimeTemplate, moduleMemCaches2 } = this; const { hashFunction, hashDigest, hashDigestLength } = this.outputOptions; + const errors = []; for (const module of this.modules) { const memCache = moduleMemCaches2 && moduleMemCaches2.get(module); for (const runtime of chunkGraph.getModuleRuntimes(module)) { @@ -3907,13 +3909,20 @@ Or do you want to use the entrypoints '${name}' and '${runtime}' independently o hashFunction, runtimeTemplate, hashDigest, - hashDigestLength + hashDigestLength, + errors ); if (memCache) { memCache.set(`moduleHash-${getRuntimeKey(runtime)}`, digest); } } } + if (errors.length > 0) { + errors.sort(compareSelect(err => err.module, compareModulesByIdentifier)); + for (const error of errors) { + this.errors.push(error); + } + } this.logger.log( `${statModulesHashed} modules hashed, ${statModulesFromCache} from cache (${ Math.round( @@ -3930,17 +3939,22 @@ Or do you want to use the entrypoints '${name}' and '${runtime}' independently o hashFunction, runtimeTemplate, hashDigest, - hashDigestLength + hashDigestLength, + errors ) { - const moduleHash = createHash(hashFunction); - module.updateHash(moduleHash, { - chunkGraph, - runtime, - runtimeTemplate - }); - const moduleHashDigest = /** @type {string} */ ( - moduleHash.digest(hashDigest) - ); + let moduleHashDigest; + try { + const moduleHash = createHash(hashFunction); + module.updateHash(moduleHash, { + chunkGraph, + runtime, + runtimeTemplate + }); + moduleHashDigest = /** @type {string} */ (moduleHash.digest(hashDigest)); + } catch (err) { + errors.push(new ModuleHashingError(module, err)); + moduleHashDigest = "XXXXXX"; + } chunkGraph.setModuleHashes( module, runtime, @@ -4091,6 +4105,7 @@ This prevents using hashes of each other and should be avoided.`); const codeGenerationJobs = []; /** @type {Map>} */ const codeGenerationJobsMap = new Map(); + const errors = []; const processChunk = chunk => { // Last minute module hash generation for modules that depend on chunk hashes @@ -4105,7 +4120,8 @@ This prevents using hashes of each other and should be avoided.`); hashFunction, runtimeTemplate, hashDigest, - hashDigestLength + hashDigestLength, + errors ); let hashMap = codeGenerationJobsMap.get(hash); if (hashMap) { @@ -4128,10 +4144,10 @@ This prevents using hashes of each other and should be avoided.`); codeGenerationJobs.push(job); } } - this.logger.timeAggregate("hashing: hash runtime modules"); - this.logger.time("hashing: hash chunks"); - const chunkHash = createHash(hashFunction); try { + this.logger.timeAggregate("hashing: hash runtime modules"); + this.logger.time("hashing: hash chunks"); + const chunkHash = createHash(hashFunction); if (outputOptions.hashSalt) { chunkHash.update(outputOptions.hashSalt); } @@ -4162,6 +4178,12 @@ This prevents using hashes of each other and should be avoided.`); }; otherChunks.forEach(processChunk); for (const chunk of runtimeChunks) processChunk(chunk); + if (errors.length > 0) { + errors.sort(compareSelect(err => err.module, compareModulesByIdentifier)); + for (const error of errors) { + this.errors.push(error); + } + } this.logger.timeAggregateEnd("hashing: hash runtime modules"); this.logger.timeAggregateEnd("hashing: hash chunks"); @@ -4801,6 +4823,9 @@ This prevents using hashes of each other and should be avoided.`); chunkGraph.connectChunkAndModule(chunk, module); } + /** @type {WebpackError[]} */ + const errors = []; + // Hash modules for (const module of modules) { this._createModuleHash( @@ -4810,15 +4835,14 @@ This prevents using hashes of each other and should be avoided.`); hashFunction, runtimeTemplate, hashDigest, - hashDigestLength + hashDigestLength, + errors ); } const codeGenerationResults = new CodeGenerationResults( this.outputOptions.hashFunction ); - /** @type {WebpackError[]} */ - const errors = []; /** * @param {Module} module the module * @param {Callback} callback callback diff --git a/lib/ModuleHashingError.js b/lib/ModuleHashingError.js new file mode 100644 index 000000000..77c8f415a --- /dev/null +++ b/lib/ModuleHashingError.js @@ -0,0 +1,29 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const WebpackError = require("./WebpackError"); + +/** @typedef {import("./Module")} Module */ + +class ModuleHashingError extends WebpackError { + /** + * Create a new ModuleHashingError + * @param {Module} module related module + * @param {Error} error Original error + */ + constructor(module, error) { + super(); + + this.name = "ModuleHashingError"; + this.error = error; + this.message = error.message; + this.details = error.stack; + this.module = module; + } +} + +module.exports = ModuleHashingError; diff --git a/lib/asset/AssetGenerator.js b/lib/asset/AssetGenerator.js index 1c22d4ecc..1dbff38fe 100644 --- a/lib/asset/AssetGenerator.js +++ b/lib/asset/AssetGenerator.js @@ -147,14 +147,18 @@ class AssetGenerator extends Generator { /** * @param {NormalModule} module module - * @returns {string|undefined} mime type + * @returns {string} mime type */ getMimeType(module) { - if (typeof this.dataUrlOptions === "function") return; + if (typeof this.dataUrlOptions === "function") { + throw new Error( + "This method must not be called when dataUrlOptions is a function" + ); + } let mimeType = this.dataUrlOptions.mimetype; if (mimeType === undefined) { - let ext = path.extname(module.nameForCondition()); + const ext = path.extname(module.nameForCondition()); if ( module.resourceResolveData && module.resourceResolveData.mimetype !== undefined @@ -164,9 +168,26 @@ class AssetGenerator extends Generator { module.resourceResolveData.parameters; } else if (ext) { mimeType = mimeTypes.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 mimeType; } @@ -212,16 +233,6 @@ class AssetGenerator extends Generator { 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 "${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' - ); - } let encodedContent; @@ -397,12 +408,18 @@ class AssetGenerator extends Generator { updateHash(hash, { module, runtime, runtimeTemplate, chunkGraph }) { if (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") { - hash.update("unknown-encoding"); - hash.update("unknown-mimetype"); + const ident = /** @type {{ ident?: string }} */ (this.dataUrlOptions) + .ident; + if (ident) hash.update(ident); } else { - hash.update(this.dataUrlOptions.encoding || DEFAULT_ENCODING); - hash.update(this.getMimeType(module) || "unknown-mimetype"); + if (this.dataUrlOptions.encoding) + hash.update(this.dataUrlOptions.encoding); + if (this.dataUrlOptions.mimetype) + hash.update(this.dataUrlOptions.mimetype); + // computed mimetype depends only on module filename which is already part of the hash } } else { hash.update("resource"); @@ -416,21 +433,26 @@ class AssetGenerator extends Generator { }; if (typeof this.publicPath === "function") { - hash.update(`path:${this.publicPath(pathData, {})}`); + hash.update("path"); + const assetInfo = {}; + hash.update(this.publicPath(pathData, assetInfo)); + hash.update(JSON.stringify(assetInfo)); } else if (this.publicPath) { - hash.update(`path:${this.publicPath}`); + hash.update("path"); + hash.update(this.publicPath); } else { hash.update("no-path"); } const assetModuleFilename = this.filename || runtimeTemplate.outputOptions.assetModuleFilename; - const { path: filename } = + const { path: filename, info } = runtimeTemplate.compilation.getAssetPathWithInfo( assetModuleFilename, pathData ); hash.update(filename); + hash.update(JSON.stringify(info)); } } } diff --git a/schemas/WebpackOptions.json b/schemas/WebpackOptions.json index 92840c054..80fd8864e 100644 --- a/schemas/WebpackOptions.json +++ b/schemas/WebpackOptions.json @@ -70,7 +70,7 @@ ] }, "AssetGeneratorDataUrlFunction": { - "description": "Function that executes for module and should return an DataUrl string.", + "description": "Function that executes for module and should return an DataUrl string. It can have a string as 'ident' property which contributes to the module hash.", "instanceof": "Function", "tsType": "((source: string | Buffer, context: { filename: string, module: import('../lib/Module') }) => string)" }, diff --git a/test/__snapshots__/StatsTestCases.basictest.js.snap b/test/__snapshots__/StatsTestCases.basictest.js.snap index 07f202eab..23fa6e36d 100644 --- a/test/__snapshots__/StatsTestCases.basictest.js.snap +++ b/test/__snapshots__/StatsTestCases.basictest.js.snap @@ -152,7 +152,7 @@ webpack/runtime/make namespace object 274 bytes {main} [code generated] [no exports] [used exports unknown] -1970-04-20 12:42:42: webpack x.x.x compiled successfully in X ms (5406e3ea7ff9b8dee68e)" +1970-04-20 12:42:42: webpack x.x.x compiled successfully in X ms (d0d97703a88fdf5418be)" `; exports[`StatsTestCases should print correct stats for asset 1`] = ` @@ -2536,7 +2536,7 @@ LOG from webpack.FileSystemInfo exports[`StatsTestCases should print correct stats for real-content-hash 1`] = ` "a-normal: assets by path *.js 3.23 KiB - asset 8b31b7e863da4eb900ec-8b31b7.js 2.75 KiB [emitted] [immutable] [minimized] (name: runtime) + asset 9690d063d027560c1900-9690d0.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) @@ -2544,7 +2544,7 @@ exports[`StatsTestCases should print correct stats for real-content-hash 1`] = ` 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) = 8b31b7e863da4eb900ec-8b31b7.js 2.75 KiB cbb9c74e42f00ada40f7-cbb9c7.js 212 bytes 1 auxiliary asset + Entrypoint index 2.96 KiB (5.89 KiB) = 9690d063d027560c1900-9690d0.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.28 KiB 9 modules @@ -2590,8 +2590,8 @@ b-normal: a-source-map: assets by path *.js 3.45 KiB - asset db6c639d86e9740bd622-db6c63.js 2.8 KiB [emitted] [immutable] [minimized] (name: runtime) - sourceMap db6c639d86e9740bd622-db6c63.js.map 14.5 KiB [emitted] [dev] (auxiliary name: runtime) + asset 3c4c8b6907eb902d46e3-3c4c8b.js 2.8 KiB [emitted] [immutable] [minimized] (name: runtime) + sourceMap 3c4c8b6907eb902d46e3-3c4c8b.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) @@ -2602,7 +2602,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.06 KiB (20.7 KiB) = db6c639d86e9740bd622-db6c63.js 2.8 KiB 9e0ae6ff74fb2c3c821b-9e0ae6.js 268 bytes 3 auxiliary assets + Entrypoint index 3.06 KiB (20.7 KiB) = 3c4c8b6907eb902d46e3-3c4c8b.js 2.8 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.28 KiB 9 modules @@ -2621,8 +2621,8 @@ a-source-map: b-source-map: assets by path *.js 3.45 KiB - asset 796d1329e3aa20b86016-796d13.js 2.8 KiB [emitted] [immutable] [minimized] (name: runtime) - sourceMap 796d1329e3aa20b86016-796d13.js.map 14.5 KiB [emitted] [dev] (auxiliary name: runtime) + asset 3c4c8b6907eb902d46e3-3c4c8b.js 2.8 KiB [emitted] [immutable] [minimized] (name: runtime) + sourceMap 3c4c8b6907eb902d46e3-3c4c8b.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 397 bytes [emitted] [dev] (auxiliary name: lazy) asset 9e0ae6ff74fb2c3c821b-9e0ae6.js 268 bytes [emitted] [immutable] [minimized] (name: index) @@ -2633,7 +2633,7 @@ b-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.06 KiB (20.7 KiB) = 796d1329e3aa20b86016-796d13.js 2.8 KiB 9e0ae6ff74fb2c3c821b-9e0ae6.js 268 bytes 3 auxiliary assets + Entrypoint index 3.06 KiB (20.7 KiB) = 3c4c8b6907eb902d46e3-3c4c8b.js 2.8 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.28 KiB 9 modules