Skip to content

Commit

Permalink
fix: remove loader-utils from plugin core
Browse files Browse the repository at this point in the history
  • Loading branch information
sibiraj-s authored and jantimon committed Mar 9, 2021
1 parent 6f39192 commit 82d0ee8
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 51 deletions.
12 changes: 3 additions & 9 deletions README.md
Expand Up @@ -464,7 +464,7 @@ which will inject the element `<base href="http://example.com/some/page.html" ta

### Long Term Caching

For long term caching add `contenthash/templatehash` to the filename.
For long term caching add `contenthash` to the filename.

**Example:**

Expand All @@ -476,15 +476,9 @@ plugins: [
]
```

`contenthash/templatehash` is the hash of the content of the output file.
`contenthash` is the hash of the content of the output file.

Optionally, You can configure like `[<hashType>:contenthash:<digestType>:<length>]`

* `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

Expand Down
81 changes: 47 additions & 34 deletions index.js
Expand Up @@ -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');
Expand Down Expand Up @@ -132,8 +131,7 @@ class HtmlWebpackPlugin {
...global,
HTML_WEBPACK_PLUGIN: true,
require: require,
htmlWebpackPluginPublicPath:
publicPath,
htmlWebpackPluginPublicPath: publicPath,
URL: require('url').URL,
__filename: templateWithoutLoaders
});
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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
});
});
Expand All @@ -306,7 +288,7 @@ function hookIntoCompiler (compiler, options, plugin) {
...generateFaviconTags(assets.favicon)
]
},
outputName: childCompilationOutputName,
outputName: options.filename,
publicPath: htmlPublicPath,
plugin: plugin
}))
Expand All @@ -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
});
Expand Down Expand Up @@ -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 }) => {
Expand All @@ -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);
})
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down
5 changes: 2 additions & 3 deletions package.json
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
27 changes: 22 additions & 5 deletions spec/basic.spec.js
Expand Up @@ -951,7 +951,24 @@ describe('HtmlWebpackPlugin', () => {
}, ['<script defer="defer" src="index_bundle.js"'], /test-\S+\.html$/, done);
});

it('should allow filename in the format of [<hashType>:contenthash:<digestType>:<length>]', done => {
it('should work with hash options provided in output options', done => {
testHtmlPlugin({
mode: 'production',
entry: {
index: path.join(__dirname, 'fixtures/index.js')
},
output: {
path: OUTPUT_DIR,
filename: '[name]_bundle.js',
hashDigestLength: 16
},
plugins: [
new HtmlWebpackPlugin({ filename: 'index.[contenthash].html' })
]
}, [], /index\.[a-z0-9]{16}\.html/, done);
});

it('should allow filename in the format of [contenthash:<length>]', done => {
testHtmlPlugin({
mode: 'production',
entry: {
Expand All @@ -962,9 +979,9 @@ describe('HtmlWebpackPlugin', () => {
filename: '[name]_bundle.js'
},
plugins: [
new HtmlWebpackPlugin({ filename: 'index.[sha256:contenthash:base32:32].html' })
new HtmlWebpackPlugin({ filename: 'index.[contenthash:4].html' })
]
}, [], /index\.[a-z0-9]{32}\.html/, done);
}, [], /index\.[a-z0-9]{4}\.html/, done);
});

it('will replace [contenthash] in the filename with a content hash of 32 hex characters', done => {
Expand All @@ -980,7 +997,7 @@ describe('HtmlWebpackPlugin', () => {
plugins: [
new HtmlWebpackPlugin({ filename: 'index.[contenthash].html' })
]
}, [], /index\.[a-f0-9]{32}\.html/, done);
}, [], /index\.[a-f0-9]{20}\.html/, done);
});

it('will replace [templatehash] in the filename with a content hash of 32 hex characters', done => {
Expand All @@ -996,7 +1013,7 @@ describe('HtmlWebpackPlugin', () => {
plugins: [
new HtmlWebpackPlugin({ filename: 'index.[templatehash].html' })
]
}, [], /index\.[a-f0-9]{32}\.html/, done);
}, [], /index\.[a-f0-9]{20}\.html/, done);
});

it('allows you to use an absolute output filename', done => {
Expand Down

0 comments on commit 82d0ee8

Please sign in to comment.