Skip to content

fix: parse with hermes-parser only when a file opts into Flow - #1452

Open
elirangoshen wants to merge 1 commit into
callstack:mainfrom
elirangoshen:fix/babel-loader-flow-pragma-gate
Open

fix: parse with hermes-parser only when a file opts into Flow#1452
elirangoshen wants to merge 1 commit into
callstack:mainfrom
elirangoshen:fix/babel-loader-flow-pragma-gate

Conversation

@elirangoshen

Copy link
Copy Markdown

Fixes #1447

Summary

babelLoader parsed every non-TypeScript file with hermes-parser. Metro does not do this: @react-native/babel-preset passes parseLangTypes: 'flow' to babel-plugin-syntax-hermes-parser, which skips hermes-parser for files without an @flow pragma and leaves them to @babel/parser.

This change matches that behavior. Setting hermesParserOverrides.flow to 'all' opts every file back in.

Why it matters

hermes-parser's Hermes-AST to Babel-AST conversion is quadratic in the number of sibling nodes, because each node replacement copies the whole sibling array. Prebuilt minified dependencies hit that path hard. In the Expensify app, @lottiefiles/dotlottie-react/dist/browser/index.js took 90.1s with hermes-parser and 0.1s with @babel/parser. The file is 502KB across 22 lines, has no @flow pragma, and contains one ArrayExpression with 128,834 elements. It runs as a single serial task, so it set a floor on the whole build no matter how many workers we gave it.

Across that app's babel lane of 3,522 files, 2,779 carry no pragma. Those parse in 1.1s with @babel/parser against 4.7s with hermes-parser, and hermes-parser fails on 2 of them.

Swapping parsers wholesale does not work: @babel/parser cannot read the Flow syntax in 241 react-native 0.86 files, which use as casts and component(...) types. Gating on the pragma is what the preset already does.

I reported the conversion cost upstream with a profile and a minimal repro in facebook/hermes#2158.

Change

const needsHermesParser =
  !isTypeScript &&
  shouldUseHermesParser(src, customOptions?.hermesParserOverrides?.flow);

shouldUseHermesParser lives in utils.ts and returns true when flow === 'all' or the source matches /@flow/. hermes-parser now loads lazily, only when a file needs it, so builds that never touch a Flow file skip resolving it.

Behavior change

Files containing Flow syntax without an @flow pragma will now fail to parse, as they do under Metro. Setting hermesParserOverrides: { flow: 'all' } restores the old behavior. I marked the changeset as a patch, but tell me if you would rather it be minor given the shift in semantics.

Tests

babelLoader.test.ts gets four cases for parser selection: no pragma skips hermes-parser, an @flow pragma uses it, flow: 'all' uses it regardless of pragma, and TypeScript sources skip it.

I also fixed the existing loadHermesParser mock, which threw Configuration contains string/RegExp pattern, but no filename was passed to Babel. No test had exercised that path before, so the mock was never called.

pnpm jest passes 316 tests across 34 suites. pnpm --filter @callstack/repack typecheck and pnpm biome check are clean.

The perf repro is a large minified file, so it does not fit a unit test. facebook/hermes#2158 has a programmatic generator (build an ArrayExpression with N elements, then convert) if you want it as a benchmark somewhere.

babelLoader sent every non-TypeScript file through hermes-parser, unlike Metro,
where the React Native preset's default parseLangTypes: 'flow' limits it to
files carrying an @flow pragma. hermes-parser's Hermes-AST to Babel-AST
conversion is quadratic in the number of sibling nodes, so one prebuilt minified
dependency could add minutes to a build.
@vercel

vercel Bot commented Sep 4, 2026

Copy link
Copy Markdown

@elirangoshen is attempting to deploy a commit to the Callstack Team on Vercel.

A member of the Team first needs to authorize it.

@changeset-bot

changeset-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a434812

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

This PR includes changesets to release 6 packages
Name Type
@callstack/repack Patch
@callstack/repack-plugin-expo-modules Patch
@callstack/repack-plugin-nativewind Patch
@callstack/repack-plugin-reanimated Patch
@callstack/repack-dev-server Patch
@callstack/repack-init Patch

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

@elirangoshen
elirangoshen marked this pull request as ready for review September 4, 2026 09:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

babelLoader always uses hermes-parser, unlike Metro — 90s on one prebuilt file

1 participant