Skip to content

Commit 49ce56d

Browse files
committed
RFC0894: Removing deep imports from react-native
1 parent 7b3286f commit 49ce56d

File tree

1 file changed

+161
-0
lines changed

1 file changed

+161
-0
lines changed
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
---
2+
title: Removing deep imports from react-native
3+
author:
4+
- Alex Hunt <[email protected]>
5+
date: 2024-04-04
6+
---
7+
8+
# RFC0894: Removing deep imports from `react-native`
9+
10+
## Summary
11+
12+
In this RFC, we propose removing **subpath imports** (or "**deep imports**") from React Native's JavaScript API.
13+
14+
These imports, such as the below, allow direct access to internal modules. While historically supported, we plan to phase out subpath imports to reduce our API surface area.
15+
16+
```ts
17+
// Before - subpath imports allowed
18+
import {Alert, AlertType} from 'react-native/Libraries/Alert/Alert';
19+
20+
// After - must import from 'react-native'
21+
import {Alert, AlertType} from 'react-native';
22+
```
23+
24+
Doing this will:
25+
26+
- Define a clear public API contract for `react-native` that minimises future breaking changes.
27+
- Improve maintainability for React Native and its ecosystem.
28+
29+
#### Planned sequencing
30+
31+
- React Native 0.80: Deprecation of subpath imports, introduce runtime warnings.
32+
- React Native 0.82 (or later): Removal of subpath imports via `"exports"` and the Strict TypeScript API.
33+
34+
Between deprecation and removal, we will hold an API consultation period over ***at least*** two releases.
35+
36+
## Motivation
37+
38+
We’re driven to remove subpath imports for these reasons:
39+
40+
- **Simpler and more stable API**: Limiting imports to one runtime entry point reduces reliance on unstable internal modules, giving us exact control over the public API.
41+
- **Package encapsulation**: Again, keeps our internal structure flexible, letting us refactor without breaking user code.
42+
- **Simplify React Native upgrades**.
43+
- **Alignment with standards**: Modern JavaScript packages, including React, favour single entry point APIs, making React Native more consistent and intuitive.
44+
- **Reduces standards conflicts**. (e.g. under [`package.json` `"exports"` support](https://metrobundler.dev/docs/package-exports/#breaking-import-specifiers-are-matched-exactly), we can't apply [platform-specific module](https://reactnative.dev/docs/platform-specific-code) resolution on subpaths.)
45+
46+
### Towards a stable React Native API
47+
48+
**Without a controlled package entry point, we cannot provide a stable React Native API.**
49+
50+
Our vision with React Native's Stable API effort is to simplify and better define how apps and frameworks consume React Native. Eventually, these changes will let us iterate towards a stable, long-term `react-native` package we aim to keep unbroken across releases.
51+
52+
## Detailed design
53+
54+
### Clarifying scope
55+
56+
When removing access to subpath imports, we refer to the *runtime API* of the `react-native` npm package.
57+
58+
- **Included**: JavaScript modules imported by an app or library, that are bundled by Metro and executed at runtime.
59+
- **Not included**: Non-runtime support files such as scripts, e.g. `./cli.js`, `./jest-preset.js`, `./jest/`.
60+
61+
In practical terms, this means imports to modules under `react-native/Libraries/*` will be removed, in favour of the **main entry point** (`react-native/index.js`).
62+
63+
#### Removal of internal APIs is a breaking change
64+
65+
This move, shipped first as a non-breaking deprecation, will intentionally result in breaking changes for developers.
66+
67+
The current "public API" of `react-native` is unbounded, since any module, and any potentially internal, intermediary code can be accessed.
68+
69+
```ts
70+
// Before - subpath imports allowed
71+
import {Alert, AlertType} from 'react-native/Libraries/Alert/Alert';
72+
import NativeAlertManager from 'react-native/Libraries/Alert/NativeAlertManager';
73+
74+
// After - must import from 'react-native'
75+
import {Alert, AlertType} from 'react-native';
76+
// - ✅ Alert and AlertType APIs are public
77+
// - 🚫 Internal NativeAlertManager.js module is private and inaccessible
78+
```
79+
80+
> [!Note]
81+
> **💡 Most apps**, if following our documented APIs, should already be using the `'react-native'` import path exclusively.
82+
83+
### What will be in the public API?
84+
85+
By removing subpath imports, we will scope the `react-native` package down to a narrower, enumerable public API, using the existing `index.js` and `index.d.ts` exports as a starting point.
86+
87+
All existing APIs exported from the main `'react-native'` import path will be **unchanged**. As of March 2025, we now define a strict list of type exports, see [`index.js.flow`](/TODO) — symbols that are not in this file will not be part of the public API.
88+
89+
#### Framework considerations
90+
91+
One index file for React Native's public API is our ideal final state — for apps, libraries, and [React Native Frameworks](./0759-react-native-frameworks.md). However, we may revise this decision down the line if there emerges a strong need for an escape hatch for specific internals. The intent is for us to narrow from all files → explicit entry point(s) to our packages.
92+
93+
This will be open to input during the API consultation period, and we'll directly engage with Expo and our partners around this.
94+
95+
#### First party usages
96+
97+
We will also address any first party usages of subpath imports within React Native itself — making sure these APIs are exposed on the main module.
98+
99+
Already actioned:
100+
101+
- `CodegenTypes`, `codegenNativeComponent`, `codegenNativeCommands` (https://github.com/facebook/react-native/pull/49854).
102+
103+
### Performance considerations
104+
105+
Routing all module imports through one index file has potential performance implications. We believe these will be negligible.
106+
107+
- **Existing index optimisations unchanged**: The shape of `index.js` is unchanged, preserving the existing inline `require()` pattern for internal imports. Metro will correctly optimise these imports in the production bundle.
108+
- (Shakeable ESM imports, which do not rely on this special pattern, are something we want to pursue in Metro down the line.)
109+
110+
## Adoption strategy
111+
112+
The impact of these changes is potentially considerable, particularly for React Native libraries and Frameworks. After an initial deprecation, we will take our time and listen to the ecosystem before shipping a hard removal.
113+
114+
- **Deprecate in 0.80**, with an API consultation period of at least two major releases.
115+
- **Offer tools**: Provide migration aids, including dev-mode warnings, an ESLint rule, and via related TypeScript changes.
116+
- **Fully remove when confident**.
117+
118+
### Deprecation period
119+
120+
#### Dedicated announcement post
121+
122+
We'll communicate the impact of this change to users in the 0.80 release post on the React Native website, as well as a dedicated deep-dive post.
123+
124+
#### Dedicated GitHub discussion thread
125+
126+
As well as monitoring new GitHub issues, this thread (maintained by [@huntie](https://github.com/huntie)) will form the main public communication channel during our API consultation period.
127+
128+
https://github.com/react-native-community/discussions-and-proposals/discussions/893
129+
130+
#### Lint and runtime warnings in 0.80
131+
132+
We aim to ship development-time warnings both in ESLint (surfacing in text editors/command line) and via a dev-only Metro transform plugin (surfacing in the JavaScript console).
133+
134+
```ts
135+
import {Alert} from 'react-native/Libraries/Alert/Alert';
136+
// ^ Warning: Deep imports from React Native are deprecated.
137+
// 'Alert' should be imported from 'react-native'.
138+
// Autofix:
139+
// import {Alert} from 'react-native';
140+
```
141+
142+
**Under the hood**: As we are unsure about the % usage of [@react-native/eslint-config](https://www.npmjs.com/package/@react-native/eslint-config), we believe integrating via Metro is important for consistent visibility.
143+
144+
### Eventual strong enforcement
145+
146+
Again, it's important that we give the community sufficient time to adapt to this change before we action a hard removal — we anticipate this will be during **H2 2025**.
147+
148+
Once the API consultation period is over, and we have reasonable confidence from libraries and partners, we'll action a hard removal of subpath imports via the [`package.json` `"exports"` spec](https://nodejs.org/docs/latest-v18.x/api/packages.html#exports).
149+
150+
**Future `react-native` package.json**
151+
152+
This will functionally disallow unlisted subpath imports under TypeScript, Metro, Jest, Node.js, and others.
153+
154+
```json5
155+
{
156+
"exports": {
157+
".": "./index.js", // 🚫 Subpath imports disallowed
158+
// ... (non-runtime exports)
159+
}
160+
}
161+
```

0 commit comments

Comments
 (0)