Skip to content

feat(stylex): add native StyleX compiler and pass - #127

Open
MarcelOlsen wants to merge 5 commits into
lovablelabs:mainfrom
MarcelOlsen:stylex
Open

MarcelOlsen wants to merge 5 commits into
lovablelabs:mainfrom
MarcelOlsen:stylex

Conversation

@MarcelOlsen

@MarcelOlsen MarcelOlsen commented Aug 31, 2026 •

Copy link
Copy Markdown

Adds native StyleX support to OJ's development and build pipelines, using the clean-room Rust compiler fru vendored at crates/stylex. The current compiler source is synced from lovable@d61e617fb93d174d892bbffb0dc83e2290c39508; the OJ workspace manifest remains adapted for this repository. Babel 0.19.0 is the compatibility oracle.

Development transforms OJ's parsed AST before lowering. Compiled rules travel with cached modules, populate the stylesheet registry, replace @stylex; in served CSS, and trigger CSS updates when gated modules change. The build pipeline uses a Rolldown string transform with a splice source map, then assembles and inserts the stylesheet after bundling. The pass auto-enables when the app depends on @stylexjs/stylex; the stylex config section, --stylex-config, or OJ_STYLEX_CONFIG supplies explicit settings.

{ "stylex": { "include": ["src/**"], "useCssLayers": true, "dev": true } }

The latest compiler sync reduces temporary allocations by iterating simple evaluated namespaces directly and representing ordinary single-property paths implicitly. Complex styles retain the existing validation and flattening pipeline. The library's RuleRegistry also shares prepared rules when repetition warrants it, returns to inline storage when repetition disappears, and avoids duplicate preparation on first emission. OJ currently calls the one-shot assembler, so those registry savings require a later integration change. The standalone CLI queue change is outside this vendored library.

Compiler measurements on a 3,103-module corpus, Apple M5 Max, Rust 1.95.0, one worker:

Production compilation metric Before After
Time inside create() 33.56 ms 28.86 ms
Allocation/reallocation calls 2,511,299 2,341,595
Cumulative allocated bytes 482.86 MB 464.89 MB

Total compiler instructions fell about 3%. The allocated-byte reduction is temporary allocation traffic; peak heap stayed unchanged. Stage timing and allocation counting used separate runs. Whole-OJ experiments using a separate AST-hook prototype found 0.7–1.5% fewer instructions with PGO, but no consistent wall-time improvement. Those are prototype measurements, not a claimed speedup for this PR's string build integration. The AST-hook adapter and whole-OJ PGO setup are separate work; native scope analysis remains opt-in through FRU_NATIVE_SCOPES=1.

The retained compiler snapshot produced byte-identical complete outputs before and after the optimizations across four corpora at one and eight workers and passed 5,325 live Babel comparisons in its source repository. That repository keeps the full conformance harness and integration fixtures. Tests in this sync pin numeric property order, the $$css insertion position, dynamic class paths, error ordering, and the registry's transition from repeated to unique rules.

Two known integration constraints remain:

  • OJ's experimental disk module cache is off by default. Its StyleX configuration salt and rule replay do not validate filesystem resolution dependencies before a hit is accepted. A local restart probe reproduced stale output after changing only the package name. Correct invalidation must account for configuration, aliases, and resolved module identity; imported theme contents should not become importer dependencies. The compiler has dependency validation, but this host cache does not yet use it.
  • Babel 0.19.0's malformed @position-try output is reproduced for compatibility. StyleX-substituted sheets therefore use Lightning CSS error recovery; other sheets remain strict.

@MarcelOlsen
MarcelOlsen force-pushed the stylex branch 4 times, most recently from b1e5b5e to 27f75b5 Compare August 31, 2026 16:42
MarcelOlsen and others added 4 commits September 4, 2026 16:41
Clean-room Rust compiler for StyleX, byte-identical to
@stylexjs/babel-plugin 0.19.0: identical rule metadata, identical
generated JavaScript, identical assembled CSS, or a hard error. Parity
is enforced by a differential harness (live Babel plugin as oracle,
5,325 job-modes, 0 FAIL, in CI) living with the compiler's source of
truth; this copy is vendored from there (lovable@2ccd45e9ac6) and is not
edited in place. Unsupported features hard-error rather than diverge
(runtimeInjection: true, haste, legacy option modes, Flow-only syntax).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lre78sAtefzg8D2PJVpNxa
Dev pipeline: a pre-Transformer AST pass on gated modules (path globs +
SIMD content scan; one parse per module, synthesized nodes carry
replaced-node spans so sourcemaps stay valid), rules persisted on
CachedModule and replayed on cache hits so warm starts rebuild the
registry, @stylex; directive substitution into served css, css-update
repush on gated-module changes, /@oj/stylex.css debug route.

Build: a rolldown transform plugin (string seam with a real splice
sourcemap); the directive rides sidecar phases as an unknown at-rule
sentinel and the assembled sheet splices in before emit_split_css.

Enabled by a stylex config section (--stylex-config / OJ_STYLEX_CONFIG
override), or automatically when package.json depends on
@stylexjs/stylex. Substituted sheets parse with lightningcss
error_recovery: upstream emits @position-try rules browsers skip and the
compiler reproduces upstream byte-for-byte.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lre78sAtefzg8D2PJVpNxa
Compiler-only update of the vendored crate (perf waves 1 and 2 on the
source of truth), byte-identical to the previous copy on every corpus of
the conformance harness: both output backends over the synthetic,
community and real-app corpora incl. dev modes, style-free files, four
assemble feeds and `compile`. Measured against the previous vendored copy
(interleaved fresh processes, min of 7): astryx -37% instructions retired,
the real app in dev mode -31%, the AST backend oj's dev path uses -19%,
and the one-shot assembler on a 72k-rule registry feed 45ms -> 5ms with
peak RSS 88MB -> 42MB. No call-site changes: the pass and the registry use
the crate exactly as before.

Three seam test literals follow the crate's data model (`StylexRule` text
fields are `Arc<str>`, `const_val` is boxed); no production code in the
seams changes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019zUixZf3Yr7Rti83WUkyRW
Compiler-only update of the vendored crate (perf wave 3 on the source of
truth): a prefetching reader for path jobs, walk fusion in the transform,
and a flagged native scope-analysis spike that stays off by default. The
compiler now depends on oxc_str directly, pinned to the workspace oxc line.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q8a3Twu8KWSQpkJbXPjwwW
Sync the compiler allocation reductions and adaptive prepared-rule storage from the source of truth. Preserve the OJ workspace manifest and existing integration call sites.
@MarcelOlsen MarcelOlsen changed the title feat(stylex): native StyleX compiler and pass feat(stylex): add native StyleX compiler and pass Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant