Minize css with at property - #320
Merged
Merged
Conversation
ameerf-wix
marked this pull request as ready for review
September 6, 2026 02:23
ydaniv
approved these changes
Sep 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
generate()CSS output — whatminize_css_with_at_propertybuys usFour stacked changes to the CSS emitted by
generate(). Numbers below come from runninggenerate(config)over 11 example configs at each revision, with@wix/motion-presetsregistered so named effects expand to real keyframes.
1.
@property+ short global names + one shared lists rulePer-target hashed custom properties become short global indexed names, the per-target
coordinated-list rules collapse into a single rule with a joined selector list, and the
repeated inline
var(--x, fallback)fallbacks move into one@propertypreamble:The names no longer depend on the target, so the cost is paid once per page instead of
once per target.
2. Default elision
Once
@propertysupplies aninitial-value, writing that same value back is redundant.A custom property is now skipped when its value is exactly the default and nothing set
that same property to a non-default value earlier on the same target (tracked as a
per-target
assignedset).In practice this deletes the
--anm-cmps-N: replace; --anm-tmln-N: auto; --anm-rng-N: normal;triplet from every ordinary time-based animation — the common case — while leaving
scroll-driven effects, which set a real timeline and range, untouched. Across the 11
example configs: 73 declarations / 1825 bytes removed, with no change to slot
allocation or to the
@propertypreamble.Both halves of the condition are load-bearing. The lists are coordinated positionally, so
a slot holding two animations must contribute two entries to each list; eliding
--anm-tmln-0: auto, autoin favour of the one-entryinitial-valuewould let CSS cyclethe shorter list and hand the wrong timeline to a later animation. Comparing against the
exact single-value default is what keeps the arity honest;
assignedis what keeps thecascade honest.
3. Single-slot sequence collapse
Slots exist because the effects of a sequence run together: each needs its own entry in
the target's
animationlist, so effect N writes--anm-slot-Nand a per-targetcoordinated-list rule assembles the slots into the interaction-level
--anm-M. A targetthat appears only once in the sequence needs none of that machinery — one value, one
property — and can write
--anm-Mdirectly, exactly as a non-sequence effect does:parseSequencepre-counts slot usage per (target, list kind) before walking the effects —the tally is an upper bound, so a count of ≤ 1 is a guarantee, not a guess — and threads
useSlots: Record<ListKind, boolean>down togetCustomProps. Top-level effects passNO_SLOTS, which is the old non-sequence path unchanged.Each collapsed target drops a four-declaration coordinated-list rule, and once no target
in the config needs slots at all the four
@property --*-slot-Nregistrations go with it.The decision is per target and per list kind, so a sequence that repeats one target
and touches another once keeps slots for the first and collapses the second.
4. Structural refactor (no output change)
src/core/css.ts/cssUtils.tscleanup — all example outputs are byte-identical beforeand after:
TargetContext's ten duplicated counters (one set per list kind) collapse into twoListCounters, with names that distinguish the two namespaces they index(
listIndexfor the shorthand list,slotCursorfor sequence slots). Everyname === 'transition' ? … : …ternary becametarget[listKind(name)].endEffect/endSequence/endInteractionhelpers.buildSequenceListsRulefrom ten positional params to two,parseEffect/parseSequence/parseInteractionfrom 9/8/7 to a sharedGenerateContextbuilt once.effectToCSSreports what it wrote instead of mutating shared state and letting thecaller infer it — which also makes inert effects uniformly not consume a slot, rather
than doing so only when an earlier sequence happened to fill that index.
Size
Characters of generated CSS, master → this branch:
Whitespace-normalized (leading indentation stripped from both sides, isolating the
structural change from the pretty-printing that landed alongside it) the total is
46973 → 36720, -10253 (-22%).
The
@propertypreamble is a fixed per-page cost, so the small single-interactionfixtures come out slightly larger; the elision pays back 58% of that overhead, leaving
~4% over master for examples 01–08 combined. The trade turns clearly positive as soon as
a page has several targets —
09-page-scale(25 targets, 10 with an extra hovertransition) is the realistic shape, and it drops -29% (-31% whitespace-normalized).
The collapse only moves examples 10 and 11, the two fixtures that contain a sequence with
a single-slot target, but it moves them hard:
10-sequence-across-targets— one sequence,one effect per target, the shape a staggered entrance actually has — drops a further
-972 (-30%) on top of the elision, and is the only example where the slot
@propertyregistrations disappear entirely.Tests
packages/interact: 477 passed / 477 — 473 before the branch, 474 after the elisionand refactor, 477 after the collapse. Rest of the monorepo is green:
@wix/motion342,@wix/motion-presets516,@wix/interact-validate170,@wix/splittext131.Eight assertions in
test/css.spec.tschanged for the elision — none a renderingregression. Each either asserted a default override the branch now deliberately omits, or
used an inert effect as an incidental fixture for something unrelated (one of those was
passing vacuously and now can't). Two new tests pin the two halves of the elision guard,
and one covers
buildSequenceListsRulewith both list kinds at non-zero indices.For the collapse, one existing test was re-baselined —
should apply sequence-level conditions to the coordinated-list ruleused a single-effectsequence, which now emits no coordinated-list rule, so its fixture grew a second effect on
the same target and its assertions are unchanged — and three were added: the animation
kind, the transition kind, and the mixed shape where only one of two targets collapses.
One intended behaviour change worth calling out: an interaction whose only effect is inert
used to emit four reset declarations plus a lists rule, and now emits an empty stylesheet —
correct, since nothing ever turned those slots on, but visible to anything inspecting
_generate().cssRules.ee4fa44re-baselines the one CSS assertion inpackages/splittext/test/splitText.integration.spec.tsonto the indented output thisbranch emits. That spec imports
generatefrom the built@wix/interact, so it needsa rebuilt
dist/to pass locally; CI runsyarn buildbeforeyarn test, so it isunaffected.
Unrelated pre-existing issue:
packages/interact/tsconfig.jsonincludes onlysrc/**, soyarn lintnever typechecks test files;test/css.spec.tshas aTS2537that no CI stepcurrently surfaces.