## Summary
- Add a manual `workflow_dispatch` workflow that publishes only
`react-devtools-cdt-mcp`.
- Match the runtime release security model: empty default permissions,
checkout of `github.sha` only, protected `npm` environment, Node 24, and
OIDC trusted publishing (no `NPM_TOKEN`).
Stacked on https://github.com/react/react/pull/37500
## Test plan
- [ ] Open the workflow in GitHub Actions and confirm it is
`workflow_dispatch` only.
- [ ] Dry-run dispatch after the version bump lands, once npm trusted
publishing is configured for this package.
Sizebot compared the pull request head build against the build of
`pull_request.base.sha`, which is the tip of the base branch at event
time, not the commit the pull request diverged from. The field's
semantics are undocumented in GitHub's API schema (the OpenAPI
description types it as a bare string); the observed behavior and the
compare API's `merge_base_commit` confirm the difference.
The difference between `pull_request.base.sha` and the merge base is
confirmed with an example in https://github.com/react/react/pull/37356
The sizebot job now resolves the merge-base through the compare API and
downloads the base build for that commit instead, so the report only
ever contains the pull request's own changes. The job gains `contents:
read` for the compare call.
When no base build can be downloaded for the merge-base, for example
because its artifacts aged out of the retention window or its run
failed, the sizebot job records a `base-build-not-found` result instead
of failing immediately. `render-comment.js` on the default branch
renders that as a warning comment naming the base commit and writes the
`sizebot-problem.txt` marker, so the comment workflow fails its check
after posting the warning, the same pattern already used for build
configuration drift. The sizebot job itself intentionally stays green: a
failed run would make the renderer discard the results and mask the
warning with a generic "did not complete" message.
Co-authored-by: Claude Code (kimi-k3[1m]) <noreply@anthropic.com>
## Summary
- configure the React Compiler Rust crates as a Cargo workspace with
shared package metadata and versioned internal dependencies
- add workflows to open a signed version-bump pull request and publish
the workspace through crates.io trusted publishing
- add release scripts and contributor documentation for versioning,
validation, and publishing
## Test plan
- Run `cargo check --locked --workspace` from `compiler/`.
- Run **(Compiler) Publish Rust Crates** from the Actions tab with **Dry
run** enabled; confirm every workspace crate packages successfully
without publishing.
- Run **(Compiler) Update Rust Crate Version** with a test version;
confirm it verifies one shared version and opens a signed version-bump
pull request containing the updated workspace manifest and lockfile.
The build workers can restore different weights if they don't restore
the cache at the exact same time. The more time difference, the more
likely they restore different weights which could lead to some bundles
not being built at all (e.g.
https://github.com/react/react/actions/runs/32822851267).
A new job now restores the latest entry once per run and republishes it
as a per-run artifact. The new job sits adds no wall time because it
runs in parallel with `runtime_compiler_node_modules_cache`, which
already gates the build workers and takes about 30 seconds on a cache
hit, while the resolve job does strictly less work (no checkout, no Node
setup, a 5KB cache entry instead of the node_modules restore), so it
finishes first and the build workers start at the same time as before.
Co-authored-by: Claude Code (kimi-k3[1m]) <noreply@anthropic.com>
In `build_and_lint`, `actions/setup-java` ran sequentially between
setup-node and the node_modules cache restore, but Java is only needed
by `yarn build` for the Closure Compiler bundles. This change marks the
setup-java step as a background step.
Setting up Java is mostly network (download) and CPU (unpack). It
overlaps with installing/restoring node_modules which is network and FS
work. So we aren't competing for resources that would make concurrently
running steps moot.
Co-authored-by: Claude Code (kimi-k3[1m]) <noreply@anthropic.com>
Every downstream job in `runtime_build_and_test.yml` restored the 50
`_build_*` artifacts with `actions/download-artifact` only after
setup-node, the node_modules cache restore, and any installs had
completed, even though the download is independent of all of them.
This change marks the download as a [background
step](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idstepsbackground)
started immediately after checkout, and adds an explicit `wait:
download_build` before the first step that reads `build/`.
The download starts after checkout because `actions/checkout` runs `git
clean`, which would wipe a previously downloaded `build/` directory. The
`sizebot` job is unchanged because its base-build download also writes
`./build` and would collide with a concurrent artifact restore.
This only shaves of a few seconds from wall time. It's more about
establishing precedent.
Co-authored-by: Claude Code (kimi-k3[1m]) <noreply@anthropic.com>
`build_and_lint` assigns its `[bundle, bundleType]` pairs to 25 workers
per channel by round-robin, which leaves the slowest worker with 56-62
seconds of rollup time while the mean worker has 40-42 seconds (measured
from the timestamped `BUILDING`/`COMPLETE` lines in recent `main` run
logs).
This change shards by measured build time instead, and the measurement
maintains itself:
`yarn build` writes the timing results into `build/__shard_timings__/`,
which rides along inside the existing per-worker artifacts.
`process_artifacts_combined`, which already downloads all 50 artifacts
and is off the critical path, combines them into `build-weights.json`
and saves it to the actions cache under a per-run key. Readers restore
the most recent entry via a `restore-keys` prefix. Only pushes can save
to cache keys that PRs can read, so pull request runs benefit from the
weights but cannot poison them.
The new measurement is always written verbatim rather than merged with
previous weights, so removed bundles drop out instead of accumulating.
A per-bundle diff against the previous weights is logged so that we can
monitor whether single-run variance is too high, in which case shards
should be determined from timings across the last N runs instead. Even
on this PR a perfect prediction would've only gained us ?s for the
slowest shard.
Co-authored-by: Claude Code (kimi-k3[1m]) <noreply@anthropic.com>
The build size comparison comment was posted by Danger, which
authenticated with a personal access token hardcoded in
`scripts/tasks/danger.js`. That token has since been revoked, so sizebot
has been posting nothing at all (due to e.g.
https://github.com/react/react/actions/runs/32181295467/job/95855395224?pr=37315).
This change rebuilds it on the short-lived `GITHUB_TOKEN` that Actions
mints per run and a new workflow only responsible for rendering
untrusted JSON input as markdown in a PR comment.
A straight token swap would not have worked. Fork pull requests did
receive sizebot comments, but only because the token was in checked-out
source: the sizebot job runs on the `pull_request` trigger, where a
fork's `GITHUB_TOKEN` is read-only and cannot comment. The comment
therefore moves to a new `workflow_run` workflow,
`runtime_sizebot_comment.yml`, which runs in this repository with a
writable token no matter where the pull request came from. It posts a
placeholder when a build is requested and rewrites it in place when the
build completes, fails, is cancelled, or is held for maintainer
approval.
The measurement stays on the unprivileged side of that boundary which
are recorded as raw sizes into a `sizebot-results` artifact, and the new
workflow downloads only that JSON and renders it from a default-branch
checkout. The job holding `pull-requests: write` never unpacks a build
produced by a fork, which matters because the existing base-build
download justifies using an unverified artifact on the grounds that the
job has restricted permissions. Thresholds, the critical bundle list,
and the comment template all live on the trusted side, and the renderer
validates every field it reads out of the artifact so that a crafted
build path cannot inject markdown. The pull request number is resolved
from the API rather than from the artifact, since a number read from
fork-controlled data would let any contributor post a bot comment on an
arbitrary pull request.
Resolving that number needs a branch lookup rather than any of the
obvious approaches. `workflow_run.pull_requests` is empty for fork runs,
and neither `commits/{sha}/pulls` nor the search API indexes fork pull
request head commits, so the workflow looks the pull request up by
`owner:ref` instead.
A comment is only ever left alone in one situation: when it already
describes the pull request's current head and the event being handled
belongs to an older commit. Everything else is written, and marked stale
whenever the report does not describe the current head. That single rule
covers both an old run finishing after a force push and a new build
superseding a report already on display, and in the latter case the
previous numbers stay visible instead of being blanked back to a
placeholder.
The results file carries a `version` field. Its writer is whatever
`compare-sizes.js` a pull request branch happens to carry, while its
reader is on the default branch, so the two can mismatch and the
renderer needs to be able to say so instead of misrendering a table.
Porting the table fixed a longstanding bug in `change()`. Testing
`decimal < 0.0001` reported every size decrease as unchanged, which is
why `signDisplay: 'exceptZero'` never had a negative number to render: a
709.04 kB to 708.68 kB drop printed as `=`. It now compares the
magnitude.
Co-authored-by: Claude Code (kimi-k3[1m]) <noreply@anthropic.com>
> [!NOTE]
> I had to invalidate `node_modules` cache keys on CI, because we are
now using `react-devtools-facade` package and we need to create a
symlink to it, otherwise Flow will fail. We use a hash value of
`yarn.lock`, but it's not invalidated when a package inside yarn
workspace is using another private package.
---
This change introduces new `react-devtools-cdt-mcp` package, which is
responsible for installing React tools for `chrome-devtools-mcp` server.
It is using `react-devtools-facade` as a dependency: it doesn't own the
implementation of these tools, it just relays Facade's tools to
`chrome-devtools-mcp` format.
When CDT MCP navigates to a fresh page, it emits unique
`devtoolsdiscovery` method, that has `respondWith` method, which is the
integration point between runtime libraries and MCP server.
`react-devtools-cdt-mcp` is the owner for this integration. More details
are available here -
https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/docs/third-party-developer-tools.md.
Here is the list of tools that are being installed:
- `react_get_component_tree`
- `react_get_component_by_uid`
- `react_find_components`
- `react_get_component_source`
- `react_get_owner_stack_trace`
- `react_get_owner_stack`
- `react_start_profiling`
- `react_stop_profiling `
- `react_get_trace_overview`
- `react_get_commit_report`
---
Quick demo how to test the package with `chrome-devtools-mcp` CLI. Steps
are also mentioned in `README` file:
https://github.com/user-attachments/assets/af9fefce-03f0-4ebd-a94f-e9dd72b87f4d
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 <joesavona@meta.com>
Co-authored-by: Mike Vitousek <mvitousek@fb.com>
Co-authored-by: Mike Vitousek <mvitousek@meta.com>
Co-authored-by: lauren <poteto@users.noreply.github.com>
Co-authored-by: lauren <lauren@anysphere.co>
<!--
Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.
Before submitting a pull request, please make sure the following is
done:
1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
10. If you haven't already, complete the CLA.
Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->
## Summary
<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->
- Adds a new react-flight-server-fb package providing RSC Flight
bindings for Meta's internal bundler stack
- Unlike webpack/turbopack integrations, this uses no manifest. Module
metadata is self-contained in ClientReference objects and sent over the
wire as-is
- Registers dom-browser-fb and dom-node-fb host configs for Rollup
builds targeting FB_WWW_DEV and FB_WWW_PROD
Key design differences from other bundler
- No build-time manifest
- Module IDs use Haste module names (e.g. `"MyComponent"`), with named
exports encoded as `"Module#export"`, rather than file paths resolved
through a manifest
- Client-side loading uses `Bootloader.handlePayload()` +
`JSResource().load()`
- `resolveClientReferenceMetadata` and `resolveClientReference` are
pass-throughs
## How did you test this change?
<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
If you leave this empty, your PR will very likely be closed.
-->
E2E integration test is set up on Meta's internal system.
## Summary
PR #36285 deleted the Paper (legacy) renderer, including the shim file
`scripts/rollup/shims/react-native/ReactNative.js`. However, the
`runtime_commit_artifacts` workflow still tries to `rm` this file after
moving build artifacts into `compiled-rn/`. Since the file no longer
exists in the build output, `rm` (without `-f`) fails and kills the
entire step.
This has caused **every run of the Commit Artifacts workflow to fail
since #36285 landed on April 16**, blocking both `builds/facebook-www`
and `builds/facebook-fbsource` branches from receiving new build
artifacts. This in turn blocks DiffTrain from syncing React changes into
Meta's internal monorepo.
We're currently hardcoding experimental options to
`eslint-plugin-react-hooks`. This blocks the release on features that
might not be ready.
This PR extends the ReactFeatureFlag infra to support flags for
`eslint-plugin-react-hooks`. An alternative would be to create a
separate flag system for build tools, but for now we have a small number
of these and reusing existing infra seems like the simplest approach.
I ran a full `yarn build` and checked the output resolved the flag
values as expected:
_build/oss-stable-semver/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js_
```js
var eprh_enableUseKeyedStateCompilerLint = false;
var eprh_enableVerboseNoSetStateInEffectCompilerLint = false;
var eprh_enableExhaustiveEffectDependenciesCompilerLint = 'off';
```
_build/facebook-www/ESLintPluginReactHooks-dev.classic.js_
```js
var eprh_enableUseKeyedStateCompilerLint = true;
var eprh_enableVerboseNoSetStateInEffectCompilerLint = true;
var eprh_enableExhaustiveEffectDependenciesCompilerLint = 'extra-only';
```
---------
Co-authored-by: lauren <lauren@anysphere.co>
## Summary
ESLint v10.0.0 was released on February 7, 2026. The current
`peerDependencies` for `eslint-plugin-react-hooks` only allows up to
`^9.0.0`, which causes peer dependency warnings when installing with
ESLint v10.
This PR:
- Adds `^10.0.0` to the eslint peer dependency range
- Adds `eslint-v10` to devDependencies for testing
- Adds an `eslint-v10` e2e fixture (based on the existing `eslint-v9`
fixture)
ESLint v10's main breaking changes (removal of legacy eslintrc config,
deprecated context methods) don't affect this plugin - flat config is
already supported since v7.0.0, and the deprecated APIs already have
fallbacks in place.
## How did you test this change?
Ran the existing unit test suite:
```
cd packages/eslint-plugin-react-hooks && yarn test
```
All 5082 tests passed.
Requires full error message in assert helpers.
Some of the error messages we asset on add a native javascript stack
trace, which would be a pain to add to the messages and maintain. This
PR allows you to just add `\n in <stack>` placeholder to the error
message to denote a native stack trace is present in the message.
---
Note: i vibe coded this so it was a pain to backtrack this to break this
into a stack, I tried and gave up, sorry.
DevTools has ~45 test files which don't distribute well across 10
shards,
causing shard 3 to run 2x slower than others (104s vs ~50s). This moves
DevTools build tests to a separate job with 3 shards for better load
balancing.
Now that RN is only on the New Architecture, we can stop stop syncing
the legacy React Native renderers.
In this diff, I just stop syncing them. In a follow up I'll delete the
code for them so only Fabric is left.
This will also allow us to remove the `enableLegacyMode` feature flag.
The workflow was correctly publishing the package(s) specified in
`only`, but due to incorrect logic it would also run the 'Publish all
packages' step.
I happened to notice that I forgot to cache playwright in
run_devtools_e2e_tests, so it would try to install it every time which
can randomly take a while to complete (I'm not sure why it's not
deterministic, but the dependencies appear to be installed
inconsistently across multiple workflows).
This PR adds the same cache we use for other steps that use playwright,
which should shave off some time from this workflow when the cache is
warm.
Additionally I omitted the standalone install-deps command as it appears
to be redundant and adds a lot of extra time to CI, due to the fact that
it installs many unrelated dependencies.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34320).
* #34321
* __->__ #34320
Because we sync built artifacts into Meta, we can't support edits from
inside www/fbsource to be synced back into OSS as it would cause merge
conflicts for future OSS PRs.
We have a workflow that should automatically catch and close these PRs,
but it looks like this one was missing one permission.
Previously the experimental workflow relied on the canary one running
first to avoid race conditions. However, I didn't account for the fact
that the canary one can now be skipped.
It may be useful at times to publish only specific packages as an
experimental tag. For example, if we need to cherry pick some fixes for
an old release, we can first do so by creating that as an experimental
release just for that package to allow for quick testing by downstream
projects.
Similar to .github/workflows/runtime_releases_from_npm_manual.yml I
added three options (`dry`, `only_packages`, `skip_packages`) to
`runtime_prereleases.yml` which both the manual and nightly workflows
reuse. I also added a discord notification when the manual workflow is
run.
When I added the `ready_for_review` event in #32344, no notifications
for opened draft PRs were sent due to some other condition. This is not
the case anymore, so we need to exclude draft PRs from triggering a
notification when the workflow is run because of an `opened` event. This
event is still needed because the `ready_for_review` event only fires
when an existing draft PR is converted to a non-draft state. It does not
trigger for pull requests that are opened directly as ready-for-review.