diff --git a/README.md b/README.md index 9f5b7d7a..02d46c71 100644 --- a/README.md +++ b/README.md @@ -464,7 +464,7 @@ which will inject the element `:contenthash::]` - -* `hashType` - one of `sha1`, `md5`, `sha256`, `sha512` or any other node.js supported hash type -* `digestType` - one of `hex`, `base26`, `base32`, `base36`, `base49`, `base52`, `base58`, `base62`, `base64` -* `maxlength` - maximum length of the generated hash in chars - -**Defaults:** `[md5:contenthash:hex:9999]` +Refer webpack's [Template Strings](https://webpack.js.org/configuration/output/#template-strings) for more details ### Events diff --git a/index.js b/index.js index eb74aeb1..45f286ad 100644 --- a/index.js +++ b/index.js @@ -14,7 +14,6 @@ const vm = require('vm'); const fs = require('fs'); const _ = require('lodash'); const path = require('path'); -const loaderUtils = require('loader-utils'); const { CachedChildCompilation } = require('./lib/cached-child-compiler'); const { createHtmlTagObject, htmlTagObjectToString, HtmlTagArray } = require('./lib/html-tags'); @@ -132,8 +131,7 @@ class HtmlWebpackPlugin { ...global, HTML_WEBPACK_PLUGIN: true, require: require, - htmlWebpackPluginPublicPath: - publicPath, + htmlWebpackPluginPublicPath: publicPath, URL: require('url').URL, __filename: templateWithoutLoaders }); @@ -189,13 +187,6 @@ function hookIntoCompiler (compiler, options, plugin) { options.filename = path.relative(outputPath, filename); } - // `contenthash` is introduced in webpack v4.3 - // which conflicts with the plugin's existing `contenthash` method, - // hence it is renamed to `templatehash` to avoid conflicts - options.filename = options.filename.replace(/\[(?:(\w+):)?contenthash(?::([a-z]+\d*))?(?::(\d+))?\]/ig, (match) => { - return match.replace('contenthash', 'templatehash'); - }); - // Check if webpack is running in production mode // @see https://github.com/webpack/webpack/blob/3366421f1784c449f415cda5930a8e445086f688/lib/WebpackOptionsDefaulter.js#L12-L14 const isProductionLikeMode = compiler.options.mode === 'production' || !compiler.options.mode; @@ -249,21 +240,12 @@ function hookIntoCompiler (compiler, options, plugin) { compilation.errors.push(prettyError(templateResult.error, compiler.context).toString()); } - const compiledEntries = 'compiledEntry' in templateResult ? { - hash: templateResult.compiledEntry.hash, - chunk: templateResult.compiledEntry.entry - } : { - hash: templateResult.mainCompilationHash - }; - - const childCompilationOutputName = compilation.getAssetPath(options.filename, compiledEntries); - // If the child compilation was not executed during a previous main compile run // it is a cached result const isCompilationCached = templateResult.mainCompilationHash !== compilation.hash; /** The public path used inside the html file */ - const htmlPublicPath = getPublicPath(compilation, childCompilationOutputName, options.publicPath); + const htmlPublicPath = getPublicPath(compilation, options.filename, options.publicPath); /** Generated file paths from the entry point names */ const assets = htmlWebpackPluginAssets(compilation, sortedEntryNames, htmlPublicPath); @@ -288,7 +270,7 @@ function hookIntoCompiler (compiler, options, plugin) { assets.favicon = faviconPath; return getHtmlWebpackPluginHooks(compilation).beforeAssetTagGeneration.promise({ assets: assets, - outputName: childCompilationOutputName, + outputName: options.filename, plugin: plugin }); }); @@ -306,7 +288,7 @@ function hookIntoCompiler (compiler, options, plugin) { ...generateFaviconTags(assets.favicon) ] }, - outputName: childCompilationOutputName, + outputName: options.filename, publicPath: htmlPublicPath, plugin: plugin })) @@ -320,7 +302,7 @@ function hookIntoCompiler (compiler, options, plugin) { return getHtmlWebpackPluginHooks(compilation).alterAssetTagGroups.promise({ headTags: assetGroups.headTags, bodyTags: assetGroups.bodyTags, - outputName: childCompilationOutputName, + outputName: options.filename, publicPath: htmlPublicPath, plugin: plugin }); @@ -351,7 +333,7 @@ function hookIntoCompiler (compiler, options, plugin) { const injectedHtmlPromise = Promise.all([assetTagGroupsPromise, templateExectutionPromise]) // Allow plugins to change the html before assets are injected .then(([assetTags, html]) => { - const pluginArgs = { html, headTags: assetTags.headTags, bodyTags: assetTags.bodyTags, plugin: plugin, outputName: childCompilationOutputName }; + const pluginArgs = { html, headTags: assetTags.headTags, bodyTags: assetTags.bodyTags, plugin: plugin, outputName: options.filename }; return getHtmlWebpackPluginHooks(compilation).afterTemplateExecution.promise(pluginArgs); }) .then(({ html, headTags, bodyTags }) => { @@ -361,7 +343,7 @@ function hookIntoCompiler (compiler, options, plugin) { const emitHtmlPromise = injectedHtmlPromise // Allow plugins to change the html after assets are injected .then((html) => { - const pluginArgs = { html, plugin: plugin, outputName: childCompilationOutputName }; + const pluginArgs = { html, plugin: plugin, outputName: options.filename }; return getHtmlWebpackPluginHooks(compilation).beforeEmit.promise(pluginArgs) .then(result => result.html); }) @@ -372,16 +354,15 @@ function hookIntoCompiler (compiler, options, plugin) { return options.showErrors ? prettyError(err, compiler.context).toHtml() : 'ERROR'; }) .then(html => { - // Allow to use [templatehash] as placeholder for the html-webpack-plugin name - // See also https://survivejs.com/webpack/optimizing/adding-hashes-to-filenames/ - // From https://github.com/webpack-contrib/extract-text-webpack-plugin/blob/8de6558e33487e7606e7cd7cb2adc2cccafef272/src/index.js#L212-L214 - const finalOutputName = childCompilationOutputName.replace(/\[(?:(\w+):)?templatehash(?::([a-z]+\d*))?(?::(\d+))?\]/ig, (_, hashType, digestType, maxLength) => { - return loaderUtils.getHashDigest(Buffer.from(html, 'utf8'), hashType, digestType, parseInt(maxLength, 10)); - }); + const filename = options.filename.replace(/\[templatehash([^\]]*)\]/g, require('util').deprecate( + (match, options) => `[contenthash${options}]`, + '[templatehash] is now [contenthash]') + ); + const replacedFilename = replacePlaceholdersInFilename(filename, html, compilation); // Add the evaluated html code to the webpack assets - compilation.emitAsset(finalOutputName, new webpack.sources.RawSource(html, false)); - previousEmittedAssets.push({ name: finalOutputName, html }); - return finalOutputName; + compilation.emitAsset(replacedFilename.path, new webpack.sources.RawSource(html, false), replacedFilename.info); + previousEmittedAssets.push({ name: replacedFilename.path, html }); + return replacedFilename.path; }) .then((finalOutputName) => getHtmlWebpackPluginHooks(compilation).afterEmit.promise({ outputName: finalOutputName, @@ -519,6 +500,38 @@ function hookIntoCompiler (compiler, options, plugin) { }); } + /** + * Replace [contenthash] in filename + * + * @see https://survivejs.com/webpack/optimizing/adding-hashes-to-filenames/ + * + * @param {string} filename + * @param {string|Buffer} fileContent + * @param {WebpackCompilation} compilation + * @returns {{ path: string, info: {} }} + */ + function replacePlaceholdersInFilename (filename, fileContent, compilation) { + if (/\[\\*([\w:]+)\\*\]/i.test(filename) === false) { + return { path: filename, info: {} }; + } + const hash = compiler.webpack.util.createHash(compilation.outputOptions.hashFunction); + hash.update(fileContent); + if (compilation.outputOptions.hashSalt) { + hash.update(compilation.outputOptions.hashSalt); + } + const contentHash = hash.digest(compilation.outputOptions.hashDigest).slice(0, compilation.outputOptions.hashDigestLength); + return compilation.getPathWithInfo( + filename, + { + contentHash, + chunk: { + hash: contentHash, + contentHash + } + } + ); + } + /** * Helper to sort chunks * @param {string[]} entryNames diff --git a/package.json b/package.json index c814d399..d8855a78 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ ] }, "devDependencies": { - "@types/loader-utils": "2.0.1", "@types/node": "11.13.9", "commitizen": "4.2.1", "css-loader": "5.0.1", @@ -45,9 +44,9 @@ "standard-version": "9.1.0", "style-loader": "2.0.0", "typescript": "4.1.3", - "webpack": "5.23.0", + "webpack": "5.24.3", "webpack-recompilation-simulator": "3.2.0", - "webpack-cli": "4.2.0" + "webpack-cli": "4.5.0" }, "dependencies": { "@types/html-minifier-terser": "^5.0.0", diff --git a/spec/basic.spec.js b/spec/basic.spec.js index 572d7740..794e14c6 100644 --- a/spec/basic.spec.js +++ b/spec/basic.spec.js @@ -951,7 +951,24 @@ describe('HtmlWebpackPlugin', () => { }, ['