Skip to content

Commit 75f1017

Browse files
jbromaclaude
andauthored
docs: add module resolution documentation (#1338)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 49e0654 commit 75f1017

2 files changed

Lines changed: 300 additions & 0 deletions

File tree

website/src/latest/docs/features/_meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[
2+
"module-resolution",
23
"code-splitting",
34
"module-federation",
45
"dev-server",
Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,300 @@
11
# Module resolution
2+
3+
Module resolution is the process by which a bundler determines which file to load when you import a module. In React Native, this process has unique requirements—platform-specific files, scaled assets, and the `react-native` condition in package exports all need special handling.
4+
5+
Re.Pack is designed to match [Metro's resolution behavior](https://metrobundler.dev/docs/resolution) as closely as possible, ensuring that projects migrating from Metro or using libraries designed for Metro work correctly.
6+
7+
Getting module resolution right ensures that:
8+
9+
- Platform-specific files (`.ios.js`, `.android.js`) load correctly for each target
10+
- Libraries with React Native-specific entry points work as expected
11+
- Scaled assets (`@2x`, `@3x` images) resolve properly
12+
13+
## How Re.Pack resolves modules
14+
15+
Re.Pack configures the underlying bundler's resolver to match Metro's behavior. When using Rspack, resolution is handled by [rspack-resolver](https://github.com/unrs/rspack-resolver)—a Rust port of [enhanced-resolve](https://github.com/webpack/enhanced-resolve) with the same interface. When using webpack, the original enhanced-resolve is used.
16+
17+
The [`getResolveOptions()`](/api/utils/get-resolve-options) utility returns the configuration needed to match Metro's resolution behavior:
18+
19+
```ts
20+
import * as Repack from "@callstack/repack";
21+
22+
export default (env) => {
23+
return {
24+
resolve: {
25+
...Repack.getResolveOptions(),
26+
},
27+
};
28+
};
29+
```
30+
31+
Resolution happens per-platform—each build (iOS, Android, etc.) runs as a separate bundler process with platform-specific resolution configured automatically based on the target platform.
32+
33+
## Platform-specific resolution
34+
35+
React Native allows you to create platform-specific versions of files using special extensions. Re.Pack resolves these extensions in the following order:
36+
37+
1. `.{platform}.{ext}` (e.g., `.ios.js`, `.android.js`)
38+
2. `.native.{ext}` (when `preferNativePlatform: true`, which is the default)
39+
3. `.{ext}` (base extension)
40+
41+
### Example
42+
43+
Given a request for `./component` on iOS, Re.Pack will look for files in this order:
44+
45+
```
46+
component.ios.js → Platform-specific (highest priority)
47+
component.native.js → Native fallback
48+
component.js → Base file (lowest priority)
49+
```
50+
51+
The same request on Android:
52+
53+
```
54+
component.android.js → Platform-specific (highest priority)
55+
component.native.js → Native fallback
56+
component.js → Base file (lowest priority)
57+
```
58+
59+
### Supported source extensions
60+
61+
Re.Pack supports these source file extensions by default:
62+
63+
- `.js`, `.jsx`
64+
- `.ts`, `.tsx`
65+
- `.json`
66+
67+
Each of these can be combined with platform and native extensions:
68+
69+
```
70+
.ios.js, .ios.jsx, .ios.ts, .ios.tsx, .ios.json
71+
.native.js, .native.jsx, .native.ts, .native.tsx, .native.json
72+
.android.js, .android.jsx, .android.ts, .android.tsx, .android.json
73+
```
74+
75+
### Disabling native extension fallback
76+
77+
If you don't want `.native.*` files to be resolved as fallbacks, set `preferNativePlatform` to `false`:
78+
79+
```ts
80+
Repack.getResolveOptions({
81+
preferNativePlatform: false,
82+
});
83+
```
84+
85+
:::tip
86+
87+
This is useful when building for non-native platforms (like web) where `.native.*` files might contain React Native-specific code that won't work in a browser.
88+
89+
:::
90+
91+
With this setting, a request for `./component` on a `web` platform would only check:
92+
93+
```
94+
component.web.js → Platform-specific
95+
component.js → Base file (no .native.js fallback)
96+
```
97+
98+
## Package resolution (main fields)
99+
100+
:::info What are main fields?
101+
102+
Main fields are `package.json` properties that point to a package's entry file. Packages can specify different entry points for different environments—`main` for Node.js, `browser` for web, `react-native` for React Native.
103+
104+
:::
105+
106+
When resolving a package's entry point, Re.Pack checks these fields in `package.json` in order:
107+
108+
1. `react-native` — React Native-specific entry point
109+
2. `browser` — Browser-compatible entry point
110+
3. `main` — Standard Node.js entry point
111+
112+
This matches Metro's default configuration and ensures React Native-optimized code is preferred.
113+
114+
### Example
115+
116+
Given this `package.json`:
117+
118+
```json
119+
{
120+
"name": "some-library",
121+
"main": "lib/index.js",
122+
"browser": "lib/browser.js",
123+
"react-native": "lib/native.js"
124+
}
125+
```
126+
127+
Re.Pack will resolve to `lib/native.js` because `react-native` has the highest priority.
128+
129+
:::tip
130+
131+
For more details on how main fields work, see the [resolve.mainFields](https://rspack.dev/config/resolve#resolvemainfields) documentation.
132+
133+
:::
134+
135+
## Package exports (conditional exports)
136+
137+
:::info What are package exports?
138+
139+
Package exports (`exports` field in `package.json`) are a modern replacement for main fields. They let packages define multiple entry points (e.g., `pkg/utils`), serve different code per environment, and hide internal files. See the [Node.js docs](https://nodejs.org/api/packages.html#package-entry-points) for details.
140+
141+
:::
142+
143+
Modern packages use the `exports` field in `package.json` to define entry points with conditions. Re.Pack supports this through the `enablePackageExports` option.
144+
145+
:::caution
146+
147+
Package exports support is **disabled by default** (`enablePackageExports: false`) to maintain backwards compatibility with existing React Native projects. Enable it explicitly if your dependencies require it.
148+
149+
:::
150+
151+
### Enabling package exports
152+
153+
```ts
154+
Repack.getResolveOptions({
155+
enablePackageExports: true,
156+
});
157+
```
158+
159+
### How conditional exports work
160+
161+
When enabled, Re.Pack uses the `react-native` condition to resolve packages. The resolver also differentiates between ESM and CommonJS:
162+
163+
- **ESM imports**: Uses conditions `['react-native', 'import']`
164+
- **CommonJS requires**: Uses conditions `['react-native', 'require']`
165+
166+
:::tip
167+
168+
These ESM/CJS conditions are always configured (via `byDependency`) to support [package imports](https://nodejs.org/api/packages.html#subpath-imports), even when `enablePackageExports` is `false`.
169+
170+
:::
171+
172+
### Example package with exports
173+
174+
```json
175+
{
176+
"name": "modern-library",
177+
"exports": {
178+
".": {
179+
"react-native": "./dist/native/index.js",
180+
"import": "./dist/esm/index.js",
181+
"require": "./dist/cjs/index.js",
182+
"default": "./dist/index.js"
183+
},
184+
"./utils": {
185+
"react-native": "./dist/native/utils.js",
186+
"import": "./dist/esm/utils.js",
187+
"require": "./dist/cjs/utils.js"
188+
}
189+
}
190+
}
191+
```
192+
193+
With `enablePackageExports: true`, importing this package in React Native will resolve to the `react-native` condition entry points.
194+
195+
:::warning
196+
197+
Some packages may have `exports` configurations that work differently than their `main`/`react-native` field configurations. Test thoroughly when enabling this option in existing projects.
198+
199+
:::
200+
201+
## Asset resolution
202+
203+
Re.Pack handles scaled assets (images with `@1x`, `@2x`, `@3x` suffixes) automatically using `extensionAlias` configuration.
204+
205+
### Supported scalable assets
206+
207+
Images that support resolution scaling:
208+
209+
- `bmp`, `gif`, `jpg`, `jpeg`, `png`, `psd`, `svg`, `webp`, `tiff`
210+
211+
### How scaled resolution works
212+
213+
When you import an image like `./icon.png`, Re.Pack's `extensionAlias` configuration allows the resolver to find scaled variants:
214+
215+
```
216+
icon@0.75x.png
217+
icon@1x.png
218+
icon@1.5x.png
219+
icon@2x.png
220+
icon@3x.png
221+
icon@4x.png
222+
icon.png
223+
```
224+
225+
The actual asset selection (choosing the right scale for the device) happens at runtime through React Native's asset system.
226+
227+
```jsx
228+
// Import resolves to the appropriate scaled variant
229+
import icon from './assets/icon.png';
230+
231+
// React Native selects the correct scale at runtime
232+
<Image source={icon} />
233+
```
234+
235+
### Other supported asset types
236+
237+
Re.Pack also supports these non-scalable asset types:
238+
239+
- **Video**: `m4v`, `mov`, `mp4`, `mpeg`, `mpg`, `webm`
240+
- **Audio**: `aac`, `aiff`, `caf`, `m4a`, `mp3`, `wav`
241+
- **Documents**: `html`, `pdf`, `yaml`, `yml`
242+
- **Fonts**: `otf`, `ttf`
243+
- **Other**: `zip`, `obj`
244+
245+
## Troubleshooting
246+
247+
Most resolution issues can be solved with two configuration options:
248+
249+
- **`resolve.alias`** — Redirect imports to a different module or file
250+
- **`module.rules`** — Control how specific modules are processed
251+
252+
```ts
253+
export default (env) => {
254+
return {
255+
resolve: {
256+
alias: {
257+
// Redirect problematic package to a compatible version
258+
'legacy-package': 'legacy-package/dist/react-native',
259+
},
260+
},
261+
module: {
262+
rules: [
263+
{
264+
// Force specific files through a loader
265+
test: /problematic-module/,
266+
use: 'babel-loader',
267+
},
268+
],
269+
},
270+
};
271+
};
272+
```
273+
274+
### Package not resolving correctly
275+
276+
1. **Check the package's `package.json`** — Look at `main`, `react-native`, `browser`, and `exports` fields
277+
2. **Verify platform extensions** — Ensure platform-specific files use correct naming (`.ios.js`, not `.iOS.js`)
278+
3. **Check `enablePackageExports`** — Some modern packages require this to be `true`
279+
280+
### Platform-specific files not being picked up
281+
282+
1. **Verify file naming** — Extensions must be lowercase (`.ios.js`, not `.IOS.js`)
283+
2. **Check the platform value** — Ensure you're building for the correct target platform
284+
3. **Inspect resolve configuration** — Log the output of `getResolveOptions()` to verify extensions order
285+
286+
### Package exports compatibility issues
287+
288+
When enabling `enablePackageExports`, some packages may resolve differently:
289+
290+
1. **Compare with Metro** — Test the same import in a Metro-bundled project
291+
2. **Check condition order** — The `react-native` condition should take precedence
292+
3. **Inspect the package** — Some packages have incorrect or incomplete `exports` configurations
293+
294+
## Related documentation
295+
296+
- [getResolveOptions](/api/utils/get-resolve-options) — API reference
297+
- [Rspack resolve configuration](https://rspack.dev/config/resolve) — Rspack resolver options
298+
- [webpack resolve configuration](https://webpack.js.org/configuration/resolve/) — webpack resolver options
299+
- [Code Splitting](/docs/features/code-splitting) — For chunk resolution
300+
- [Glossary](/docs/resources/glossary) — Terminology reference

0 commit comments

Comments
 (0)