Skip to content

Commit

Permalink
feat(transformer): support react fast refresh (#4587)
Browse files Browse the repository at this point in the history
close: #3943

## Further improvements

There is a double visit here. We need to collect all react hooks calling in `Function` and `ArrowFunctionExpression`. If we want to remove this implementation, we may wait for #4188.

https://github.com/oxc-project/oxc/blob/d797e595d286c613848b773c256bd43124ad1981/crates/oxc_transformer/src/react/refresh.rs#L744-L947

## Tests

All tests copy from https://github.com/facebook/react/blob/main/packages/react-refresh/src/__tests__/ReactFresh-test.js

There are still 4 tests that have not been passed

**1. refresh/can-handle-implicit-arrow-returns/input.jsx**

Related to #4767. transform correct, just output doesn't match the expected output

**2. refresh/registers-identifiers-used-in-jsx-at-definition-site/input.jsx**
**3. refresh/registers-identifiers-used-in-react-create-element-at-definition-site/input.jsx**

Blocked by #4746

**4. refresh/supports-typescript-namespace-syntax/input.tsx**

oxc transforms ts to js first, so probably we can ignore this case. If we really want to pass this test, we also need to turn off `TypeScript` plugin.

## What's next?

### Options:

1. Support transform `refresh_reg` and `refresh_sig` options to `MemberExpression`. Currently `import.meta.xxxx` still is an `Identifier`
2. Support `emit_full_signatures` option

### Other
NAPI, testing in `monitor-oxc`, etc..
  • Loading branch information
Dunqing committed Aug 15, 2024
1 parent 1eb59d2 commit 9c2ae8e
Show file tree
Hide file tree
Showing 62 changed files with 1,972 additions and 7 deletions.
6 changes: 5 additions & 1 deletion crates/oxc_transformer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub use crate::{
env::EnvOptions,
es2015::{ArrowFunctionsOptions, ES2015Options},
options::{BabelOptions, TransformOptions},
react::{ReactJsxRuntime, ReactOptions},
react::{ReactJsxRuntime, ReactOptions, ReactRefreshOptions},
typescript::TypeScriptOptions,
};
use crate::{
Expand Down Expand Up @@ -127,6 +127,7 @@ impl<'a> Transformer<'a> {
impl<'a> Traverse<'a> for Transformer<'a> {
fn enter_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
self.x0_typescript.transform_program(program, ctx);
self.x1_react.transform_program(program, ctx);
}

fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
Expand Down Expand Up @@ -183,6 +184,7 @@ impl<'a> Traverse<'a> for Transformer<'a> {
}

fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
self.x1_react.transform_expression_on_exit(expr, ctx);
self.x3_es2015.transform_expression_on_exit(expr, ctx);
}

Expand Down Expand Up @@ -265,6 +267,7 @@ impl<'a> Traverse<'a> for Transformer<'a> {

fn enter_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
self.x0_typescript.transform_statements(stmts);
self.x1_react.transform_statements(stmts, ctx);
self.x2_es2021.transform_statements(stmts, ctx);
self.x2_es2020.transform_statements(stmts, ctx);
self.x2_es2016.transform_statements(stmts, ctx);
Expand All @@ -273,6 +276,7 @@ impl<'a> Traverse<'a> for Transformer<'a> {

fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
self.x0_typescript.transform_statements_on_exit(stmts, ctx);
self.x1_react.transform_statements_on_exit(stmts, ctx);
self.x2_es2021.transform_statements_on_exit(stmts, ctx);
self.x2_es2020.transform_statements_on_exit(stmts, ctx);
self.x2_es2016.transform_statements_on_exit(stmts, ctx);
Expand Down
55 changes: 51 additions & 4 deletions crates/oxc_transformer/src/react/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@ mod jsx;
mod jsx_self;
mod jsx_source;
mod options;
mod refresh;
mod utils;

use std::rc::Rc;

use oxc_allocator::Vec;
use oxc_ast::ast::*;
use oxc_traverse::TraverseCtx;
use refresh::ReactRefresh;

pub use self::{
display_name::ReactDisplayName,
jsx::ReactJsx,
options::{ReactJsxRuntime, ReactOptions},
options::{ReactJsxRuntime, ReactOptions, ReactRefreshOptions},
};
use crate::context::Ctx;

