Skip to content

Babel preset: Remove regenerator from Hermes dev builds - #59303

Open
robhogan wants to merge 2 commits into
mainfrom
robhogan/remove-dev-regenerator
Open

robhogan wants to merge 2 commits into
mainfrom
robhogan/remove-dev-regenerator

Conversation

@robhogan

@robhogan robhogan commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Motivation

  • Dev/release consistency in correctness and performance shape.
  • ~10% faster dev cold builds
  • More responsive dev apps (~2x faster promise handling)
  • Improved debugging support

Summary

Since #52651, Hermes profiles have lowered generators (with enableBabelRuntime: false), for...of, ?., ?? and optional catch binding in dev builds only, because Hermes couldn't show local variables when paused on a breakpoint in a native generator.

That turned out to be a Hermes bug: a breakpoint on that a yield line resolved to scope-less entry code before the body had even run. It was fixed in facebook/hermes@d1238c8 (hermes-v260318099.0.2), which main has used since #58397.

This removes enableRegenerator, so Hermes-profile dev output matches release. It also fixes a dev-only divergence - loose for-of never calls iterator.return(), so breaking out of a for...of over a generator skipped its finally in dev but not release.

Note that with the default enableBabelRuntime, generators were already native in dev, so in practice the generator change mostly affects enableBabelRuntime: false setups, including RN's own Jest. (There's no good reason generator was ever conditional on enableBabelRuntime === false - it seems to date from #44668 and probably a misunderstanding of what the regenerator argument to @babel/plugin-transform-runtime did - it doesn't make that plugin transform generators, it just rewrites the helper import.)

Native generator debugging still isn't perfect on .0.2 - stepping through try/catch can stop on a catch line that doesn't run, and breakpoints on yield* lines or on a throw after a resume don't fire. Those already affect every default config though, so there's no regression in most cases. Regenerator has its own problems, eg breakpoints often stop a line early because statements merge into its case blocks. Overall, native is now a better experience.

Detailed comparison in https://robhogan.github.io/reports/react-native-regenerator-removal/

Changelog

[GENERAL] [FIXED] - Babel preset: Hermes profiles no longer lower generators, for...of, optional chaining, nullish coalescing or optional catch binding in dev, matching release

Test Plan

Reproduced the original bug through Hermes's CDPAgent test harness with Debugger.setBreakpointByUrl. On hermes-v250829098.0.16 and v260318099.0.1, a breakpoint on a generator's last yield line pauses at the generator call with only the global scope, and every local evaluates to undefined. On v260318099.0.2 and .0.4 it pauses at the real code with all locals.

AI-driven breakpoint sweep: this preset with dev: true, enableBabelRuntime: false, before and after this change, over 248 source lines of generator, async function, async generator, exception and loop fixtures. A breakpoint was set on every line, each pause was mapped back through the source map, and every local was evaluated. Same results with eager and lazy compilation.

Hermes Identical Different, equivalent Native better Native worse Mixed (loop headers) Total
250829098.0.16 164 10 49 22 3 248
260318099.0.2 183 11 47 3 3 247
260318099.0.4 184 11 47 3 3 248

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 21, 2026
@robhogan
robhogan requested review from javache and motiz88 October 21, 2026 14:06
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Oct 21, 2026
@robhogan

robhogan commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator Author

Jest failures are legitimate due to changing some inlining behaviour - looking into that now. I might need to stack a fix under this to do it cleanly. Converting to draft for the moment.

@robhogan

Copy link
Copy Markdown
Collaborator Author

Prerequisite: #59307

meta-codesync Bot pushed a commit that referenced this pull request Oct 22, 2026
Summary:
Test only change. RN's own Jest transform (`jest/preprocessor.js`, for this repo's own tests, not public) doesn't include `babel-plugin-jest-hoist`, so `jest.mock` calls run where they're written. Several tests only saw their mocks because inline requires deferred the `require` of the module under test (or of its dependencies) until first use.

That only works for destructured requires like `const {foo} = require('./foo')` while the Babel preset lowers destructuring, which in this config it currently does only as a side effect of lowering generators with regenerator. #59303 removes that, and 114 tests across these 16 suites fail.

This makes the tests install their mocks before the modules under test load, so they pass either way, without diverging Jest's transform from the app's:

- Require modules under test after the `jest.mock` calls that affect them.
- Use mock factories instead of spies or automocks where the code under test binds a function when it loads, or loads an ESM-only package (`octokit/rest`).
- Re-require modules after `jest.resetModules()` where tests reset the registry (`publish-npm`, `Animated`).

The release script tests in particular are dangerous when their mocks silently stop applying - locally, they ran the real version bump against the checkout.

## Changelog:

[INTERNAL] [CHANGED] - Don't rely on inline requires to apply mocks in JS tests

Pull Request resolved: #59307

Test Plan: The full Jest suite passes on this PR (6,284 tests), and with #59303 applied on top (6,285), both under Node 24 with `--no-cache`. Without this PR, #59303 fails 114 tests in 16 suites, matching its CI.

Reviewed By: christophpurrer

Differential Revision: D126757286

Pulled By: javache

fbshipit-source-id: ce39217579aa9805f74786ee9b669bf411e97191
Hermes profiles have lowered generators (with `enableBabelRuntime: false`), `for...of`, `?.`, `??` and optional catch binding in dev builds only since #52651, because Hermes couldn't show local variables when paused on a breakpoint in a native generator.

That was a Hermes bug: the generator's resume prologue was stamped with the source location of its last `yield`, so a breakpoint on that line resolved to scope-less entry code. It's fixed in facebook/hermes@d1238c8c6 (hermes-v260318099.0.2), which main has used since #58397.

This removes `enableRegenerator`, so Hermes-profile dev output matches release. It also fixes a dev/release divergence - loose `for-of` never calls `iterator.return()`, so breaking out of a `for...of` over a generator skipped its `finally` in dev only.

Changelog: [General][Changed] - Babel preset: Hermes profiles no longer lower generators, `for...of`, optional chaining, nullish coalescing or optional catch binding in dev, matching release
@robhogan
robhogan force-pushed the robhogan/remove-dev-regenerator branch from 474f820 to 405fdcb Compare October 22, 2026 09:34
@robhogan
robhogan marked this pull request as ready for review October 22, 2026 09:39
@robhogan

Copy link
Copy Markdown
Collaborator Author

@javache (from #59307)

Will #59303 cause changes to our internal inline-requires transform?

Only in dev, and only to align with prod.

The difference is destructuring of CJS require:

const {foo, bar} = require('./stuff')

Is not inlined by inlineRequires, which runs after the RN preset. So with Babel lowered destructuring this was previously inlined and now isn't in release, since #58965.

It's still lowered and therefore inlined in dev && !enableBabelRuntime && enableRegenerator because lowered destructuring is a prerequisite of regenerator, dropped by this PR.

I didn't spot this in #58965. It's likely to have minimal impact at Meta because there's no change to ESM syntax, but probably a handful of 3P CJS libraries will have lost some inlining.

@robhogan

Copy link
Copy Markdown
Collaborator Author

I missed some snapshot failures for set-version-test from #59307. Added in a new commit here and also separately in #59352 if we want to land that first so this is clean.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant