Skip to content

Commit 58620dc

Browse files
fix(html): keep minifying a document the round-trip guard checks (#21842)
* fix(html): keep minifying a document the round-trip guard checks The printer verifies a handful of parse shapes by re-parsing its own output and comparing tree digests, but compared them byte for byte — so collapsed whitespace, a dropped comment, a minified inline sheet and a respelled attribute value all read as a moved node, and the whole document came back unminified. A nested list item is enough to turn the guard on, which is most nav menus and every rendered Markdown list. Digest both trees the way the print leaves them instead; a node that really moves still reshapes the tree and still hands the source back. Alongside it, in the CSS minifier: `flex` and the four logical `border-<edge>` shorthands now merge out of their longhands — the edges state their grammar as `<'border-top'>`, so the generator follows that reference rather than listing them, and Chromium says both reset nothing their longhands do not, where `border` itself clears every `border-image-*` and so stays out. Two printing costs go with them: whether a sibling opens an `@layer` block is read off the node rather than off its printed text, which had flattened every rule's rope at every nesting level, and the print store grows in place rather than copying what it already holds at each step. * chore: format the regenerated changelog The release commit wrote a code span prettier respaces, which fails `fmt:check` on every branch grown from it. * fix(html): put a deferred body back before the round-trip guard reads it An `<svg>` subtree, an inline `<style>` and an `<iframe srcdoc>` print as a marker for an asynchronous renderer to answer, so the guard's re-parse read text where the node stood and handed the whole document back unminified. * chore: shorten the changeset and a test comment to their limits * test(html): cover the deferred-svg guard where the print is deterministic The config case reached the deferred path only where the minimizer plugin handed the renderer through, so its snapshot recorded a transform CI never applied. A `processAsync` unit test drives the same guard directly. * chore(html): shorten the deferred-write comment to two lines
1 parent 0b2952e commit 58620dc

22 files changed

Lines changed: 596 additions & 78 deletions

File tree

.changeset/010-css-html-minify.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"webpack": minor
3+
---
4+
5+
Minify more HTML and CSS shorthands, and cut two costs off printing.

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,7 @@
799799

800800
- Fix snapshot validity check for context dependencies in watch mode by treating watchpack's existence-only entries (`{}`) as cache misses. (by [@alexander-akait](https://github.com/alexander-akait) in [#20916](https://github.com/webpack/webpack/pull/20916))
801801

802-
- Support no-expression template literals in computed member access (e.g. `` import.meta[`url`] ``). (by [@alexander-akait](https://github.com/alexander-akait) in [#20889](https://github.com/webpack/webpack/pull/20889))
802+
- Support no-expression template literals in computed member access (e.g. ``import.meta[`url`]``). (by [@alexander-akait](https://github.com/alexander-akait) in [#20889](https://github.com/webpack/webpack/pull/20889))
803803

804804
- Improve tree-shaking in `isPure`: handle more expression types (`ArrayExpression`, `ObjectExpression`, `NewExpression`, `ChainExpression`, `UnaryExpression` (safe operators), `MetaProperty`, `TaggedTemplateExpression`, `BinaryExpression` (strict equality)), prevent `/*#__PURE__*/` comments from leaking across `ObjectExpression` properties, and detect PURE comments inside `TemplateLiteral` interpolations. (by [@alexander-akait](https://github.com/alexander-akait) in [#20723](https://github.com/webpack/webpack/pull/20723))
805805

eslint.config.mjs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,12 @@ export default defineConfig([
236236
{
237237
// `color-name` is ESM, so the CSS data generator reaches its table
238238
// through a dynamic import rather than a `require` no jest `vm` supports.
239-
files: ["tooling/generate-css-data.js"],
239+
// `html-minifier-next` is ESM only, and the HTML comparison imports it the
240+
// same way.
241+
files: [
242+
"tooling/generate-css-data.js",
243+
"tooling/compare-html-minifiers.js"
244+
],
240245
rules: {
241246
"n/no-unsupported-features/es-syntax": [
242247
"error",

lib/css/data.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ const UNSHARED_LONGHAND_KEYWORDS = new Map([
517517
// appearing once, in grammar order. A merge emits every value, so the only
518518
// question is whether each parses back into the longhand it was authored on.
519519
// prettier-ignore
520-
const FAMILY_LONGHANDS = new Map([["border-bottom", ["border-bottom-width","border-bottom-style","border-bottom-color"]], ["border-left", ["border-left-width","border-left-style","border-left-color"]], ["border-right", ["border-right-width","border-right-style","border-right-color"]], ["border-top", ["border-top-width","border-top-style","border-top-color"]], ["column-rule", ["column-rule-width","column-rule-style","column-rule-color"]], ["flex-flow", ["flex-direction","flex-wrap"]], ["list-style", ["list-style-type","list-style-position","list-style-image"]], ["outline", ["outline-width","outline-style","outline-color"]], ["text-decoration", ["text-decoration-line","text-decoration-style","text-decoration-color","text-decoration-thickness"]], ["text-emphasis", ["text-emphasis-style","text-emphasis-color"]], ["text-wrap", ["text-wrap-mode","text-wrap-style"]]]);
520+
const FAMILY_LONGHANDS = new Map([["border-block-end", ["border-block-end-width","border-block-end-style","border-block-end-color"]], ["border-block-start", ["border-block-start-width","border-block-start-style","border-block-start-color"]], ["border-bottom", ["border-bottom-width","border-bottom-style","border-bottom-color"]], ["border-inline-end", ["border-inline-end-width","border-inline-end-style","border-inline-end-color"]], ["border-inline-start", ["border-inline-start-width","border-inline-start-style","border-inline-start-color"]], ["border-left", ["border-left-width","border-left-style","border-left-color"]], ["border-right", ["border-right-width","border-right-style","border-right-color"]], ["border-top", ["border-top-width","border-top-style","border-top-color"]], ["column-rule", ["column-rule-width","column-rule-style","column-rule-color"]], ["flex-flow", ["flex-direction","flex-wrap"]], ["list-style", ["list-style-type","list-style-position","list-style-image"]], ["outline", ["outline-width","outline-style","outline-color"]], ["text-decoration", ["text-decoration-line","text-decoration-style","text-decoration-color","text-decoration-thickness"]], ["text-emphasis", ["text-emphasis-style","text-emphasis-color"]], ["text-wrap", ["text-wrap-mode","text-wrap-style"]]]);
521521

522522
// The properties whose comma-separated items take a `<custom-ident>`, where a
523523
// vendor spelling is a name the engine parses rather than one it may drop — so a
@@ -537,6 +537,12 @@ const CUSTOM_IDENT_LIST_PROPERTIES = new Set([
537537
"will-change"
538538
]);
539539

540+
// The shorthands whose grammar juxtaposes its longhands in a fixed order, so a
541+
// merge writes every value by position rather than reading which slot takes it.
542+
const ORDERED_LONGHANDS = new Map([
543+
["flex", ["flex-grow", "flex-shrink", "flex-basis"]]
544+
]);
545+
540546
const SLASH_LONGHANDS = new Map([
541547
[
542548
"grid-area",
@@ -597,7 +603,10 @@ const MERGE_LONGHANDS = new Set([
597603
"corner-start-start-shape",
598604
"corner-top-left-shape",
599605
"corner-top-right-shape",
606+
"flex-basis",
600607
"flex-direction",
608+
"flex-grow",
609+
"flex-shrink",
601610
"flex-wrap",
602611
"grid-column-end",
603612
"grid-column-start",
@@ -682,10 +691,10 @@ const OMITTABLE_INITIAL_KEYWORDS = new Map([["grid-auto-flow", ["row", ["column"
682691
// makes the merge ambiguous, and `FAMILY_SLOT_CLASSES` names a type the printer
683692
// cannot classify as readily as one it can, so an unknown one declines.
684693
// prettier-ignore
685-
const FAMILY_SLOT_KEYWORDS = new Map([["border-bottom-width", ["medium","thick","thin"]], ["border-bottom-style", ["dashed","dotted","double","groove","hidden","inset","none","outset","ridge","solid"]], ["border-bottom-color", []], ["border-left-width", ["medium","thick","thin"]], ["border-left-style", ["dashed","dotted","double","groove","hidden","inset","none","outset","ridge","solid"]], ["border-left-color", []], ["border-right-width", ["medium","thick","thin"]], ["border-right-style", ["dashed","dotted","double","groove","hidden","inset","none","outset","ridge","solid"]], ["border-right-color", []], ["border-top-width", ["medium","thick","thin"]], ["border-top-style", ["dashed","dotted","double","groove","hidden","inset","none","outset","ridge","solid"]], ["border-top-color", []], ["column-rule-width", ["medium","thick","thin"]], ["column-rule-style", ["dashed","dotted","double","groove","hidden","inset","none","outset","ridge","solid"]], ["column-rule-color", []], ["flex-direction", ["column","column-reverse","row","row-reverse"]], ["flex-wrap", ["nowrap","wrap","wrap-reverse"]], ["list-style-type", ["none"]], ["list-style-position", ["inside","outside"]], ["list-style-image", ["none"]], ["outline-width", ["medium","thick","thin"]], ["outline-style", ["auto","dashed","dotted","double","groove","inset","none","outset","ridge","solid"]], ["outline-color", ["auto"]], ["text-decoration-line", ["blink","grammar-error","line-through","none","overline","spelling-error","underline"]], ["text-decoration-style", ["dashed","dotted","double","solid","wavy"]], ["text-decoration-color", []], ["text-decoration-thickness", ["auto","from-font"]], ["text-emphasis-style", ["circle","dot","double-circle","filled","none","open","sesame","triangle"]], ["text-emphasis-color", []], ["text-wrap-mode", ["nowrap","wrap"]], ["text-wrap-style", ["auto","balance","pretty","stable"]]]);
694+
const FAMILY_SLOT_KEYWORDS = new Map([["border-block-end-width", ["medium","thick","thin"]], ["border-block-end-style", ["dashed","dotted","double","groove","hidden","inset","none","outset","ridge","solid"]], ["border-block-end-color", []], ["border-block-start-width", ["medium","thick","thin"]], ["border-block-start-style", ["dashed","dotted","double","groove","hidden","inset","none","outset","ridge","solid"]], ["border-block-start-color", []], ["border-bottom-width", ["medium","thick","thin"]], ["border-bottom-style", ["dashed","dotted","double","groove","hidden","inset","none","outset","ridge","solid"]], ["border-bottom-color", []], ["border-inline-end-width", ["medium","thick","thin"]], ["border-inline-end-style", ["dashed","dotted","double","groove","hidden","inset","none","outset","ridge","solid"]], ["border-inline-end-color", []], ["border-inline-start-width", ["medium","thick","thin"]], ["border-inline-start-style", ["dashed","dotted","double","groove","hidden","inset","none","outset","ridge","solid"]], ["border-inline-start-color", []], ["border-left-width", ["medium","thick","thin"]], ["border-left-style", ["dashed","dotted","double","groove","hidden","inset","none","outset","ridge","solid"]], ["border-left-color", []], ["border-right-width", ["medium","thick","thin"]], ["border-right-style", ["dashed","dotted","double","groove","hidden","inset","none","outset","ridge","solid"]], ["border-right-color", []], ["border-top-width", ["medium","thick","thin"]], ["border-top-style", ["dashed","dotted","double","groove","hidden","inset","none","outset","ridge","solid"]], ["border-top-color", []], ["column-rule-width", ["medium","thick","thin"]], ["column-rule-style", ["dashed","dotted","double","groove","hidden","inset","none","outset","ridge","solid"]], ["column-rule-color", []], ["flex-direction", ["column","column-reverse","row","row-reverse"]], ["flex-wrap", ["nowrap","wrap","wrap-reverse"]], ["list-style-type", ["none"]], ["list-style-position", ["inside","outside"]], ["list-style-image", ["none"]], ["outline-width", ["medium","thick","thin"]], ["outline-style", ["auto","dashed","dotted","double","groove","inset","none","outset","ridge","solid"]], ["outline-color", ["auto"]], ["text-decoration-line", ["blink","grammar-error","line-through","none","overline","spelling-error","underline"]], ["text-decoration-style", ["dashed","dotted","double","solid","wavy"]], ["text-decoration-color", []], ["text-decoration-thickness", ["auto","from-font"]], ["text-emphasis-style", ["circle","dot","double-circle","filled","none","open","sesame","triangle"]], ["text-emphasis-color", []], ["text-wrap-mode", ["nowrap","wrap"]], ["text-wrap-style", ["auto","balance","pretty","stable"]]]);
686695

687696
// prettier-ignore
688-
const FAMILY_SLOT_CLASSES = new Map([["border-bottom-width", ["length"]], ["border-bottom-style", []], ["border-bottom-color", ["color"]], ["border-left-width", ["length"]], ["border-left-style", []], ["border-left-color", ["color"]], ["border-right-width", ["length"]], ["border-right-style", []], ["border-right-color", ["color"]], ["border-top-width", ["length"]], ["border-top-style", []], ["border-top-color", ["color"]], ["column-rule-width", ["length"]], ["column-rule-style", []], ["column-rule-color", ["color"]], ["flex-direction", []], ["flex-wrap", []], ["list-style-type", ["custom-ident","string"]], ["list-style-position", []], ["list-style-image", ["image"]], ["outline-width", ["length"]], ["outline-style", []], ["outline-color", ["color"]], ["text-decoration-line", []], ["text-decoration-style", []], ["text-decoration-color", ["color"]], ["text-decoration-thickness", ["length","percentage"]], ["text-emphasis-style", ["string"]], ["text-emphasis-color", ["color"]], ["text-wrap-mode", []], ["text-wrap-style", []]]);
697+
const FAMILY_SLOT_CLASSES = new Map([["border-block-end-width", ["length"]], ["border-block-end-style", []], ["border-block-end-color", ["color"]], ["border-block-start-width", ["length"]], ["border-block-start-style", []], ["border-block-start-color", ["color"]], ["border-bottom-width", ["length"]], ["border-bottom-style", []], ["border-bottom-color", ["color"]], ["border-inline-end-width", ["length"]], ["border-inline-end-style", []], ["border-inline-end-color", ["color"]], ["border-inline-start-width", ["length"]], ["border-inline-start-style", []], ["border-inline-start-color", ["color"]], ["border-left-width", ["length"]], ["border-left-style", []], ["border-left-color", ["color"]], ["border-right-width", ["length"]], ["border-right-style", []], ["border-right-color", ["color"]], ["border-top-width", ["length"]], ["border-top-style", []], ["border-top-color", ["color"]], ["column-rule-width", ["length"]], ["column-rule-style", []], ["column-rule-color", ["color"]], ["flex-direction", []], ["flex-wrap", []], ["list-style-type", ["custom-ident","string"]], ["list-style-position", []], ["list-style-image", ["image"]], ["outline-width", ["length"]], ["outline-style", []], ["outline-color", ["color"]], ["text-decoration-line", []], ["text-decoration-style", []], ["text-decoration-color", ["color"]], ["text-decoration-thickness", ["length","percentage"]], ["text-emphasis-style", ["string"]], ["text-emphasis-color", ["color"]], ["text-wrap-mode", []], ["text-wrap-style", []]]);
689698

690699
// The identifiers that are a `<color>` on their own — named, system and the two
691700
// context-dependent ones. Read off the `<color>` grammar outside any function,
@@ -5282,6 +5291,7 @@ module.exports.NTH_NAMED_EQUIVALENTS = NTH_NAMED_EQUIVALENTS;
52825291
module.exports.NTH_PSEUDO_FUNCTIONS = NTH_PSEUDO_FUNCTIONS;
52835292
module.exports.OMITTABLE_INITIAL_KEYWORDS = OMITTABLE_INITIAL_KEYWORDS;
52845293
module.exports.ONE_VALUE_PAIR_SHORTHANDS = ONE_VALUE_PAIR_SHORTHANDS;
5294+
module.exports.ORDERED_LONGHANDS = ORDERED_LONGHANDS;
52855295
module.exports.PAIR_LONGHANDS = PAIR_LONGHANDS;
52865296
module.exports.PLACE_SHORTHANDS = PLACE_SHORTHANDS;
52875297
module.exports.POSITION_PROPERTIES = POSITION_PROPERTIES;

0 commit comments

Comments
 (0)