Skip to content

feat(react-compiler): scaffold SWC port of Babel entrypoint#11687

Merged
kdy1 merged 3 commits intomainfrom
kdy1/react-compiler-swc-port
Mar 16, 2026
Merged

feat(react-compiler): scaffold SWC port of Babel entrypoint#11687
kdy1 merged 3 commits intomainfrom
kdy1/react-compiler-swc-port

Conversation

@kdy1
Copy link
Copy Markdown
Member

@kdy1 kdy1 commented Mar 16, 2026

Summary

  • add swc_ecma_react_compiler public API surface for options, errors, and entrypoint
  • port Program-level compile entrypoint with function selection, directives, suppression handling, runtime and gating imports, and pass adapter
  • add pipeline stage modules (hir, ssa, inference, optimization, reactive_scopes, validation, transform, utils) and fixture harness plus upstream sync script
  • keep fast_check::is_required behavior compatible

Tests

  • cargo test -p swc_ecma_react_compiler
  • cargo clippy -p swc_ecma_react_compiler --all-targets -- -D warnings

Notes

  • workspace-wide cargo fmt --all and cargo clippy --all --all-targets -- -D warnings currently fail due an existing unrelated parse error in crates/swc_es_parser/tests/bench_parser_inputs.rs (unclosed delimiter).

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 16, 2026

🦋 Changeset detected

Latest commit: 4190543

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@socket-security
Copy link
Copy Markdown

socket-security Bot commented Mar 16, 2026

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm js-beautify is 100.0% likely obfuscated

Confidence: 1.00

Location: Package overview

From: package.jsonnpm/js-beautify@1.14.7

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/js-beautify@1.14.7. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 16, 2026

Binary Sizes

File Size
swc.linux-x64-gnu.node 28M (28750152 bytes)

Commit: 1b29e25

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Mar 16, 2026

Merging this PR will not alter performance

✅ 219 untouched benchmarks


Comparing kdy1/react-compiler-swc-port (4190543) with main (62eeee1)

Open in CodSpeed

@kdy1 kdy1 force-pushed the kdy1/react-compiler-swc-port branch from 4544fb4 to 454c859 Compare March 16, 2026 05:24
@kdy1 kdy1 marked this pull request as ready for review March 16, 2026 06:40
@kdy1 kdy1 requested review from a team as code owners March 16, 2026 06:40
Copilot AI review requested due to automatic review settings March 16, 2026 06:40
kodiakhq[bot]
kodiakhq Bot previously approved these changes Mar 16, 2026
@claude

This comment has been minimized.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Scaffolds an initial SWC-based port of the React Compiler “Babel entrypoint” into a new swc_ecma_react_compiler crate, including a public options/error API surface, a program-level compilation entrypoint, and a first-pass fixture harness.

Changes:

  • Introduces a new compile_program/compile_fn entrypoint with suppression handling, directive parsing, import injection, and a staged (currently mostly no-op) pipeline skeleton.
  • Adds public configuration types (options, targets, logger events) and structured error/diagnostic types.
  • Adds fixture-based tests plus an upstream fixture sync script and manifest.

Reviewed changes

