fix: make processStylexRules sort comparator deterministic#1513
Merged
mellyeliu merged 2 commits intofacebook:mainfrom Mar 13, 2026
Merged
fix: make processStylexRules sort comparator deterministic#1513mellyeliu merged 2 commits intofacebook:mainfrom
mellyeliu merged 2 commits intofacebook:mainfrom
Conversation
The sort comparator in `processStylexRules` had two bugs causing
non-deterministic CSS rule ordering when input order varies (which
happens in parallel bundlers like Vite/Rolldown where module transform
order is non-deterministic):
1. Asymmetric @-rule check: the condition
`rule1.startsWith('@') && !rule2.startsWith('@')` only entered the
query-string comparison path when the first rule had an @ prefix,
not the second. This made compare(A, B) use different logic than
compare(B, A).
2. Transitivity violation: @media/@container/@starting-style rules used
query-string comparison, but var()-wrapped and plain/pseudo-element
rules used property-string comparison. Comparing across categories
used different data for transitive pairs, violating the requirement
that if A < B and B < C then A < C.
Replace the multi-path comparison with a simple total order:
priority -> CSS property -> full rule string as tiebreaker.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@matclayton is attempting to deploy a commit to the Meta Open Source Team on Vercel. A member of the Team first needs to authorize it. |
…tests Makes the expected CSS output visible in the test file, which is more useful for reviewers and makes regressions easier to diagnose.
mellyeliu
approved these changes
Mar 13, 2026
Member
mellyeliu
left a comment
There was a problem hiding this comment.
Cleaned up the test assertions a bit. Thanks so much for the fix!
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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.
Sorry for what is clearly an Claude/AI generated patch, but we've been seeing issues with StyleX (across multiple recent versions) and it creating non-deterministic ordering of the CSS Output. The same code compiled multiple times, gives different outputs. After having Claude work through the problem, we've manually validated the patch below against our code base, and observed the output to now be deterministic when its applied against 0.18.1 using the unplugin plugin with vite. On the surface the below fits with our understanding of the issue at hand, however I'm not familiar with the detailed inner workings of the plugin.
Summary
The sort comparator in
processStylexRuleshas two bugs that cause non-deterministic CSS rule ordering across builds. When a parallel bundler (Vite, Rolldown, Rollup) transforms modules in a different order between builds, identical source code produces different CSS output — breaking content hashes and causing unnecessary cache invalidation.Bug 1: Asymmetric @-rule check
When
rule2has@butrule1doesn't, it falls through to property-only comparison. This meanscompare(A, B)andcompare(B, A)can use different comparison logic — an antisymmetry violation that makes the sort unstable.Bug 2: Transitivity violation
Three categories of rules use different comparison paths:
@media/@container/@starting-stylerules -> query-string comparisonvar(--...)-wrapped rules -> property-string comparison (no@prefix)Comparing across categories uses different data for the same transitive relationship. For example, given rules A (pseudo-element), B (
@starting-style), C (var(--...)-wrapped):@)@)@)This violates transitivity (
A < BandB < Cdoes not guaranteeA < C), causingArray.prototype.sortto produce different results depending on which pairs it compares — which depends on input order.Fix
Replace the split query/property comparison with a simple, transitive total order:
Sort order: priority -> CSS property -> full rule string. The full
ltrrule string as final tiebreaker guarantees a total order (no two distinct rules compare as equal), eliminating all input-order dependence.Impact
useLegacyClassnamesSortpath is unchangedTest Plan
Added two new tests in
transform-process-test.js:@media,@container,@starting-style,var()-wrapped, and pseudo-element rules at the same priority, then processes them in original, reversed, and shuffled orders — asserts all produce identical outputBoth tests fail on the current code and pass with this fix.
Discovered and validated in a production Vite + Rolldown build environment where the non-determinism caused CSS file hashes to change on every build despite no code changes.