Expand All @@ -29,10 +32,12 @@ use crate::context::Ctx;
pub struct React<'a> {
jsx: ReactJsx<'a>,
display_name: ReactDisplayName<'a>,
refresh: ReactRefresh<'a>,
jsx_plugin: bool,
display_name_plugin: bool,
jsx_self_plugin: bool,
jsx_source_plugin: bool,
refresh_plugin: bool,
}

// Constructors
Expand All @@ -49,29 +54,61 @@ impl<'a> React<'a> {
jsx_source_plugin,
..
} = options;
let refresh = options.refresh.clone();
Self {
jsx: ReactJsx::new(options, Rc::clone(&ctx)),
display_name: ReactDisplayName::new(ctx),
display_name: ReactDisplayName::new(Rc::clone(&ctx)),
jsx_plugin,
display_name_plugin,
jsx_self_plugin,
jsx_source_plugin,
refresh_plugin: refresh.is_some(),
refresh: ReactRefresh::new(&refresh.unwrap_or_default(), ctx),
}
}
}

// Transforms
impl<'a> React<'a> {
pub fn transform_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
if self.refresh_plugin {
self.refresh.transform_program(program, ctx);
}
}

pub fn transform_program_on_exit(
&mut self,
program: &mut Program<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if self.refresh_plugin {
self.refresh.transform_program_on_exit(program, ctx);
}
if self.jsx_plugin {
self.jsx.transform_program_on_exit(program, ctx);
}
}

pub fn transform_statements(
&mut self,
stmts: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
if self.refresh_plugin {
self.refresh.transform_statements(stmts, ctx);
}
}

pub fn transform_statements_on_exit(
&mut self,
stmts: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
if self.refresh_plugin {
self.refresh.transform_statements_on_exit(stmts, ctx);
}
}

pub fn transform_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if self.jsx_plugin {
match expr {
Expand All @@ -87,9 +124,9 @@ impl<'a> React<'a> {
}

pub fn transform_call_expression(
&self,
&mut self,
call_expr: &mut CallExpression<'a>,
ctx: &TraverseCtx<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if self.display_name_plugin {
self.display_name.transform_call_expression(call_expr, ctx);
Expand All @@ -108,4 +145,14 @@ impl<'a> React<'a> {
self.jsx.jsx_source.transform_jsx_opening_element(elem, ctx);
}
}

pub fn transform_expression_on_exit(
&mut self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if self.refresh_plugin {
self.refresh.transform_expression_on_exit(expr, ctx);
}
}
}
39 changes: 39 additions & 0 deletions crates/oxc_transformer/src/react/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ pub struct ReactOptions {
///
/// This value is used to skip Babel tests, and is not used in oxc.
pub use_spread: Option<bool>,

/// Fast Refresh
pub refresh: Option<ReactRefreshOptions>,
}

impl Default for ReactOptions {
Expand All @@ -122,6 +125,7 @@ impl Default for ReactOptions {
pragma_frag: None,
use_built_ins: None,
use_spread: None,
refresh: None,
}
}
}
Expand Down Expand Up @@ -186,3 +190,38 @@ impl ReactOptions {
}
}
}

#[derive(Debug, Clone, Deserialize)]
#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
pub struct ReactRefreshOptions {
/// Specify the identifier of the refresh registration variable.
/// Defaults to `$RefreshReg$`.
#[serde(default = "default_refresh_reg")]
pub refresh_reg: String,

/// Specify the identifier of the refresh signature variable.
/// Defaults to `$RefreshSig$`.
#[serde(default = "default_refresh_sig")]
pub refresh_sig: String,

#[serde(default)]
pub emit_full_signatures: bool,
}

impl Default for ReactRefreshOptions {
fn default() -> Self {
Self {
refresh_reg: default_refresh_reg(),
refresh_sig: default_refresh_sig(),
emit_full_signatures: false,
}
}
}

fn default_refresh_reg() -> String {
String::from("$RefreshReg$")
}

fn default_refresh_sig() -> String {
String::from("$RefreshSig$")
}
Loading

0 comments on commit 9c2ae8e

Please sign in to comment.