Revision history for CSS::Minifier
0.0.7 2026-06-04T03:35:20+03:00
- AST rewrite: replace CSS::Stylesheet with own brace-counting
parser (Parser.rakumod); own Document tree (Document.rakumod)
replaces CSS::Writer output; Normalizer.rakumod replaces
CSS::Properties normalization
- Zero dependencies: remove CSS::Stylesheet, CSS::Properties,
CSS::Writer — META6.json depends is now []
- Drop :$preserve-unknown option and the pre-scan/re-injection
system it depended on
- Add !dedup-declarations: remove duplicate property:value pairs
within a ruleset
- Add !consolidate-shorthands: consolidate longhands to shorthand
for margin, padding, border, border-{top,right,bottom,left},
outline, list-style, background, and font; skip values
containing var()/calc()/ env()/min()/max()/clamp()
- Fix !consolidate-shorthands duplicate-declaration bug: use
%consolidated set to distinguish merger-created shorthands from
original declarations
- Fix !consolidate-shorthands ordering: emit shorthand at the
position of its last longhand (was first), preserving cascade
order across interleaved non-group properties
- Fix !consolidate-shorthands mixed !important: skip consolidation
when longhands disagree on importance (was applying !important
to all sides if any longhand was important)
- Fix !write-at-rule space: use $ar.prelude ?? ' ' !! '' instead
of hardcoded media/font-face check, so @supports, @keyframes,
@layer, @container also get space before {
- Fix !extract-braceless quote walkers: use skip-quoted (handles
backslash escapes) instead of bare while loops
- Fix Writer quote normalization: skip "→' conversion when value
already contains single quotes
- Fix Writer hex color uppercasing: general hex-uppercase step so
#fff→#FFF (no longer relies on CSS::Properties)
- Add !important to Dedup !at-rule-decl-key for consistency with
!ruleset-key
- Add background and font shorthand consolidation: background
consolidated for single and multi-layer (comma-separated)
declarations with optional background-size via position / size
syntax and optional background-origin/background-clip as box
values; multi-layer handles cycling per CSS spec when layer
counts differ across longhands; font consolidated with optional
font-stretch, requiring only font-size and font-family;
line-height consumed if present
- Add %CUSTOM dispatch table in Normalizer for shorthands requiring
value-level assembly (background, font); simple shorthands
(margin, padding, border, etc.) stay on the join(' ') path
- Add !assemble-background and !assemble-font methods to Normalizer;
!assemble-font normalizes bold→700, normal→400 and skips
font-weight:400, font-style:normal, font-variant:normal as CSS
shorthand default values
- Add has-top-level-comma() and split-top-level() to Util.rakumod
— comma-at-depth-0 detection and splitting; used for multi-layer
background dispatch
- Add $CSS-WIDE-RE guard (initial|inherit|unset|revert) — prevents
consolidation when any longhand has a CSS-wide keyword value
0.0.6 2026-06-03T21:12:55+03:00
- Fix re-minification growth (final): colon in media-feature regex
`/\(.*:.*\)/` was being interpreted as a Raku regex adverb (`:`)
instead of a literal colon, so the denormalize-prelude fallback
in !find-at-rule-block was never triggered. This caused
!prop-exists to always return False for any @media qualifier
whose prelude had been space-normalized by !walk-decls, because
the exact prelude search (with space after `:`) never matched
the pipeline output (without space after `:`). Consequence:
every property inside @media was re-injected on every pass,
causing unbounded growth.
- Add normalize-prelude / denormalize-prelude subroutines to
bridge the two prelude formats
- Normalize preludes in !walk-decls so qualifier keys are
consistent
- Fix !find-at-rule-block to try both normalized and denormalized
forms during block search
- Fix !prop-exists to scan ALL at-rule blocks with the same
prelude (not just the first) for the inner selector
- Fix !merge-qualified to find the at-rule block that actually
contains the target inner selector; add idempotency guard to
skip re-injection when the exact ruleset already exists in the
block
- Fix normalize/denormalize-prelude nested-parens handling
(e.g. calc(), min(), max()) — use a token with &val to match
balanced inner parens instead of a bare <-[):]>+ capture
0.0.5 2026-06-03T06:24:07+03:00
- Fix !normalize-block trailing semicolon: always omit (rebuilds
from scratch), making output consistent with pipeline blocks
- Fix !find-qual-end depth counting: O(n) single-pass with
quote/comment skipping instead of O(n²) rescan from 0
- Fix !normalize-selector quote walking: use skip-quoted for
backslash-escape handling
- Fix split-selectors quote walking: use skip-quoted for
backslash-escape handling (same bug, Util.rakumod)
- Update Merging.rakumod POD: propertyless rules are skipped
(not barriers)
- Update README Level 2: add propertyless-rules note
- Add test coverage: escaped-quote attribute selector, content
brace depth guard, @keyframes margin:0px, no-trailing-newline
- Fix !normalize-all-decls inline quote walkers: use skip-quoted
(fixes double-backslash \" edge case)
- Fix split-xpath quote walking: use skip-quoted for consistency
(Util.rakumod)
0.0.4 2026-06-03T04:48:31+03:00
- Fix order stability: insert re-injected rulesets at their
natural @order position (after predecessor) instead of appending
at end, so first-pass and second-pass selector order is
identical
- Add rgb()/rgba() → hex shortening to !minify-value
(post-processing pass), closing the gap where rgb() values
inside @keyframes and other pipeline-bypassed blocks stayed
unshortened
- Remove stale Merging-plugin !important warning from README —
!important is part of the Str(:optimize) grouping key, so mixed
groups never form
- Fix re-minification growth (remaining): normalize combinator
spacing (+ / > / ~) in !normalize-selector so selectors like
.foo+.bar (no space before +) match the pipeline output .foo +
.bar (with space). Always call !normalize-selector (not just
for comma-containing selectors) to catch this case. Prevents
runaway re-injection on subsequent passes for any selector
containing tight combinators.
0.0.3 2026-06-03T03:39:52+03:00
- Fix braceless at-rules (@import, @charset, @namespace) silently
dropped — extract before parse, prepend to output
- Fix braceless at-rules trailing newline — suppress separator
when no ruleset follows
- Fix @view-transitions and other declaration-only at-rules
dropped by pipeline
- Fix @font-face descriptors dropped by !walk-decls
(e.g. font-display)
- Fix !merge-props to respect !important cascade
- Fix :has()/:is()/:where() duplicate rulesets — strip spurious
truncated prefix ruleset
- Fix prefers-color-scheme/contrast bogus @media not all — replace
with original query
- Fix !replace-bogus-not-all genuine-guard (space before { in
%by-sel key was missing)
- Fix !find-at-rule-block and !prop-exists to target correct Nth
block for duplicate at-rules (e.g. multiple @font-face); fall
back to block 0 when Nth block was deduped away
- Fix !find-at-rule-block advance past brace instead of past
prelude (fragile with space-before-{ variant)
- Fix !merge-qualified double-semicolon when block body already
ends with semicolon
- Fix !prop-exists to scan ALL rulesets for a selector (not just
the first one), preventing spurious re-injection when
duplicate-selector rulesets exist at level 1
- Fix !prop-exists quote normalisation (pre-scanned selectors use
`"`, pipeline output uses `'`)
- Fix !prop-exists boundary guard — skip substring matches
(e.g. `#header` inside `[data-bs-theme='dark'] #header`) while
allowing valid merged-group matches (e.g. `h1` after `, `)
- Rewrite 01-basic and 02-plugins with exact golden output (was
loose regex/count checks)
- Add 03-edge-cases — 11 subtests covering empty input,
!important, custom properties, braceless at-rules, attribute
selectors, nested at-rules, chained merging, @font-face, and more
- Add 04-golden — 57 exact-output regression tests covering the
full minification surface including @keyframes, combinators,
rgb(), @view-transitions, :has()/:is()/:where(),
prefers-color-scheme, and multi-block @font-face
- Fix !has-selector-in regex injection — wrap $sel in quotemeta to
escape metacharacters
- Fix !merge-qualified inner quote mismatch — normalise `"` → `'`
for at-rule inner selectors
- Fix !replace-bogus-not-all inner quote mismatch — same
normalisation for the media-query path
- Fix !remove-selector boundary guard — walk backwards past \s
chars (not just `}`) to handle merged output with :sep("\n")
separators
- Fix !walk-decls NUL-counter grep — use explicit block ({ ... })
instead of broken WhateverCode || to count existing NUL-suffixed
keys for 3+ duplicate at-rule blocks (e.g. three @font-face)
- Fix !dedup-selector-lists split on ', ' breaking with attribute
selectors containing commas — add bracket/paren/string-aware
split-xpath sub to Util; dump naive split(', ') in Merging; use
.splice instead of = for AST mutation
- Fix !merge-decls spurious re-injection after pipeline dedup —
dedup selector lists in qualifier before prop-exists check via
new split-selectors sub; re-inject using deduped qualifier
- Fix upstream xpath crash on pseudo-elements (::before, ::after,
etc.) — monkey-patch missing `xpath-pseudo-elem` method onto
CSS::Selector::To::XPath
- Fix idempotency — strip CSS comments before declaration
extraction in !walk-decls so re-minified output doesn't grow
- Fix non-adjacent same-declaration merging — propertyless rules
are now treated as barriers (last), preserving cascade order
- Fix propertyless rules break non-adjacent merging — change from
`last` to `$i++; next` so parser-artifact propertyless rulesets
are skipped instead of acting as hard barriers
- Fix cascade-order change during same-declaration merging — track
last-merged position for each group and sort by it before
assigning to $stylesheet.rules, preserving which rule wins by
cascade order
- Fix @keyframes keyframe selector order — add @order array
alongside %by-sel in !walk-decls, iterate @order in !merge-decls
Phase 2 instead of %by-sel.kv (Raku Hash iteration is
randomized)
- Remove dead `@skipped` / `@merged.append` code from Merging
plugin
- Update documentation: README and POD limitations reflect fixed
bugs
- Fix !walk-decls regex fragility — replace <-[:;]>+ / <-[;}]>+
regex with parse-body character-walker that tracks strings,
paren depth, and comments, correctly handling ; and } inside
values
- Fix /*! */ license extraction inside string values — replace
naive regex with !extract-licenses character-walking state
machine
- Normalize declaration values in !merge-decls re-injection path —
add !minify-value method that shortens colors, removes
zero-units, normalizes font-weight, and normalizes quotes (" →
') for re-injected declarations
- Add !normalize-all-decls post-processing pass — walks the output
string and normalizes values + deduplicates declarations in ALL
{…} blocks, catching @keyframes keyframes, nested at-rules, and
other blocks the pipeline can't reach (recursive leaf-block
walker)
- Fix broken hex shortening regex in !minify-value (was using
self- referential captures that never matched; now properly
backrefs each hex digit pair) so 6-char colors are shortened in
@keyframes keyframe blocks and other post-processed blocks
- Fix zero-unit regex in !minify-value — added ``
negative lookbehind to prevent stripping units from non-zero
values containing a `0` digit (e.g. `30px` → `30`); now only
standalone `0px` → `0`
- Fix golden test 70 for @keyframes normalization — accept both
keyframe selector orders (from/to vs to/from) since hash
iteration order in %by-sel is non-deterministic between Raku
runs
- Fix parse-body double-increment when : or ; found inside parens
(skipped one character after :/; inside functions — never fires
in practice since : in property names is invalid CSS)
- Fix has-brace check in !walk-decls — use quote-aware walker
instead of naive / '{' / regex that would false-positive on {
inside quoted strings (theoretical, extremely rare)
- Fix braceless at-rule extraction — replace .*?; regex with
quote/paren-aware find-semicolon walker so ; inside url() or
strings is correctly skipped (theoretical, ; inside URL is rare)
- Fix find-semicolon double-increment on (/) — the when blocks
were incrementing $i inside AND falling through to $i++ at the
while-loop bottom, skipping one character after each paren
- Remove dead code (= Empty) in Merging Plugin line 98 — $next is
about to be dropped, the assignment had no effect
- Fix re-minification growth — normalize selectors (commas → `, `)
in !walk-decls and media-query preludes (colons → `: `) in
!find-at-rule-block so extracted qualifiers match pipeline
output format, preventing runaway re-injection on subsequent
passes
0.0.2 2026-05-21T16:27:43+03:00
- Fix `1em` → `em` regression from CSS::Writer (override write-num
for `em`/`ex` in Writer.rakumod)
- Remove %KNOWN-PROPS filter in !walk-decls so all known
properties are pre-scanned (catches CSS3 values like `position:
sticky`, `overflow: clip` that the grammar drops)
- Fix falsy-zero bug in !prop-exists (Raku `0` is falsy, causing
all rulesets at position 0 to always re-inject)
- Add %SHORTHAND map in !prop-exists to avoid re-injecting
longhands consolidated to shorthands by CSS::Properties
(e.g. `background-color` → `background`)
- Fix same falsy-zero bug in at-rule path of !prop-exists
0.0.1 2026-05-21T01:30:31+03:00
- Initial version