Skip to content

Inline destructured requires - #2087

Merged
robhogan merged 1 commit into
mainfrom
robhogan/inline-destructured-requires
Oct 23, 2026
Merged

robhogan merged 1 commit into
mainfrom
robhogan/inline-destructured-requires

Conversation

@robhogan

Copy link
Copy Markdown
Collaborator

Summary

Inline requires only inlines requires assigned to an identifier, ie const foo = require('foo') or const foo = require('foo').foo. Despite what the plugin's doc comment says, const {foo, bar} = require('foo') has never been inlined - it only appeared to work because Babel presets used to lower destructuring into the identifier form first.

react/react-native#58965 stopped @react-native/babel-preset lowering destructuring for Hermes profiles, so since then destructured CommonJS requires are evaluated eagerly at module init. ESM imports aren't affected.

This adds direct support for top-level object patterns with plain, renamed or string keys. Each reference becomes require('foo').bar, exactly what the lowered form produced. With memoizeCalls, a module's properties share one memoised module, so each scope requires it at most once:

var _foo;
(_foo || (_foo = require("foo"))).a();
_foo.b.x();

Defaults, rest elements, nested patterns and computed keys are left alone, as are reassigned bindings and properties assigned on the module elsewhere - the same constraints as the existing identifier forms.

Changelog: [Performance] Inline destructured CommonJS requires (const {foo} = require('foo')) under inlineRequires

Test plan

New cases cover renamed and string keys, references across scopes and branches, unused requires and each unsupported pattern, with and without memoizeCalls.

End to end, I swapped this build into React Native's own Jest transform, which runs inline requires, on react/react-native#59303 (which preserves destructuring in that config). The 17 suites that relied on inline requires to load modules after their jest.mock calls pass in their original form, before react/react-native#59307 and react/react-native#59352 fixed them: 292 tests and 70 snapshots, against 110 failures without this change. RN's full suite passes too (6,289 tests).

`inlineRequires` only inlined requires assigned to an identifier, so `const {foo} = require('foo')` stayed eager at module scope unless a Babel preset had lowered the destructuring first. `@react-native/babel-preset` stopped lowering destructuring for Hermes profiles in react/react-native#58965.

This inlines top-level object patterns with plain, renamed or string keys directly: each reference becomes `require('foo').foo`, matching the lowered form. With `memoizeCalls`, a module's properties share one memoized module. Defaults, rest elements, nesting and computed keys are left alone, as are reassigned bindings and properties assigned on the module.

Changelog: [Performance] Inline destructured CommonJS requires (`const {foo} = require('foo')`) under `inlineRequires`
@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 22, 2026
@robhogan
robhogan merged commit 104590a into main Oct 23, 2026
16 checks passed
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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants