diff --git a/src/htmlminifier.js b/src/htmlminifier.js index 6c2d6dd..1a930cb 100644 --- a/src/htmlminifier.js +++ b/src/htmlminifier.js @@ -904,14 +904,35 @@ async function minifyHTML(value, options, partialMarkup) { }); const ids = []; - new CleanCSS().minify(wrapCSS(text, type)).warnings.forEach(function (warning) { - const match = uidPattern.exec(warning); - if (match) { - const id = uidAttr + match[2] + uidAttr; - text = text.replace(id, ignoreCSS(id)); - ids.push(id); + // Loop removing a single ID at a time from the warnings, a + // warning might contain multiple IDs in the context, but we only + // handle the first match on each attempt. + while (true) { + const minifyTest = new CleanCSS().minify(wrapCSS(text, type)); + if (minifyTest.warnings.length === 0) { + // There are no warnings. + break; } - }); + if (!minifyTest.warnings.every(function (warning) { + // It is very important to reset the RegExp before searching + // as it's re-used each time. + uidPattern.lastIndex = 0; + const match = uidPattern.exec(warning); + if (match) { + const id = uidAttr + match[2] + uidAttr; + // Only substitute each ID once, if this has come up + // multiple times, then we need to abort. + if (!ids.includes(id)) { + text = text.replace(id, ignoreCSS(id)); + ids.push(id); + return true; + } + } + return false; + })) { + break; + } + } return fn(text, type).then(chunk => { ids.forEach(function (id) { diff --git a/tests/minifier.spec.js b/tests/minifier.spec.js index 67b61a6..2fa6463 100644 --- a/tests/minifier.spec.js +++ b/tests/minifier.spec.js @@ -3594,3 +3594,14 @@ test('minify Content-Security-Policy', async () => { input = ''; expect(await minify(input)).toBe(input); }); + +test('minify CSS multiple ignore in single warning', async () => { + const input = ''; + const output = ''; + expect(await minify(input, { + ignoreCustomFragments: [/\{%[\s\S]*?%\}/], + minifyCSS: { + level: 0 + } + })).toBe(output); +});