mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
Update readme and other docs
ghstack-source-id: b25cc2a481dc1508bbb710dff472f2d23667ec98 Pull Request resolved: https://github.com/facebook/react-forget/pull/2939
This commit is contained in:
parent
e1771545a2
commit
95ba7dcff5
|
|
@ -1,117 +1,7 @@
|
|||
# React Compiler
|
||||
|
||||
React Compiler is an experimental Babel plugin to automatically memoize React Hooks and Components.
|
||||
React Compiler is a compiler that optimizes React applications, ensuring that only the minimal parts of components and hooks will re-render when state changes. The compiler also validates that components and hooks follow the Rules of React.
|
||||
|
||||
## Development
|
||||
More information about the design and architecture of the compiler are covered in the [Design Goals](./docs/DESIGN_GOALS.md).
|
||||
|
||||
```sh
|
||||
# tsc --watch
|
||||
$ yarn dev
|
||||
|
||||
# in another terminal window
|
||||
$ yarn test --watch
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
An overview of the implementation can be found in the [Architecture Overview](./ARCHITECTURE.md).
|
||||
|
||||
This transform
|
||||
|
||||
- needs [plugin-syntax-jsx](https://babeljs.io/docs/en/babel-plugin-syntax-jsx) as a dependency to inherit the syntax from.
|
||||
- should be run before [plugin-transform-react-jsx](https://github.com/babel/babel/tree/main/packages/babel-plugin-transform-react-jsx)
|
||||
- assume the enforcement of [rules of hooks](https://reactjs.org/docs/hooks-rules.html), i.e.
|
||||
- only call hooks from React functions
|
||||
- only call hooks at the top level
|
||||
- <https://www.npmjs.com/package/eslint-plugin-react-hooks>
|
||||
|
||||
Scaffolding
|
||||
|
||||
- <https://github.com/facebook/flow/tree/master/packages/babel-plugin-transform-flow-enums>
|
||||
- <https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-react-jsx/src/create-plugin.ts>
|
||||
|
||||
Reference
|
||||
|
||||
- [Babel Plugin Handbook](https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md)
|
||||
|
||||
## Rust Development
|
||||
|
||||
## First-Time Setup
|
||||
|
||||
1. Install Rust using `rustup`. See the guide at https://www.rust-lang.org/tools/install.
|
||||
2. Install Visual Studio Code from https://code.visualstudio.com/.
|
||||
Note to Meta employees: install the stock version from that website, not the pre-installed version.
|
||||
3. Install the Rust Analyzer VSCode extension through the VSCode marketplace. See instructions at https://rust-analyzer.github.io/manual.html#vs-code.
|
||||
4. Install `cargo edit` which extends cargo with commands to manage dependencies. See https://github.com/killercup/cargo-edit#installation
|
||||
5. Install `cargo insta` which extens cargo with a command to manage snapshots. See https://insta.rs/docs/cli/
|
||||
|
||||
## Workspace Hygiene
|
||||
|
||||
### Adding Dependencies
|
||||
|
||||
To add a dependency, add it to the top-level `Cargo.toml`
|
||||
|
||||
```
|
||||
// Cargo.toml
|
||||
[workspace.dependencies]
|
||||
...
|
||||
new_dep = { version = "x.y.z" }
|
||||
...
|
||||
```
|
||||
|
||||
Then reference it from your crate as follows:
|
||||
|
||||
```
|
||||
// crates/react_foo/Cargo.toml
|
||||
[dependencies]
|
||||
...
|
||||
new_dep = { workspace = true }
|
||||
...
|
||||
```
|
||||
|
||||
### Adding new crates
|
||||
|
||||
Rust's compilation strategy is largely based on parallelizing at the granularity of crates, so builds can be faster when projects
|
||||
have more but smaller crates. Where possible it helps to structure crates to minimize dependencies. For example, our various compiler
|
||||
passes depend on each other in the sense that they often must run in a certain order. However, they often don't need to call each other,
|
||||
so they can generally be split into crates of similar types of passes, so that those crates can compile in parallel.
|
||||
|
||||
As a rule of thumb, add crates at roughly the granularity of our existing top-level folds. If you have some one-off utility code that
|
||||
doesn't fit neatly in a crate, add it to `react_utils` rather than add a one-off crate for it.
|
||||
|
||||
## Running Tests
|
||||
|
||||
Run all tests with the following from the root directory:
|
||||
|
||||
```
|
||||
cargo test
|
||||
```
|
||||
|
||||
The majority of our tests will (should) live in the `react_fixtures` crate, which is a test-only crate that runs compilation end-to-end with snapshot
|
||||
tests. To run just these tests use:
|
||||
|
||||
```
|
||||
# quiet version
|
||||
cargo test -p react_fixtures
|
||||
|
||||
# without suppressing stdout/stderr output
|
||||
cargo test -p react_fixtures -- --nocapture
|
||||
```
|
||||
|
||||
Another hint is that VSCode will show a "Run test" option if you hover over a test in the source code, this lets you run a single test easily.
|
||||
The command line will also give you the CLI command to run just that one test.
|
||||
|
||||
## Updating Snapshots
|
||||
|
||||
The above tests make frequent use of snapshot tests. If snapshots do not match the tests will fail with a diff, if the new output is correct you
|
||||
can accept the changes with:
|
||||
|
||||
```
|
||||
cargo insta accept
|
||||
```
|
||||
|
||||
If this command fails, see the note in "first-time setup" about installing `cargo insta`.
|
||||
|
||||
## CI Configuration
|
||||
|
||||
GitHub CI is configured in `.github/workflows/rust.yml`.
|
||||
More information about developing the compiler itself is covered in the [Development Guide](./docs/DEVELOPMENT_GUIDE.md).
|
||||
|
|
@ -41,14 +41,15 @@ React Compiler has two primary public interfaces: a Babel plugin for transformin
|
|||
|
||||
The core of the compiler is largely decoupled from Babel, using its own intermediate representations. The high-level flow is as follows:
|
||||
|
||||
- Babel Plugin: Determines which functions in a file should be compiled, based on the plugion options and any local opt-in/opt-out directives. For each component or hook to be compiled, the plugin calls the compiler, passing in the original function and getting back a new AST node which will replace the original.
|
||||
- Lowering (BuildHIR): The first step of the compiler is to convert the Babel AST into React Compiler's primary intermediate representation, HIR (High-level Intermediate Representation). This phase is primarily based on the AST itself, but currently leans on Babel to resolve identifiers. The HIR preserves the precise order-of-evaluation semantics of JavaScript, resolves break/continue to their jump points, etc. The resulting HIR forms a control-flow graph of basic blocks, each of which contains zero or more consecutive instructions followed by a terminal. The basic blocks are stored in reverse postorder, such that forward iteration of the blocks allows predecessors to be visited before successors _unless_ there is a "back edge" (ie a loop).
|
||||
- SSA Conversion (EnterSSA): The HIR is converted to HIR form, such that all Identifiers in the HIR are updated to an SSA-based identifier.
|
||||
- **Babel Plugin**: Determines which functions in a file should be compiled, based on the plugion options and any local opt-in/opt-out directives. For each component or hook to be compiled, the plugin calls the compiler, passing in the original function and getting back a new AST node which will replace the original.
|
||||
- **Lowering** (BuildHIR): The first step of the compiler is to convert the Babel AST into React Compiler's primary intermediate representation, HIR (High-level Intermediate Representation). This phase is primarily based on the AST itself, but currently leans on Babel to resolve identifiers. The HIR preserves the precise order-of-evaluation semantics of JavaScript, resolves break/continue to their jump points, etc. The resulting HIR forms a control-flow graph of basic blocks, each of which contains zero or more consecutive instructions followed by a terminal. The basic blocks are stored in reverse postorder, such that forward iteration of the blocks allows predecessors to be visited before successors _unless_ there is a "back edge" (ie a loop).
|
||||
- **SSA Conversion** (EnterSSA): The HIR is converted to HIR form, such that all Identifiers in the HIR are updated to an SSA-based identifier.
|
||||
- Validation: We run various validation passes to check that the input is valid React, ie that it does not break the rules. This includes looking for conditional hook calls, unconditional setState calls, etc.
|
||||
- Optimization: Various passes such as dead code elimination and constant propagation can generally improve performance and reduce the amount of instructions to be optimized later.
|
||||
- Type Inference (InferTypes): We run a conservative type inference pass to identify certain key types of data that may appear in the program that are relevant for further analysis, such as which values are hooks, primitives, etc.
|
||||
- Inferring Mutable Ranges: Several passes are involved in determing groups of values that are created/mutated together and the set of instructions involved in creating/mutating those values. We call these groups "reactive scopes", and each can have one or more declarations (or occassionaly a reassignment).
|
||||
- Constructing/Optimizing Reactive Scopes: Once the compiler determines the set of reactive scopes, it then transforms the program to make these scopes explicit in the HIR. The code is later converted to a ReactiveFunction, which is a hybrid of the HIR and an AST. Scopes are further pruned and transformed. For example, the compiler cannot make hook calls conditional, so any reactive scopes that contain a hook call must be pruned. If two consecutive scopes will always invalidate together, we attempt to merge them to reduce overhead, etc.
|
||||
- Codegen: Finally, the ReactiveFunction hybrid HIR/AST is converted back to a raw Babel AST node, and returned to the Babel plugin.
|
||||
- **Optimization**: Various passes such as dead code elimination and constant propagation can generally improve performance and reduce the amount of instructions to be optimized later.
|
||||
- **Type Inference** (InferTypes): We run a conservative type inference pass to identify certain key types of data that may appear in the program that are relevant for further analysis, such as which values are hooks, primitives, etc.
|
||||
- **Inferring Reactive Scopes**: Several passes are involved in determing groups of values that are created/mutated together and the set of instructions involved in creating/mutating those values. We call these groups "reactive scopes", and each can have one or more declarations (or occassionaly a reassignment).
|
||||
- **Constructing/Optimizing Reactive Scopes**: Once the compiler determines the set of reactive scopes, it then transforms the program to make these scopes explicit in the HIR. The code is later converted to a ReactiveFunction, which is a hybrid of the HIR and an AST. Scopes are further pruned and transformed. For example, the compiler cannot make hook calls conditional, so any reactive scopes that contain a hook call must be pruned. If two consecutive scopes will always invalidate together, we attempt to merge them to reduce overhead, etc.
|
||||
- **Codegen**: Finally, the ReactiveFunction hybrid HIR/AST is converted back to a raw Babel AST node, and returned to the Babel plugin.
|
||||
- **Babel Plugin**: The Babel plugin replaces the original node with the new version.
|
||||
|
||||
The ESLint plugin works similarly. For now, it effectively invokes the Babel plugin on the code and reports back a subset of the errors. The compiler can report a variety of errors, including that the code is simply invalid JavaScript, but the ESLint plugin filters to only show the React-specific errors.
|
||||
|
|
|
|||
108
compiler/docs/DEVELOPMENT_GUIDE.md
Normal file
108
compiler/docs/DEVELOPMENT_GUIDE.md
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
# React Compiler Development Guide
|
||||
|
||||
Note: for general notes about contributing, see the [CONTRIBUTING.md](../CONTRIBUTING.md).
|
||||
|
||||
## Compiler Development
|
||||
|
||||
For general compiler development we recommend the following workflow:
|
||||
|
||||
```sh
|
||||
# Install dependencies
|
||||
yarn
|
||||
|
||||
# build the custom test runner
|
||||
yarn snap:build
|
||||
|
||||
# Run the primary tests in watch mode
|
||||
yarn snap --watch
|
||||
```
|
||||
|
||||
`snap` is our custom test runner, which creates "golden" test files that have the expected output for each input fixture, as well as the results of executing a specific input (or sequence of inputs) in both the uncompiled and compiler versions of the input.
|
||||
|
||||
When contributing changes, we prefer to:
|
||||
* Add one or more fixtures that demonstrate the current compiled output for a particular combination of input and configuration. Send this as a first PR.
|
||||
* Then, make changes to the compiler that achieve the desired output for those examples. Commit both the output changes and the corresponding compiler changes in a second PR.
|
||||
|
||||
## (WIP) Rust Development
|
||||
|
||||
We have a work-in-progress Rust port of React Compiler. The code here is unused, and was developed to help understand the feasibility and path to porting to Rust and integrating with various Rust or non-JS build systems, such as SWC, OXC, ESBuild, etc. We are currently in the process of changing the data representation used in parts of the compiler to both improve compilation, which will also have the side benefit of making it even easier to port to Rust. We will re-evaluate where to go with the Rust port once that refactoring is complete.
|
||||
|
||||
### First-Time Setup
|
||||
|
||||
1. Install Rust using `rustup`. See the guide at https://www.rust-lang.org/tools/install.
|
||||
2. Install Visual Studio Code from https://code.visualstudio.com/.
|
||||
Note to Meta employees: install the stock version from that website, not the pre-installed version.
|
||||
3. Install the Rust Analyzer VSCode extension through the VSCode marketplace. See instructions at https://rust-analyzer.github.io/manual.html#vs-code.
|
||||
4. Install `cargo edit` which extends cargo with commands to manage dependencies. See https://github.com/killercup/cargo-edit#installation
|
||||
5. Install `cargo insta` which extens cargo with a command to manage snapshots. See https://insta.rs/docs/cli/
|
||||
|
||||
### Workspace Hygiene
|
||||
|
||||
#### Adding Dependencies
|
||||
|
||||
To add a dependency, add it to the top-level `Cargo.toml`
|
||||
|
||||
```
|
||||
// Cargo.toml
|
||||
[workspace.dependencies]
|
||||
...
|
||||
new_dep = { version = "x.y.z" }
|
||||
...
|
||||
```
|
||||
|
||||
Then reference it from your crate as follows:
|
||||
|
||||
```
|
||||
// crates/react_foo/Cargo.toml
|
||||
[dependencies]
|
||||
...
|
||||
new_dep = { workspace = true }
|
||||
...
|
||||
```
|
||||
|
||||
#### Adding new crates
|
||||
|
||||
Rust's compilation strategy is largely based on parallelizing at the granularity of crates, so builds can be faster when projects
|
||||
have more but smaller crates. Where possible it helps to structure crates to minimize dependencies. For example, our various compiler
|
||||
passes depend on each other in the sense that they often must run in a certain order. However, they often don't need to call each other,
|
||||
so they can generally be split into crates of similar types of passes, so that those crates can compile in parallel.
|
||||
|
||||
As a rule of thumb, add crates at roughly the granularity of our existing top-level folds. If you have some one-off utility code that
|
||||
doesn't fit neatly in a crate, add it to `react_utils` rather than add a one-off crate for it.
|
||||
|
||||
### Running Tests
|
||||
|
||||
Run all tests with the following from the root directory:
|
||||
|
||||
```
|
||||
cargo test
|
||||
```
|
||||
|
||||
The majority of our tests will (should) live in the `react_fixtures` crate, which is a test-only crate that runs compilation end-to-end with snapshot
|
||||
tests. To run just these tests use:
|
||||
|
||||
```
|
||||
# quiet version
|
||||
cargo test -p react_fixtures
|
||||
|
||||
# without suppressing stdout/stderr output
|
||||
cargo test -p react_fixtures -- --nocapture
|
||||
```
|
||||
|
||||
Another hint is that VSCode will show a "Run test" option if you hover over a test in the source code, this lets you run a single test easily.
|
||||
The command line will also give you the CLI command to run just that one test.
|
||||
|
||||
### Updating Snapshots
|
||||
|
||||
The above tests make frequent use of snapshot tests. If snapshots do not match the tests will fail with a diff, if the new output is correct you
|
||||
can accept the changes with:
|
||||
|
||||
```
|
||||
cargo insta accept
|
||||
```
|
||||
|
||||
If this command fails, see the note in "first-time setup" about installing `cargo insta`.
|
||||
|
||||
### CI Configuration
|
||||
|
||||
GitHub CI is configured in `.github/workflows/rust.yml`.
|
||||
Loading…
Reference in New Issue
Block a user