Copilot reviewed 26 out of 27 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
crates/swc_ecma_react_compiler/src/lib.rs Exposes public API + SWC pass wrapper around compile_program.
crates/swc_ecma_react_compiler/src/options.rs Defines plugin options, defaults, parsing, and target/runtime selection.
crates/swc_ecma_react_compiler/src/error.rs Introduces compiler error + diagnostic detail structures.
crates/swc_ecma_react_compiler/src/entrypoint/program.rs Implements the program-level compiler traversal, selection logic, and reporting.
crates/swc_ecma_react_compiler/src/entrypoint/imports.rs Adds runtime/external import detection + insertion helpers.
crates/swc_ecma_react_compiler/src/entrypoint/gating.rs Parses use memo if(...) dynamic gating directives.
crates/swc_ecma_react_compiler/src/entrypoint/suppression.rs Parses ESLint/Flow suppression comments into skip diagnostics.
crates/swc_ecma_react_compiler/src/utils/mod.rs Adds directive collection + naming/identifier helpers.
crates/swc_ecma_react_compiler/src/{hir,inference,ssa,optimization,validation,transform,reactive_scopes}/mod.rs Adds pipeline stage modules as placeholders for subsequent porting work.
crates/swc_ecma_react_compiler/tests/fixture.rs Fixture harness executing compile_program and asserting output/errors.
crates/swc_ecma_react_compiler/tests/fixtures/** Adds initial local fixtures + upstream fixture manifest seed.
crates/swc_ecma_react_compiler/scripts/sync_fixtures.sh Script to sync upstream fixtures via gh api.
crates/swc_ecma_react_compiler/Cargo.toml Adds required dependencies/dev-dependencies for tests/harness.
Cargo.lock Lockfile updates for new deps.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +129 to +143
module.body.insert(
0,
ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
span: DUMMY_SP,
specifiers: vec![new_specifier],
src: Box::new(Str {
span: DUMMY_SP,
value: Atom::new(module_name).into(),
raw: None,
}),
type_only: false,
with: None,
phase: ImportPhase::Evaluation,
})),
);
Comment on lines +112 to +126
let local = Ident::new_no_ctxt(Atom::new(local_hint), DUMMY_SP);

let new_specifier = ImportSpecifier::Named(ImportNamedSpecifier {
span: DUMMY_SP,
local: local.clone(),
imported: Some(ModuleExportName::Ident(Ident::new_no_ctxt(
Atom::new(imported_name),
DUMMY_SP,
))),
is_type_only: false,
});

if let Some(import_decl) = find_existing_import_mut(module, module_name) {
import_decl.specifiers.push(new_specifier);
return Some(local);
Comment on lines +538 to +558
let block = match &*arrow.body {
BlockStmtOrExpr::BlockStmt(block) => block,
BlockStmtOrExpr::Expr(_) => {
// We compile concise-body arrows by wrapping into a block.
let expr = match *arrow.body.clone() {
BlockStmtOrExpr::Expr(expr) => expr,
BlockStmtOrExpr::BlockStmt(_) => unreachable!(),
};
arrow.body = Box::new(BlockStmtOrExpr::BlockStmt(BlockStmt {
span: arrow.span,
ctxt: arrow.ctxt,
stmts: vec![swc_ecma_ast::Stmt::Return(swc_ecma_ast::ReturnStmt {
span: arrow.span,
arg: Some(expr),
})],
}));

match &*arrow.body {
BlockStmtOrExpr::BlockStmt(block) => block,
BlockStmtOrExpr::Expr(_) => unreachable!(),
}
Comment on lines +56 to +57

program.visit_mut_children_with(self);
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 454c859574

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/swc_ecma_react_compiler/src/entrypoint/imports.rs
Comment thread crates/swc_ecma_react_compiler/src/entrypoint/suppression.rs
kodiakhq[bot]
kodiakhq Bot previously approved these changes Mar 16, 2026
@kdy1
Copy link
Copy Markdown
Member Author

kdy1 commented Mar 16, 2026

Addressed review #11687 (review) in 710d12c:

  • ignore type-only imports in memo-cache import detection and import reuse
  • apply eslint-disable-next-line suppression to the following function (and consume it once)
  • added regression tests for both cases

@claude

This comment has been minimized.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 710d12cd3c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

BlockStmtOrExpr::Expr(expr) => expr,
BlockStmtOrExpr::BlockStmt(_) => unreachable!(),
};
arrow.body = Box::new(BlockStmtOrExpr::BlockStmt(BlockStmt {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Defer concise-arrow rewriting until compilation is confirmed

compile_arrow rewrites every concise arrow body into a block (arrow.body = ...) before deciding whether the function should actually be compiled. When selected_type is None (for example, non-React names in infer mode, annotation mode without opt-in, or lint/no-emit flows), the function returns early and leaves this rewrite behind, so code gets mutated even though the compiler reported no transformation for that function.

Useful? React with 👍 / 👎.

open_eslint_disable = Some(comment.clone());
}

let is_enable = text.contains("eslint-enable") && has_rule;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Accept bare eslint-enable as ending a suppression range

Suppression parsing only treats eslint-enable as a close marker when the comment text also contains one of the configured rule names (&& has_rule). A common pattern like /* eslint-disable react-hooks/rules-of-hooks */ ... /* eslint-enable */ will never close, so the disable is treated as active to EOF and unrelated later functions are incorrectly skipped as suppressed.

