Skip to content

Commit 7799977

Browse files
committed
docs: add compatibility list to ESM docs
1 parent 1093e42 commit 7799977

File tree

2 files changed

+46
-15
lines changed

2 files changed

+46
-15
lines changed

docs/pages/build.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ In addition, the following options are supported:
176176

177177
##### `esm`
178178

179-
Setting this option to `true` will output ES modules compatible code for Node.js 12+, modern browsers and other tools that support `package.json`'s `exports` field.
179+
Setting this option to `true` will output ES modules compatible code for Node.js 12+, modern browsers and tools that support `package.json`'s `exports` field.
180180

181181
See the [ESM support](./esm.md) guide for more details.
182182

@@ -241,7 +241,9 @@ Example:
241241

242242
Enable compiling source files with Babel and use CommonJS module system. This is essentially the same as the `module` target and accepts the same options, but transforms the `import`/`export` statements in your code to `require`/`module.exports`.
243243

244-
This is useful for supporting usage of this module with `require` in Node versions older than 20 (it can still be used with `import` for Node.js 12+ if `module` target with `esm` is enabled), and some tools such as [Jest](https://jestjs.io). The output file should be referenced in the `main` field. If you have a [dual package setup](esm.md#dual-package-setup) with both ESM and CommonJS builds, it needs to be specified in `exports['.'].require` field of `package.json`.
244+
This is useful for supporting tools that don't support ES modules yet, see [the Compatibility section in our ESM guide](./esm.md#compatibility) for more details.
245+
246+
The output file should be referenced in the `main` field. If you have a [dual package setup](esm.md#dual-package-setup) with both ESM and CommonJS builds, it needs to be specified in `exports['.'].require` field of `package.json`.
245247

246248
Example:
247249

docs/pages/esm.md

+42-13
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ You can verify whether ESM support is enabled by checking the configuration for
1212
"output": "lib",
1313
"targets": [
1414
["module", { "esm": true }],
15-
["commonjs", { "esm": true }],
1615
"typescript"
1716
]
1817
}
@@ -63,9 +62,7 @@ The `./package.json` field is used to point to the library's `package.json` file
6362
Using the `exports` field has a few benefits, such as:
6463

6564
- 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.
65+
- 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`, `module`, `react-native`, `production`, `development` etc.).
6966

7067
## Dual package setup
7168

@@ -174,31 +171,63 @@ With this approach, the ESM and CommonJS versions of the package are treated as
174171

175172
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.
176173

174+
## Compatibility
175+
176+
[Node.js](https://nodejs.org) v12 and higher natively support ESM and the `exports` field. However, an ESM library can synchronously loaded in a CommonJS environment in only in recent Node.js versions. The following Node.js versions support synchronous `require()` for ESM libraries without any flags or warnings:
177+
178+
- v20.19.0 and higher (LTS)
179+
- v22.12.0 and higher (LTS)
180+
- v23.4.0 and higher
181+
182+
Older versions can still load your library asynchronously using `import()`.
183+
184+
Most modern tools such as [Webpack](https://webpack.js.org), [Rollup](https://rollupjs.org), [Vite](https://vitejs.dev) etc. also support ESM and the `exports` field. See the supported conditions in the [Webpack documentation](https://webpack.js.org/guides/package-exports/#conditions).
185+
186+
[Metro](https://metrobundler.dev) 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`](https://metrobundler.dev/docs/package-exports/) in the Metro 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.
187+
188+
[Jest](https://jestjs.io) supports the `exports` field, but doesn't support ESM natively. Experimental support is [available under a flag](https://jestjs.io/docs/ecmascript-modules), but requires changes to how tests are written. It can still load ESM libraries using a transform such as [`babel-jest`](https://github.com/jestjs/jest/tree/main/packages/babel-jest).
189+
177190
## Guidelines
178191

179192
There are still a few things to keep in mind if you want your library to be ESM-compatible:
180193

181-
- Avoid using default exports in your library. Named exports are recommended. Default exports produce a CommonJS module with a `default` property, which will work differently than the ESM build and can cause issues.
194+
- Avoid using default exports in your library. Named exports are recommended. Default exports produce a CommonJS module with a `default` property, which will work differently than the ESM build and can cause issues if you have a dual package setup. Needing to use `.default` in CommonJS environment may also be confusing for users.
182195
- If the library uses platform-specific extensions (e.g., `.ios.js` or `.android.js`), the ESM output will not be compatible with Node.js, i.e. it's not possible to use the library in Node.js with `import` syntax. It's necessary to omit file extensions from the imports to make platform-specific extensions work, however, Node.js requires file extensions to be present.
183196

184-
Bundlers such as Metro can handle this without additional configuration. Other bundlers may need to be configured to make extensionless imports to work, (e.g. it's necessary to specify [`resolve.fullySpecified: false`](https://webpack.js.org/configuration/module/#resolvefullyspecified) for Webpack).
197+
While Bob automatically adds file extensions to the import statements during the build process if `esm` is set to `true`, it will skip the imports that reference files with platform-specific extensions to avoid breaking the resolution.
198+
199+
Bundlers such as Metro can handle imports without file extensions for ESM without additional configuration. Other bundlers may need to be configured to make extensionless imports to work, (e.g. it's necessary to specify [`resolve.fullySpecified: false`](https://webpack.js.org/configuration/module/#resolvefullyspecified) for Webpack).
185200

186201
It's still possible to use the library in Node.js using the CommonJS build with `require`:
187202

188203
```js
189204
const { foo } = require('my-library');
190205
```
191206

192-
Alternatively, if you want to be able to use the library in Node.js with `import` syntax, you can use `require` to import code with platform-specific extensions in your library:
207+
Alternatively, if you want to be able to use the library in Node.js with `import` syntax, there are a few options:
193208

194-
```js
195-
// will import `foo.native.js`, `foo.ios.js`, `foo.js` etc.
196-
const { foo } = require('./foo');
197-
```
209+
- Use `Platform.select` instead of platform-specific extensions:
210+
211+
```js
212+
import { Platform } from 'react-native';
213+
214+
const foo = Platform.select({
215+
android: require('./fooAndroid.js'),
216+
ios: require('./fooIOS.js'),
217+
default: require('./fooFallback.js'),
218+
});
219+
```
220+
221+
- Use `require` to import code with platform-specific extensions in your library:
222+
223+
```js
224+
// will import `foo.native.js`, `foo.ios.js`, `foo.js` etc.
225+
const { foo } = require('./foo');
226+
```
198227

199-
Make sure to have a file without any platform-specific extensions that will be loaded by Node.js.
228+
Make sure to have a file without any platform-specific extensions that will be loaded by Node.js.
200229

201-
Also note that if your module (e.g. `foo.js` in this case) contains ESM syntax, it will only work on Node.js 20 or newer.
230+
Also note that if your module (e.g. `foo.js` in this case) contains ESM syntax, it will only work on a recent Node.js version. See [Compatibility](#compatibility) section for more information.
202231

203232
- Avoid using `.cjs`, `.mjs`, `.cts` or `.mts` extensions. Metro always requires file extensions in import statements when using `.cjs` or `.mjs` which breaks platform-specific extension resolution.
204233
- Avoid using `"moduleResolution": "node16"` or `"moduleResolution": "nodenext"` in your `tsconfig.json` file. They require file extensions in import statements which breaks platform-specific extension resolution.

0 commit comments

Comments
 (0)