mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
main
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8e28314c42 |
[rust-compiler] Use rustc-hash FxHasher for all maps and sets (#36811)
Swaps the default hashers across the Rust compiler crates for rustc-hash's faster `FxHasher`. ## Why `FxHasher` is a simple, fast, non-cryptographic hasher. std's default (SipHash) is DoS-resistant but heavy — the compiler keys maps/sets almost entirely on small integer IDs (`IdentifierId`, `BlockId`, `ScopeId`, …) where that protection buys nothing. Switching gives: - **Performance** — faster hashing on the hot ID-keyed maps/sets used throughout the passes. - **Slight binary size reduction** — drops the heavy SipHash hasher pulled in by std's `HashMap`/`HashSet`. ## Changes - `std::collections::HashMap`/`HashSet` → `rustc_hash::FxHashMap`/`FxHashSet` - `indexmap` `IndexMap`/`IndexSet` now use `FxBuildHasher`, inlined as `IndexMap<K, V, FxBuildHasher>` (no `FxIndexMap` type aliases) - `rustc-hash = "2"` added to all 12 crate manifests - `rustc_hash`/`indexmap` imports consolidated into single brace imports at the top of each file The change is serde-neutral: `FxHashMap` and `IndexMap<_, _, FxBuildHasher>` serialize to byte-identical JSON, and `IndexMap` keeps insertion order. `cargo check`, `cargo fmt --check`, and `cargo test` all pass with no warnings. |
||
|
|
c9f0af7565 |
[rust-compiler] Represent string values as JsString (WTF-16 aware) (#36731)
Last of the boundary trilogy, after #36729 and #36730 (both merged); rebased onto main as a standalone 3-commit change. JS strings are WTF-16: a lone surrogate in source (`"\uD83E"`) is a legal string value, but Rust `String` is UTF-8 and cannot hold it. The compiler previously leaned on the napi bridge's `__SURROGATE_XXXX__` marker encoding end to end, so the core compiler compared, concatenated, and constant-folded marker text as if it were the actual string value. `JsString` (in `react_compiler_diagnostics`, our lowest layer) holds the common well-formed case as a plain UTF-8 `String` with zero overhead and falls back to UTF-16 code units only for ill-formed values. `StringLiteral.value` and `PrimitiveValue::String` carry it through the pipeline; constant folding concatenates via code units so split surrogate halves re-pair exactly as they do in JS; markers are emitted only at the napi edge, where the babel bridge requires them (serde_json can neither parse nor emit a lone `\uXXXX` escape). The representation is encapsulated behind an opaque struct (private `Repr` enum, borrowed `JsStringRef` view via `as_ref`) so the "well-formed values are always UTF-8" invariant that makes the derived `PartialEq`/`Hash` sound holds by construction, per review feedback. The marker decoder scans byte-wise (an earlier draft range-sliced at fixed offsets and panicked on multibyte UTF-8 following `__SURROGATE_`), validates hex digits, and accepts uppercase only, exactly mirroring what the bridge emits; lowercase marker-shaped user text survives verbatim. The HIR debug printer renders unpaired surrogates as `\uXXXX` escapes byte-identical to the TS printer. This closes the lone-surrogate divergence on the e2e harness. Verified on the rebased branch: cargo workspace tests, Rust snap channel 1804/1804, and HIR + Code parity on the lone-surrogate fixture through the comparison harness. |
||
|
|
dbc5501a7d |
[compiler] Remove OXC and SWC plugins in favor of them being handled by those projects (#36743)
I believe the consensus is that the OXC and SWC plugins should live in those projects, and instead consume the React compiler as a crate --------- Co-authored-by: mvitousek <[email protected]> |
||
|
|
ed0367f4c0 |
[rust-compiler] Carry uninspected AST subtrees as raw JSON text (#36730)
Stacked on #36729 (upstream rejects cross-fork base branches, so this targets main as a draft; the first commit belongs to the parent PR. Review the last three commits. Will rebase and mark ready when #36729 lands.) Unmodeled AST subtrees (type annotations, class bodies, unknown statements) were stored as `serde_json::Value` trees: every node allocated through a `Map<String, Value>`, and pass-through subtrees were repeatedly traversed by code that never looks inside them. They are now `RawNode`, a newtype over `Box<RawValue>` holding the original JSON text verbatim. Design notes, since two obvious alternatives fail: - Bare `RawValue` fields break under `#[serde(tag = "type")]` enums: internally-tagged deserialization buffers content into serde's private `Content` tree, which `RawValue` cannot read from. `RawNode::deserialize` instead streams whatever deserializer it is handed through `serde_transcode` into a fresh JSON string, which works behind tagged enums, `flatten`, and `from_value` alike. - Default-limit reparses break deep ASTs: internal `RawNode` reparse sites use `from_json_str_unbounded` (disables serde_json's 128-level recursion limit, matching how the top-level parse is configured); regression-tested with a 400-deep statement chain. `parse_value` fails loudly on malformed text rather than masking corruption with `Value::Null`; RawNode holds valid JSON by construction. Size-neutral in the shipped binary; the win is structural (no speculative `Value` trees on the hot path, pass-through subtrees stay untouched text). Verified on this exact tree: cargo workspace tests, both snap channels 1804/1804. --------- Co-authored-by: Boshen <[email protected]> |
||
|
|
496f32baab |
[rust-compiler] Return the compiled AST by value instead of JSON (#36729)
`compile_program` serialized the transformed `File` to a JSON string and every in-process consumer immediately deserialized it back, so each oxc/swc compile paid a full serialize/parse round-trip of the entire program for nothing. `CompileResult::Success.ast` is now `Option<react_compiler_ast::File>`; the napi bridge keeps its JSON boundary by serializing at the edge, while the oxc and swc frontends consume the typed AST directly. This ports the typed-AST patch the Oxc team carries on their fork (co-authored with Boshen), which also makes their integration patch-free going forward. Verified: cargo workspace tests, both snap channels 1804/1804, e2e comparison harness at parity baseline (1802 byte-identical, fbt local-require the one known divergence). First of a stack of three with #36730 and #36731. Co-authored-by: Boshen <[email protected]> |
||
|
|
31e757b24a |
[rust-compiler] Drop the regex dependency from the napi binary (#36727)
The regex crate services exactly one pattern, the dynamic-gating directive `^use memo if\(([^\)]*)\)$`, while costing ~570KB of `.text` (regex + regex_automata + regex_syntax + aho_corasick) in the shipped binary; after LTO the removal saves ~1MB through dead-code cascade. Replaced with an exact hand parse: strip the `use memo if(` prefix and `)` suffix, reject conditions containing a close paren. Equivalence with the TS `DYNAMIC_GATING_DIRECTIVE` regex verified on the full gating fixture directory: 30/30 byte-identical TS-vs-Rust on the e2e comparison harness, dynamic-gating snap fixtures green. Independent of #36726; the two compose to take the default release binary from 11.2MB to 6.1MB. |
||
|
|
4deb1057b8 |
[compiler] Port React Compiler to Rust (#36173)
This is an experimental, work-in-progress port of React Compiler to Rust. Key points: * Work-in-progress - we are sharing early, prior to testing internally at Meta, to get feedback from partners in parallel with continued development. * No builds available yet, you'll have to do some hacking if you want to try this. * All fixtures pass, no known gaps but there may be lurking bugs. * The architecture was heavily guided by humans (me, @josephsavona) but majority coded by AI. I was very hands-on in setting the architecture, the testing and verification strategy, incremental migration approach, etc. I also kept a close eye on the code and spent a decent amount of time going back and forth to get code quality to a decent level. * The public API is basically "Rust Babel AST" + Scope Info in, Rust Babel AST out. We use a Rust representation of the Babel AST as our "public API", as it were, and then each integration (Babel, OXC, SWC) converts to/from their native representation. For now integrations must also provide scope information - in the future React Compiler may compute bindings and references itself from the AST. * Internally, the Rust version uses the same architecture as the TypeScript version. The compiler converts from the AST into our own intermediate representation (HIR, short for High-level Intermediate Representation) which uses a control-flow graph (CFG) and single-static assignment (SSA). We go through the same series of passes, with the same overall algorithms. It's very much a pass-by-pass port. The main differences are in the data representation - using arena-like structures (and indices into these arenas) to work within Rust's borrowing system. * Early performance numbers are derived from AI and i haven't spent much time validating the benchmark setup, beyond the fact that the optimization opportunities it discovered made complete sense and the fixes were right. With that caveat, itt does appear that the Rust version is quite fast already: 3x faster when operating as a Babel plugin. The serialization cost is quite high, but the actual transformation logic is ~10x faster, so it's net faster. Native integrations (oxc, swc) should be even faster. * There are 3 integrations right now: an alternative Babel plugin (which will eventually get removed as we integrate into babel-plugin-react-compiler), and examples of what OXC and SWC integrations could look like (see react_compiler_oxc and react_compiler_swc crates). correctness: * all 1725 fixtures pass in snap when comparing the temporary rust version of the plugin with the main version. this compares generated code output as well as errors. * all fixtures also pass a full comparison of the per-pass compiler intermediate representation — the intermediate state (including log events and errors) are ~identical after every single pass (modulo some normalization of ids) * The OXC and SWC example integrations seem to be working well, though i haven't manually verified this to the same extent as i have the Babel integration. development: * `yarn snap --rust` is the primary test suite, testing that we error or compile as expected. It does not test the inner state of the compiler along the way, though, making it less suitable for finding subtle logic gaps btw the TS and Rust versions. It's also Babel based, making it less easy to test OXC and SWC integrations. * `compiler/scripts/test-e2e.sh` is an e2e test of all 3 variants (babel wrapper around Rust, OXC/SWC integrations) against the TS implementation. This does a partial comparison, focused on final output code only (doesn't test error details etc). Useful for getting the swc and oxc integrations closer to parity. * `compiler/script/test-rust-port.sh` does detailed testing of the internal compiler state after each pass, in addition to checking the final output code. This is the key script used to port the compiler, ensuring not just that the output was the same but that each pass was capturing all the same detail. This script can be pointed at *any* directory of JS files, which we expect to use for internal testing at Meta. ## For Partners We're excited to partner with teams to integrate the Rust version of React Compiler into other tools, like OXC and SWC. If you're interested in working with us on this, the best place to start is by taking a look at the react_compiler_swc and react_compiler_oxc crates. These give you an idea of the API shape that we're thinking of. Note that the conversion from any AST into our HIR is complex, and we can only maintain one version. Hence we've aligned on using a Babel-like AST as our public API. Another key point is that we don't yet implement our own scope analysis (since the TS version of the compiler relied on Babel's scope analysis), so for now we require that the scope data be serialized. It's a denormalized graph, and some metadata has to be stored to associate nodes with scopes. We're open to feedback about the AST and scope representation - we iterated a bit just to get things to work, but it can be more optimal. Key changes that we are considering: * Currently the compiler returns `Option<Program>`, which is `Some` if anything changed. This requires replacing the entire program. We plan to change this to return a series of patches to apply, in a form that is reasonably usable and efficient for all the integrations we care about (Babel, OXC, SWC, etc). * The Rust representation of the Babel AST is fine enough, but we could make it more optimal by doing arena allocation. We also plan to change the string representation to smol_str. * The scope representation, and association of data btw AST and scope, is very much a first pass approach that is good enough. We expect to implement our own scope resolution, though, so we hopefully won't need to iterate on the scope representation and can just throw it away. In terms of the shape of the integration, we anticipate that each integration would have the following: * Implementor repo (OXC, SWC, etc): lightweight code transform and lint pipeline integration that delegates to `crates/react_compiler_<name>` from our repo * Our repo: one crate per implementor, eg react_compiler_swc, react_compiler_oxc, where most of the logic lives. This setup lets us make changes to the integration layer easily within our repo. Feedback appreciated! --------- Co-authored-by: Joe Savona <[email protected]> Co-authored-by: Mike Vitousek <[email protected]> Co-authored-by: Mike Vitousek <[email protected]> Co-authored-by: lauren <[email protected]> Co-authored-by: lauren <[email protected]> |