Useful? React with 👍 / 👎.

return Some(existing_local);
}

let local = Ident::new_no_ctxt(Atom::new(local_hint), DUMMY_SP);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid fixed local alias when injecting runtime imports

The helper import path always creates a local identifier from local_hint (for memo cache this is _c) without checking existing top-level bindings. If the module already defines _c, emitting import { c as _c } ... introduces a duplicate binding and invalid output, so the inserted import alias needs collision handling before insertion.

Useful? React with 👍 / 👎.

@kdy1 kdy1 enabled auto-merge (squash) March 16, 2026 07:34
@kdy1 kdy1 disabled auto-merge March 16, 2026 07:35
Copilot AI review requested due to automatic review settings March 16, 2026 07:36
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 16, 2026

PR Review: feat(react-compiler): scaffold SWC port of Babel entrypoint

Overall this is a well-structured scaffold that faithfully mirrors the Babel plugin's entrypoint logic. The architecture is clean: options parsing, entrypoint orchestration, and pipeline stage stubs are well-separated. Here are findings organized by category:


Code Quality

  1. Significant code duplication between compile_named_function and compile_arrow (program.rs). The two methods share ~90% of their logic (suppression checking, directive parsing, opt-in/opt-out, compilation mode selection, error recording, codegen application, gating). Consider extracting the shared logic into a helper that takes a closure or trait object for the AST-specific parts.

  2. CompilerDiagnostic struct appears unused (error.rs:77-80). It is defined and re-exported from lib.rs but never constructed anywhere. If it's a placeholder for future use, a #[allow(dead_code)] or a brief doc comment explaining intent would clarify.

  3. _script_require_stub in imports.rs is dead code with #[allow(dead_code)]. It also constructs a Pat::Invalid node, which would produce broken output if ever called. Consider removing it entirely or at minimum documenting what it's a placeholder for.

  4. collect_top_level_names (program.rs) duplicates the Module/Script match logic. A small helper like fn top_level_stmts(program) -> impl Iterator<Item = &Stmt> would reduce this.

Potential Bugs

  1. Double visitation in ReactCompilerVisitor (lib.rs:49-57): compile_program already runs program.visit_mut_with(&mut compiler) internally (line ~655 in program.rs), which uses ProgramCompiler as the visitor. Then lib.rs:57 calls program.visit_mut_children_with(self) again. Since ProgramCompiler already recurses into nested functions via its VisitMut impl, this second traversal appears redundant and could cause functions to be visited/compiled twice. This should be verified — if compile_program is the complete pass, the outer visit_mut_children_with should likely be removed.

  2. Arrow concise-body conversion clones the body (program.rs, compile_arrow):

    let expr = match *arrow.body.clone() {
        BlockStmtOrExpr::Expr(expr) => expr,
        ...
    };

    This clones the entire body just to destructure it. You could use std::mem::replace or match on a mutable reference to avoid the clone.

  3. is_hook_name("use1") returns true (utils/mod.rs): The function accepts digits after "use" (e.g., use1State), matching c.is_ascii_digit(). This seems intentional to match upstream React behavior, but it might be worth a unit test to document this edge case.

Performance

  1. Excessive cloning in the pipeline stubs: Since all pipeline stages (HIR, SSA, inference, optimization, validation, reactive_scopes) are currently no-ops that pass through the AST unchanged, the Function is cloned at least 3 times (HIR lower clones it, build_reactive_function clones body, codegen_function moves it, then apply_codegen_to_function clones params/body back). For the scaffold stage this is fine, but as real logic is added, consider taking ownership rather than cloning.

  2. suppressions_for_span iterates all suppressions for every function. For large files with many comments, consider pre-sorting suppressions by byte position for binary search.

  3. Atom::new(...) usage: Per AGENTS.md, when creating Atom instances prefer &str over String. Several places do Atom::new(imported_name) or Atom::from(import_specifier_name) from &str, which is fine. But Atom::new(local_hint) in add_import_specifier and Atom::new(module_name) in import creation could use Atom::from(...) from &str directly — worth double-checking these go through the interning fast path.

