[v6] Rework the SWC setup: native Flow support and a modernized getJsTransformRules
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 30/100
- Issue type
- Refactor
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- react-native, typescript
- Domain
- build-system, tooling
Research direction
Read the current getJsTransformRules, getSwcLoaderOptions, and getFlowTransformRules entry points, then check the Rspack parser limitation described here. Use tests/integration and the proposed tester-app or config-matrix coverage as validation starting points. Done requires an agreed v6 Flow path, updated rules and options, documentation, and coverage for component, hook, enums, and unused imports.
Written by the indexing model from the issue text.
Description
Motivation
Re.Pack's SWC-native pipeline (getJsTransformRules → getSwcLoaderOptions + getFlowTransformRules) strips Flow types in a separate JavaScript pass (flow-remove-types behind @callstack/repack/flow-loader) before handing sources to builtin:swc-loader. SWC can now parse and strip Flow natively (jsc.parser.syntax: "flow", docs) — landed via swc#11685, shipped in @swc/core 1.15.21 (March 2026), with React Native parity fixes continuing through 1.15.46 (current). That makes the extra pass redundant — and it is already broken on modern React Native.
Part of #1414 (Roadmap to V6), which lists "use swc built in flow removal".
The current Flow pass is broken on RN ≥ 0.81
flow-remove-types only erases type annotations. It does not desugar Flow's component / hook syntax or Flow enums — the keywords stay behind, producing invalid JavaScript that SWC then refuses to parse. React Native ships component syntax in core since 0.81 (Libraries/Components/View/View.js:25: component View(), so getJsTransformRules() cannot build RN 0.81+. Reproduced with the workspace versions (flow-remove-types@2.324.0, @swc/core@1.15.46):
flow-remove-types keeps the component keyword; swc parse of the output:
x Expected ';', '}' or <eof> (View.js:25)
swc one-pass { syntax: 'flow', jsx: true, enums: true, components: true }: OK
This goes unnoticed because nothing in this repo exercises getJsTransformRules — templates/ and all tester apps use @callstack/repack/babel-swc-loader. The Rspack migration guide does document getJsTransformRules + flow-loader as the setup to adopt, though (the Metro guide embeds the babel-swc-loader templates and is unaffected).
The extra pass is also the slowest part of the pipeline
Stripping all 455 .js files in react-native/Libraries (4.6 MB): flow-remove-types 659 ms (strip only, single-threaded JS) vs SWC flow mode 209 ms serial / 59 ms parallel — for strip plus full transform. Dropping the loader also removes a parse → print → source-map round trip per Flow module and the flow-remove-types + hermes-parser install footprint.
What SWC's Flow mode covers
Verified on @swc/core@1.15.46 with { syntax: 'flow', jsx: true, enums: true, components: true }:
- Annotations,
import type,opaque type,declare,export type *— stripped (parity withflow-remove-types). component/hook— desugared correctly (flow-remove-typesemits invalid JS).- Flow enums — transpiled (
flow-remove-typespasses them through verbatim), but not to theflow-enums-runtimerepresentation — see behaviour notes below. enumsandcomponentsare not default — without them these constructs are parse errors.requireDirectivemust stay off to match our currentall: true(with it, files without a@flowpragma get no stripping at all).
Blocker: Rspack does not expose syntax: "flow"
True on every published version — verified on 1.6.0 and 2.0.0-alpha.1 (unknown variant 'flow', expected 'ecmascript' or 'typescript'), and latest 2.1.6 / main has no flow variant in SwcLoaderParserConfig either. Bumping to Rspack 2 does not fix it. Rspack main is already on swc_core = "72.0.0"; the gap is a Cargo feature: SWC gates the Flow parser behind base_flow (swc_core → swc/flow → swc_ecma_parser/flow), and rspack_loader_swc doesn't enable it. The upstream fix is one feature flag, the corresponding SwcLoaderParserConfig type, and a detectSyntax mapping decision for .flow / .js.flow. Precedent for this kind of ask: rspack#13843 (swc_core bump + exposing jsc.preserveSymlinks) was accepted and shipped.
Fallback if upstream is slow: route Flow-typed modules through a Re.Pack loader backed by standalone @swc/core, which has the feature compiled in. babelSwcLoader's lazyGetSwc already resolves rspack.experiments.swc → @rspack/core → @swc/core, so the plumbing exists. Costs the JS↔Rust boundary again, but stays single-pass and works on webpack too.
Behaviour differences vs Babel
- Unused value imports are elided by default. SWC's flow mode applies TypeScript-style import elision (
import Unused from './u'; console.log(1)→console.log(1)).jsc.transform.verbatimModuleSyntax: truefixes this — verified it preserves unused value imports while still strippingimport typeandtypespecifiers. Set it and pin with a regression test. - Flow enums don't use
flow-enums-runtime. SWC emits a TS-style object IIFE. Member access matches Flow semantics, but the runtime API (.members(),.cast(),.isValid(),.getName()) doesn't exist on the result. RN core doesn't call those methods today; needs a documented caveat (and a check when bumping RN). exportDefaultFromis unavailable undersyntax: "flow"(export v from './v'→ parse error), whilegetParserOptionsenables it for all.js(RN's preset includes@babel/plugin-proposal-export-default-from). Soflowcannot blanket-replaceecmascriptfor.js— keep it scoped to Flow-typed modules until SWC supports the combination.
Other issues in getSwcLoaderOptions / getJsTransformRules
Independent of Flow, worth fixing in the same pass:
developmentis tied to the JSX runtime, notmode.getJSCTransformOptionssetsdevelopment: jsxRuntime === 'classic', so with the defaultautomaticruntime dev builds always getjsxinstead ofjsxDEV(verified) — losing__source/__self, accurate component stacks, and "open in editor". RN's preset addsjsx-source/jsx-selfwheneverdev. Should followmode.- The six-branch
oneOfcollapses under Rspack 2.detectSyntax: 'auto'(new in 2.0.0) infers the parser from the extension, leaving only the sourcemaps-off-for-node_modulessplit;tsRules/tsxRulesare byte-identical today anyway. Unrecognized extensions map to{ syntax: 'typescript', tsx: true }, so.flowneeds an explicit branch either way. env.targets: { node: 24 }is a sentinel, not a target ("assume everything supported, re-add viaenv.include"). It works but drifts silently, and Rspack 2 derives loader targets from the top-leveltargetwhen unset. Re-deriveenv.include(16 entries) from the current RN preset and write down each inclusion/omission — e.g.transform-react-display-nameis missing from both SWC paths, and the preset's regenerator-gated trio (optional-catch-binding,nullish-coalescing,for-of) is dev+Hermes-only, so omitting it may be correct but should be a written decision.lazyImportsdefaults tofalse, while RN's preset defaultslazyto itslazy-importsallowlist.isModuleis never set (babelSwcLoadersets it from Babel'ssourceType;isModule: 'unknown'is probably right here).getSwcParserConfig(babelSwcLoader) andgetParserOptions(getSwcLoaderOptions) are near-duplicates with different answers (exportDefaultFrom). Fold into one shared helper.getCodegenTransformRuleskeeps its Babel pass —@react-native/babel-plugin-codegenhas no SWC equivalent — but the "must run before Flow stripping" ordering constraint deserves a re-check once SWC parses Flow natively.
babel-swc-loader
Stays as-is — it is the default in templates/ and every tester app, and it is correct by construction (it reads the project's real Babel config and only offloads transforms it can prove equivalent). Follow-up once Flow support is available: teach swc.ts to map transform-flow-strip-types / transform-flow-enums / syntax-hermes-parser to jsc.parser.syntax: 'flow' (+ enums, components), so RN core files go through SWC instead of falling back to Babel — the biggest remaining Babel fallback for a stock RN app.
Scope
Upstream
- Rspack: file the issue/PR enabling
base_flowinrspack_loader_swc, exposing the flow parser options inSwcLoaderParserConfig, and deciding thedetectSyntaxmapping for.flow - SWC: file the issue for
exportDefaultFromundersyntax: "flow"
Core
- Switch
getFlowTransformRulesfromflow-loaderto SWC-native Flow (or the@swc/core-backed fallback loader), keeping theFLOW_TYPED_MODULESinclude/exclude surface; setverbatimModuleSyntax: true - Decide the fate of
@callstack/repack/flow-loader(public export) — deprecate as escape hatch or drop in v6 - Rewrite
getJsTransformRulesondetectSyntax: 'auto', collapsing theoneOf - Drive
jsc.transform.react.developmentfrommode - Re-derive
env.includefrom the current RN preset; alignlazyImports/isModuledefaults - Fold
getSwcParserConfigandgetParserOptionsinto one helper - Drop the
flow-remove-typesdependency once nothing uses it
v5 patch (independent of v6)
- Stop
getJsTransformRulesemitting invalid JS forcomponent/hook/ enums on RN ≥ 0.81 — either the SWC path orbabel-plugin-syntax-hermes-parser+babel-plugin-transform-flow-enumsinflow-loader
Apps, tests, docs
- Add a tester-app variant or config-matrix entry that actually builds with
getJsTransformRules— this class of bug is invisible today -
tests/integrationcoverage forcomponent/hook/ Flow enums in anode_modulesdependency; regression test pinning unused-import preservation - Update
flow-loader,get-flow-transform-rules,get-swc-loader-options,get-js-transform-rulesdocs and the Rspack migration guide; note the minimum Rspack version in the v5 → v6 migration guide
Open questions
- Wait on upstream Rspack, or ship the
@swc/core-backed loader first (works today, keeps webpack) and switch tobuiltin:swc-loaderwhen the feature flag lands? - Does
getJsTransformRulesremain a second-class alternative tobabel-swc-loader, or does v6 make it the default intemplates/? #1414 targets Rspack 2+ as a minimum, which makes it viable as a default for the first time — but only if it becomes the path we actually test.
- Dominant language
- TypeScript
- Stars
- 1.9k
- Forks
- 164
- Avg merge
- 10d 13h
- Merged PRs (30d)
- 10
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from callstack/repack
-
Difficulty 3/5 1-2 days Newbie friendliness 78/100
-
Difficulty 5/5 Over a week Newbie friendliness 35/100
-
Dev server progress goes backwards mid-compilation (non-monotonic percentage forwarded to reporters) Open
Difficulty 3/5 1-2 days Newbie friendliness 70/100
-
Difficulty 3/5 1-2 days Newbie friendliness 78/100
-
area:repack type:feature
Difficulty 5/5 Over a week Newbie friendliness 42/100
All issues in callstack/repack
Similar issues
-
blocklist removal
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
MetaMask/eth-phishing-detect#296544 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
pastelsky/bundlephobia#1122 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
-
category/development priority/P2 scope/file-operations scope/testing type/enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
-
Enatega Customer and Rider app: Add-ons price is not visible to customer after order is placed. Open
Difficulty 2/5 1-3 hours Newbie friendliness 75/100