Skip to content

Commit

Permalink
fix: when using integrity occurs ERROR in RealContentHashPlugin in se…
Browse files Browse the repository at this point in the history
…rv/watch mode after changes
  • Loading branch information
webdiscus committed Aug 16, 2024
1 parent fbf7414 commit 71f0572
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 52 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change log

## 3.17.4 (2024-08-16)

- fix: when using integrity occurs ERROR in RealContentHashPlugin in serv/watch mode after changes in dynamic imported JS files or after adding new import file

## 3.17.3 (2024-08-09)

- fix: in dev mode imports SCSS in JS when in the same file is inlined another SCSS file via `?inline` query, #102
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "html-bundler-webpack-plugin",
"version": "3.17.3",
"version": "3.17.4",
"description": "HTML bundler plugin for webpack handles a template as an entry point, extracts CSS and JS from their sources referenced in HTML, supports template engines like Eta, EJS, Handlebars, Nunjucks.",
"keywords": [
"html",
Expand Down
111 changes: 69 additions & 42 deletions src/Plugin/Extras/Integrity.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,17 @@ class Integrity {

this.options = options;
this.chunkChildChunksMap = new WeakMap();
this.referencePlaceholders = new Map();
this.placeholderByChunkId = new Map();
this.chunkByChunkId = new Map();
this.templateByChunkId = new Map();
this.placeholderByChunkId = new Map();
this.Template = null;
}

apply(compiler) {
const { pluginName } = this;
const { Template } = compiler.webpack;

this.Template = Template;

compiler.hooks.afterPlugins.tap(pluginName, (compiler) => {
compiler.hooks.thisCompilation.tap({ name: pluginName, stage: -10000 }, (compilation) => {
Expand Down Expand Up @@ -69,12 +73,12 @@ class Integrity {
this.isRealContentHash = this.options.isRealContentHash();

// dynamically import a JS file
mainTemplate.hooks.jsonpScript.tap(pluginName, (source) => this.addReference('script', source));
mainTemplate.hooks.linkPreload.tap(pluginName, (source) => this.addReference('link', source));
mainTemplate.hooks.localVars.tap(pluginName, this.setReferencePlaceholder.bind(this));
mainTemplate.hooks.jsonpScript.tap(pluginName, (source) => this.getTemplateByTag('script', source));
mainTemplate.hooks.linkPreload.tap(pluginName, (source) => this.getTemplateByTag('link', source));
mainTemplate.hooks.localVars.tap(pluginName, this.getTemplateByChunk.bind(this));

compilation.hooks.beforeRuntimeRequirements.tap(pluginName, () => {
this.placeholderByChunkId.clear();
this.templateByChunkId.clear();
});

compilation.hooks.processAssets.tap(
Expand Down Expand Up @@ -111,6 +115,9 @@ class Integrity {
return chunk ? Integrity.getIntegrity(this.compilation, content[0], chunk) : undefined;
}

/**
* @param {Object} assets
*/
processAssets(assets) {
const { compilation, isRealContentHash } = this;
const { compiler } = compilation;
Expand All @@ -122,18 +129,15 @@ class Integrity {
continue;
}

let childChunks = this.chunkChildChunksMap.get(chunk);
if (!childChunks) {
childChunks = chunk.getAllAsyncChunks();
this.chunkChildChunksMap.set(chunk, childChunks);
}
const childChunks = this.getChildChunks(chunk);

for (const childChunk of childChunks) {
if (childChunk.id == null) {
continue;
}

// TODO: find the use case when childChunk.files.size > 1
// the size of childChunk.files is always 1
const childChunkFile = [...childChunk.files][0];
const placeholder = this.placeholderByChunkId.get(childChunk.id);

Expand All @@ -143,16 +147,22 @@ class Integrity {
childChunkFile,
(source) => source,
(assetInfo) => {
return assetInfo
? {
...assetInfo,
contenthash: Array.isArray(assetInfo.contenthash)
? [...new Set([...assetInfo.contenthash, placeholder])]
: assetInfo.contenthash
? [...new Set([assetInfo.contenthash, placeholder])]
: placeholder,
}
: undefined;
if (!assetInfo) {
return undefined;
}

let contenthash = placeholder;

if (Array.isArray(assetInfo.contenthash)) {
contenthash = [...new Set([...assetInfo.contenthash, placeholder])];
} else if (assetInfo.contenthash) {
contenthash = [...new Set([assetInfo.contenthash, placeholder])];
}

return {
...assetInfo,
contenthash,
};
}
);
} else {
Expand All @@ -172,13 +182,28 @@ class Integrity {
}

/**
* Add the reference of integrity hashes into a tag object.
* @param {Chunk} chunk The webpack chunk.
* @return {Set<Chunk>} The child chunks.
*/
getChildChunks(chunk) {
let childChunks = this.chunkChildChunksMap.get(chunk);

if (!childChunks) {
childChunks = chunk.getAllAsyncChunks();
this.chunkChildChunksMap.set(chunk, childChunks);
}

return childChunks;
}

/**
* Create the integrity template by the tag.
*
* @param {string} tagName
* @param {string} source
* @return {string}
*/
addReference = (tagName, source) => {
getTemplateByTag = (tagName, source) => {
const { compilation, pluginName } = this;
const { Template } = compilation.compiler.webpack;
const { crossOriginLoading } = compilation.outputOptions;
Expand All @@ -191,40 +216,42 @@ class Integrity {
};

/**
* Set the placeholder in the hash reference using the hash of a chunk file.
* Create the integrity template by the chunk.
*
* Saves the placeholder in the hash reference using the hash of a chunk file.
* When the asset is processed, the placeholder will be replaced
* with real integrity hash of the processed asset.
*
* @param {string} source
* @param {Chunk} chunk
* @return {string}
*/
setReferencePlaceholder(source, chunk) {
const { Template } = this.compilation.compiler.webpack;

if (this.referencePlaceholders.has(chunk.id)) {
return this.referencePlaceholders.get(chunk.id);
getTemplateByChunk(source, chunk) {
if (this.templateByChunkId.has(chunk.id)) {
return this.templateByChunkId.get(chunk.id);
}

const childChunks = chunk.getAllAsyncChunks();
this.chunkChildChunksMap.set(chunk, childChunks);
const childChunks = this.getChildChunks(chunk);

if (childChunks.size < 1) {
return source;
}

const placeholders = {};
for (const childChunk of childChunks) {
const placeholder = getPlaceholder(childChunk.id);
let placeholder = this.placeholderByChunkId.get(childChunk.id);
if (!placeholder) {
placeholder = getPlaceholder(childChunk.id);
this.placeholderByChunkId.set(childChunk.id, placeholder);
}
placeholders[childChunk.id] = placeholder;
this.placeholderByChunkId.set(childChunk.id, placeholder);
this.chunkByChunkId.set(childChunk.id, childChunk);
}

if (Object.keys(placeholders).length > 0) {
const refTemplate = Template.asString([source, `${hashesReference} = ${JSON.stringify(placeholders)};`]);
this.referencePlaceholders.set(chunk.id, refTemplate);

return refTemplate;
}
const template = this.Template.asString([source, `${hashesReference} = ${JSON.stringify(placeholders)};`]);
this.templateByChunkId.set(chunk.id, template);

return source;
return template;
}

/**
Expand Down Expand Up @@ -320,10 +347,10 @@ class Integrity {
*/
const getPlaceholder = (chunkId) => {
// the prefix must be exact 7 chars, the same length as a hash function name, e.g. 'sha256-'
const placeholderPrefix = '___TMP-';
const prefix = 'xxxxxx-';
const hash = Integrity.computeIntegrity(chunkId);

return placeholderPrefix + hash.slice(placeholderPrefix.length);
return prefix + hash.slice(prefix.length);
};

module.exports = Integrity;
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<title>Dev</title>
<!-- load source script -->
<link href="main.css" rel="stylesheet" integrity="sha384-P3uRZYJvNVccCqk2iQVGvwDZOYfrwSq1FHv6Ahsf4lBoPPD3ODc0xdEdeom27+CM" crossorigin="anonymous">
<script src="main.6180b06e.js" defer="defer" integrity="sha384-Z4FhgIhPOjwFF+fQqBxQN+0fSOAaqGSti1rzQNtVlipdTz8Ruwa+NFI68hsRe75h" crossorigin="anonymous"></script>
<script src="main.b2802b44.js" defer="defer" integrity="sha384-0oaEWvg3ko+CNViLGy2MgqEQe65Jbi+2m0/2Q0Mmx5UOsruQcchdOezxnn1odKxD" crossorigin="anonymous"></script>
</head>
<body>
<h1>Hello World!</h1>
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 71f0572

Please sign in to comment.