Skip to content

Commit 5207ceb

Browse files
committed
docs: add instructions to support moduleResolution: node
1 parent f1db236 commit 5207ceb

File tree

2 files changed

+76
-23
lines changed

2 files changed

+76
-23
lines changed

docs/pages/build.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,9 @@ Example:
271271
["typescript", { "project": "tsconfig.build.json" }]
272272
```
273273

274-
The output file should be referenced in the `types` field or `exports['.'].types` field of `package.json`.
274+
The output file should be referenced in the `exports['.'].types` field of `package.json`.
275+
276+
If you need to support legacy setups that use `moduleResolution: node10` or `moduleResolution: node`, you can also add a `types` field to the `package.json` file that points to the output file.
275277

276278
#### `codegen`
277279

docs/pages/esm.md

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# ESM support
22

3+
## Default setup
4+
35
Libraries created with [`create-react-native-library`](./create.md) are pre-configured to work with ESM (ECMAScript Modules) out of the box.
46

57
You can verify whether ESM support is enabled by checking the configuration for [`react-native-builder-bob`](./build.md) in the `package.json` file of the library:
@@ -58,11 +60,16 @@ You can also specify additional conditions for different scenarios, such as `rea
5860

5961
The `./package.json` field is used to point to the library's `package.json` file. It's necessary for tools that may need to read the `package.json` file directly (e.g. [React Native Codegen](https://reactnative.dev/docs/the-new-architecture/what-is-codegen)).
6062

61-
> Note: Metro enables support for `package.json` exports by default from version [0.82.0](https://github.com/facebook/metro/releases/tag/v0.82.0). In previous versions, experimental support can be enabled by setting the `unstable_enablePackageExports` option to `true` in the [Metro configuration](https://metrobundler.dev/docs/configuration/). If this is not enabled, Metro will use the entrypoint specified in the `main` field.
63+
Using the `exports` field has a few benefits, such as:
64+
65+
- It [restricts access to the library's internals](https://nodejs.org/api/packages.html#main-entry-point-export) by default. You can explicitly specify which files are accessible with [subpath exports](https://nodejs.org/api/packages.html#subpath-exports).
66+
- It allows you to specify different entry points for different environments with [conditional exports](https://nodejs.org/api/packages.html#conditional-exports) (e.g. `node`, `browser`, `react-native`, `production`, `development` etc.). More examples can be found in the [Webpack documentation](https://webpack.js.org/guides/package-exports/#conditions).
67+
68+
> Note: Metro enables support for `package.json` exports by default from version [0.82.0](https://github.com/facebook/metro/releases/tag/v0.82.0). In previous versions, experimental support can be enabled by setting the `unstable_enablePackageExports` option to `true` in the [Metro configuration](https://metrobundler.dev/docs/configuration/). If this is not enabled, Metro will use the entrypoint specified in the `main` field. Features such as [subpath exports](https://nodejs.org/api/packages.html#subpath-exports) and [conditional exports](https://nodejs.org/api/packages.html#conditional-exports) will not work when `exports` supported is not enabled.
6269
6370
## Dual package setup
6471

65-
The previously mentioned setup only works with tools that support ES modules. If you want to support tools that don't support ESM and use the CommonJS module system, you can set up a dual package setup.
72+
The previously mentioned setup only works with tools that support ES modules. If you want to support tools that don't support ESM and use the CommonJS module system, there are a few approaches you can take.
6673

6774
A dual package setup means that you have 2 builds of your library: one for ESM and one for CommonJS. The ESM build is used by tools that support ES modules, while the CommonJS build is used by tools that don't support ES modules.
6875

@@ -82,25 +89,7 @@ To configure a dual package setup, you can follow these steps:
8289
}
8390
```
8491

85-
2. Optionally change the `main` field in your `package.json` to point to the CommonJS build:
86-
87-
```diff
88-
- "main": "./lib/module/index.js",
89-
+ "main": "./lib/commonjs/index.js",
90-
```
91-
92-
This is needed if you want to support tools that don't support the `exports` field and need to use the CommonJS build.
93-
94-
3. Optionally add a `module` field in your `package.json` to point to the ESM build:
95-
96-
```diff
97-
"main": "./lib/commonjs/index.js",
98-
+ "module": "./lib/module/index.js",
99-
```
100-
101-
The `module` field is a non-standard field that some tools use to determine the ESM entry point.
102-
103-
4. Change the `exports` field in your `package.json` to include 2 conditions:
92+
2. Change the `exports` field in your `package.json` to include 2 conditions:
10493

10594
```diff
10695
"exports": {
@@ -128,7 +117,62 @@ To configure a dual package setup, you can follow these steps:
128117

129118
The `default` field is the fallback entry point for both conditions. It's used for the actual JS code when the library is imported or required.
130119

131-
> **Important:** With this approach, the ESM and CommonJS versions of the package are treated as separate modules by Node.js as they are different files, leading to [potential issues](https://nodejs.org/docs/latest-v19.x/api/packages.html#dual-package-hazard) if the package is both imported and required in the same runtime environment. If the package relies on any state that can cause issues if 2 separate instances are loaded, it's necessary to isolate the state into a separate CommonJS module that can be shared between the ESM and CommonJS builds.
120+
3. Optionally change the `main` field in your `package.json` to point to the CommonJS build:
121+
122+
```diff
123+
- "main": "./lib/module/index.js",
124+
+ "main": "./lib/commonjs/index.js",
125+
```
126+
127+
This is needed if you want to support tools that don't support the `exports` field and need to use the CommonJS build.
128+
129+
4. Optionally add a `module` field in your `package.json` to point to the ESM build:
130+
131+
```diff
132+
"main": "./lib/commonjs/index.js",
133+
+ "module": "./lib/module/index.js",
134+
```
135+
136+
The `module` field is a non-standard field that some tools use to determine the ESM entry point.
137+
138+
5. Optionally add a `types` field in your `package.json` to point to the CommonJS type definitions:
139+
140+
```diff
141+
"main": "./lib/commonjs/index.js",
142+
"module": "./lib/module/index.js",
143+
+ "types": "./lib/typescript/commonjs/src/index.d.ts",
144+
```
145+
146+
This is necessary to support legacy TypeScript setup, i.e. which have `moduleResolution: "node10"` or `moduleResolution: "node"` under the `compilerOptions` section in the `tsconfig.json`.
147+
148+
Putting it all together, the fields in your `package.json` file should look like this:
149+
150+
```json
151+
{
152+
"main": "./lib/commonjs/index.js",
153+
"module": "./lib/module/index.js",
154+
"types": "./lib/typescript/commonjs/src/index.d.ts",
155+
"exports": {
156+
".": {
157+
"import": {
158+
"types": "./lib/typescript/module/src/index.d.ts",
159+
"default": "./lib/module/index.js"
160+
},
161+
"require": {
162+
"types": "./lib/typescript/commonjs/src/index.d.ts",
163+
"default": "./lib/commonjs/index.js"
164+
}
165+
},
166+
"./package.json": "./package.json"
167+
}
168+
}
169+
```
170+
171+
### Dual package hazard
172+
173+
With this approach, the ESM and CommonJS versions of the package are treated as separate modules by Node.js as they are different files. On Node.js, `import` will load the ESM package and `require` will load the CommonJS package, leading to [potential issues](https://nodejs.org/docs/latest-v19.x/api/packages.html#dual-package-hazard) if the package is both imported and required in the same runtime environment.
174+
175+
If the library relies on any state that can cause issues if 2 separate instances are loaded (e.g. global state, constructors, react context etc.), it's necessary to isolate the state into a separate CommonJS module that can be shared between the ESM and CommonJS builds.
132176

133177
## Guidelines
134178

@@ -189,3 +233,10 @@ There are still a few things to keep in mind if you want your library to be ESM-
189233
"./package.json": "./package.json"
190234
}
191235
```
236+
237+
## References
238+
239+
- [Node.js documentation on ESM](https://nodejs.org/docs/latest/api/esm.html)
240+
- [Publishing dual module ESM libraries](https://satya164.page/posts/publishing-dual-module-esm-libraries)
241+
- [Are the types wrong?](https://github.com/arethetypeswrong/arethetypeswrong.github.io)
242+
- [tshy - TypeScript HYbridizer](https://github.com/isaacs/tshy)

0 commit comments

Comments
 (0)