From cfdb1dfe59b33bf7441b8a8e4fc58d75e4f54cee Mon Sep 17 00:00:00 2001 From: Ryan Wilson-Perkin Date: Fri, 24 Feb 2023 16:17:15 -0500 Subject: [PATCH] Improve performance of hashRegExp lookup For applications with a very large number of assets, the cost of invoking a single regular expression with many many values in a group becomes very high. By changing to a list of regular expressions (with helper methods for maintaining the original design) we can get a large performance improvement. --- lib/optimize/RealContentHashPlugin.js | 58 ++++++++++++++++++++------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/lib/optimize/RealContentHashPlugin.js b/lib/optimize/RealContentHashPlugin.js index 7ab9c46fb8b..ba058b753a2 100644 --- a/lib/optimize/RealContentHashPlugin.js +++ b/lib/optimize/RealContentHashPlugin.js @@ -178,10 +178,43 @@ class RealContentHashPlugin { } } if (hashToAssets.size === 0) return; - const hashRegExp = new RegExp( - Array.from(hashToAssets.keys(), quoteMeta).join("|"), - "g" + const hashRegExps = Array.from(hashToAssets.keys(), quoteMeta).map( + hash => new RegExp(hash, "g") ); + + /** + * @param {string} str string to be matched against all hashRegExps + * @returns {string[] | null} matches found + */ + const hashMatch = str => { + /** @type {string[]} */ + const results = []; + for (const hashRegExp of hashRegExps) { + const matches = str.match(hashRegExp); + if (matches) { + matches.forEach(match => results.push(match)); + } + } + if (results.length) { + return results; + } else { + return null; + } + }; + + /** + * @param {string} str string to be replaced with all hashRegExps + * @param {function(string): string} fn replacement function to use when a hash is found + * @returns {string} replaced content + */ + const hashReplace = (str, fn) => { + let result = str; + for (const hashRegExp of hashRegExps) { + result = result.replace(hashRegExp, fn); + } + return result; + }; + await Promise.all( assetsWithInfo.map(async asset => { const { name, source, content, hashes } = asset; @@ -198,7 +231,7 @@ class RealContentHashPlugin { await cacheAnalyse.providePromise(name, etag, () => { const referencedHashes = new Set(); let ownHashes = new Set(); - const inContent = content.match(hashRegExp); + const inContent = hashMatch(content); if (inContent) { for (const hash of inContent) { if (hashes.has(hash)) { @@ -298,7 +331,7 @@ ${referencingAssets identifier, etag, () => { - const newContent = asset.content.replace(hashRegExp, hash => + const newContent = hashReplace(asset.content, hash => hashToNewHash.get(hash) ); return new RawSource(newContent); @@ -323,15 +356,12 @@ ${referencingAssets identifier, etag, () => { - const newContent = asset.content.replace( - hashRegExp, - hash => { - if (asset.ownHashes.has(hash)) { - return ""; - } - return hashToNewHash.get(hash); + const newContent = hashReplace(asset.content, hash => { + if (asset.ownHashes.has(hash)) { + return ""; } - ); + return hashToNewHash.get(hash); + }); return new RawSource(newContent); } ); @@ -374,7 +404,7 @@ ${referencingAssets await Promise.all( assetsWithInfo.map(async asset => { await computeNewContent(asset); - const newName = asset.name.replace(hashRegExp, hash => + const newName = hashReplace(asset.name, hash => hashToNewHash.get(hash) );