Security

  1. sync_fixtures.sh pipes gh api output through jq and base64 --decode to extract file content. The name variable comes from the manifest file and is used directly in API paths and filesystem paths. While the manifest is checked into the repo, if it were ever user-editable, path traversal (../../) in fixture names could write outside OUT_DIR. Consider validating that name contains no path separators.

Test Coverage

  1. Good inline unit test coverage for options parsing, suppression detection, import handling, and program compilation. The fixture harness covers basic-component, module-opt-out, and dynamic-gating-invalid cases.

  2. Missing test coverage for:

    • Arrow function compilation (the compile_arrow path)
    • CompilationMode::Syntax and CompilationMode::All branches
    • Export default declarations/expressions
    • Nested function depth tracking (functions inside classes)
    • validate_restricted_imports with actual blocklisted imports
    • The sources filename filter with both matching and non-matching patterns
    • PanicThresholdOptions::AllErrors and CriticalErrors actually triggering fatal errors
    • The outlined function name deduplication logic (insert_queued_outlined with name collisions)
  3. Fixture test runner (tests/fixture.rs) uses a single #[test] that loops over all fixtures. Consider using testing::fixture macro (per project conventions) so each fixture gets its own test name for better failure reporting.

Minor Nits

  • to_string_lossy() on import_decl.src.value in validate_restricted_imports is unnecessary since Atom already implements Display.
  • find_directive_disabling_memoization takes &Option<Vec<String>> — prefer Option<&[String]> for the parameter type to avoid requiring the caller to have an owned Option<Vec<...>>.

Overall: solid scaffolding with good separation of concerns. The main actionable items are the potential double-visitation bug (#5), the large duplication between named/arrow compilation (#1), and expanding test coverage for the various compilation mode branches (#13).

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Scaffolds a new swc_ecma_react_compiler crate entrypoint and option/error surface, plus an initial (mostly no-op) staged pipeline and fixture harness to validate directive/gating/suppression behavior while the Rust port is filled in.

Changes:

  • Adds public API types for options, errors/diagnostics, and a SWC Pass wrapper + compile_program / compile_fn entrypoints.
  • Implements program-level traversal for function selection, directive opt-in/opt-out, suppression handling, import injection, and outlined-function queue insertion.
  • Adds pipeline stage modules (HIR/SSA/inference/optimization/validation/transform/utils) as placeholders and introduces fixture + upstream fixture sync script.

Reviewed changes

Copilot reviewed 27 out of 28 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/swc_ecma_react_compiler/tests/fixtures/upstream_manifest.txt Manifest of upstream fixture names for sync script.
crates/swc_ecma_react_compiler/tests/fixtures/module-opt-out/input.js Fixture input for module opt-out directive.
crates/swc_ecma_react_compiler/tests/fixtures/module-opt-out/output.js Expected output for module opt-out fixture.
crates/swc_ecma_react_compiler/tests/fixtures/dynamic-gating-invalid/input.js Fixture input for invalid dynamic gating directive.
crates/swc_ecma_react_compiler/tests/fixtures/dynamic-gating-invalid/error.txt Expected error text for invalid gating fixture.
crates/swc_ecma_react_compiler/tests/fixtures/basic-component/input.js Basic component fixture input.
crates/swc_ecma_react_compiler/tests/fixtures/basic-component/output.js Expected output including runtime import insertion.
crates/swc_ecma_react_compiler/tests/fixture.rs Fixture runner: parse/compile/print and compare output or error.
crates/swc_ecma_react_compiler/src/validation/mod.rs Placeholder validation stage functions (no-op).
crates/swc_ecma_react_compiler/src/utils/mod.rs Helpers for directive collection and name classification/identifier validation.
crates/swc_ecma_react_compiler/src/transform/mod.rs React function kind enum + placeholder transform hook.
crates/swc_ecma_react_compiler/src/ssa/mod.rs Placeholder SSA stage functions (no-op).
crates/swc_ecma_react_compiler/src/reactive_scopes/mod.rs Placeholder reactive scopes IR + codegen payload structs.
crates/swc_ecma_react_compiler/src/options.rs Public options/config types and parsing/defaulting logic + tests.
crates/swc_ecma_react_compiler/src/optimization/mod.rs Placeholder optimization stage functions (no-op).
crates/swc_ecma_react_compiler/src/lib.rs Crate module wiring, re-exports, and SWC Pass wrapper.
crates/swc_ecma_react_compiler/src/inference/mod.rs Placeholder inference stage functions (no-op).
crates/swc_ecma_react_compiler/src/hir/mod.rs Placeholder HIR lowering struct + stub lower.
crates/swc_ecma_react_compiler/src/error.rs Error/diagnostic data model and formatting helpers.
crates/swc_ecma_react_compiler/src/entrypoint/suppression.rs ESLint/Flow suppression detection + conversion to compiler errors + tests.
crates/swc_ecma_react_compiler/src/entrypoint/program.rs Main program entrypoint: traversal, selection, gating, import insertion, outline queue.
crates/swc_ecma_react_compiler/src/entrypoint/mod.rs Entrypoint module wiring + public re-exports.
crates/swc_ecma_react_compiler/src/entrypoint/imports.rs Runtime/external import detection and insertion + tests.
crates/swc_ecma_react_compiler/src/entrypoint/gating.rs Dynamic gating directive parsing/validation + tests.
crates/swc_ecma_react_compiler/scripts/sync_fixtures.sh Script to fetch upstream fixtures via GitHub API and materialize local inputs/outputs.
crates/swc_ecma_react_compiler/Cargo.toml Adds needed dependencies/dev-dependencies for atoms and codegen/parser in tests.
Cargo.lock Locks new crate dependencies.
.changeset/green-owls-jump.md Declares a major changeset for the new scaffolded feature.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +563 to +583
let block = match &*arrow.body {
BlockStmtOrExpr::BlockStmt(block) => block,
BlockStmtOrExpr::Expr(_) => {
// We compile concise-body arrows by wrapping into a block.
let expr = match *arrow.body.clone() {
BlockStmtOrExpr::Expr(expr) => expr,
BlockStmtOrExpr::BlockStmt(_) => unreachable!(),
};
arrow.body = Box::new(BlockStmtOrExpr::BlockStmt(BlockStmt {
span: arrow.span,
ctxt: arrow.ctxt,
stmts: vec![swc_ecma_ast::Stmt::Return(swc_ecma_ast::ReturnStmt {
span: arrow.span,
arg: Some(expr),
})],
}));

match &*arrow.body {
BlockStmtOrExpr::BlockStmt(block) => block,
BlockStmtOrExpr::Expr(_) => unreachable!(),
}
Comment on lines +821 to +832
self.function_depth += 1;
fn_expr.function.visit_mut_children_with(self);
self.function_depth -= 1;
return;
}
Expr::Arrow(arrow) => {
self.compile_arrow(name.as_ref(), arrow, is_top_level, arrow.span);

self.function_depth += 1;
arrow.body.visit_mut_with(self);
self.function_depth -= 1;
return;
Comment on lines +116 to +121
if let Some(existing_local) = find_existing_named_import(module, module_name, imported_name) {
return Some(existing_local);
}

let local = Ident::new_no_ctxt(Atom::new(local_hint), DUMMY_SP);

@kdy1 kdy1 enabled auto-merge (squash) March 16, 2026 07:54
@kdy1 kdy1 disabled auto-merge March 16, 2026 07:58
@kdy1 kdy1 merged commit 4a1d3ce into main Mar 16, 2026
210 checks passed
@kdy1 kdy1 deleted the kdy1/react-compiler-swc-port branch March 16, 2026 07:58
@github-actions github-actions Bot modified the milestones: Planned, 1.15.21 Mar 16, 2026
@swc-project swc-project locked as resolved and limited conversation to collaborators Apr 22, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants