ESM support
Libraries created with create-react-native-library
are pre-configured to work with ESM (ECMAScript Modules) out of the box.
You can verify whether ESM support is enabled by checking the configuration for react-native-builder-bob
in the package.json
file of the library:
"react-native-builder-bob": {
"source": "src",
"output": "lib",
"targets": [
["commonjs", { "esm": true }],
["module", { "esm": true }],
["typescript", { "esm": true }]
]
}
The "esm": true
option enables ESM-compatible output by adding the .js
extension to the import statements in the generated files. For TypeScript, it also generates 2 sets of type definitions: one for the CommonJS build and one for the ES module build.
It's recommended to specify "moduleResolution": "Bundler"
and "resolvePackageJsonImports": false
in your tsconfig.json
file to match Metro's behavior (opens in a new tab):
{
"compilerOptions": {
"moduleResolution": "Bundler",
"resolvePackageJsonImports": false
}
}
Specifying "moduleResolution": "Bundler"
means that you don't need to use file extensions in the import statements. Bob automatically adds them when possible during the build process.
To make use of the output files, ensure that your package.json
file contains the following fields:
"main": "./lib/commonjs/index.js",
"module": "./lib/module/index.js",
"types": "./lib/typescript/commonjs/src/index.d.ts",
"exports": {
".": {
"import": {
"types": "./lib/typescript/module/src/index.d.ts",
"default": "./lib/module/index.js"
},
"require": {
"types": "./lib/typescript/commonjs/src/index.d.ts",
"default": "./lib/commonjs/index.js"
}
}
},
The main
, module
and types
fields are for legacy setups that don't support the exports
field. See the Manual configuration guide for more information about those fields.
The exports
field is used by modern tools and bundlers to determine the correct entry point. Here, we specify 2 conditions:
import
: Used when the library is imported with animport
statement or a dynamicimport()
. It should point to the ESM build.require
: Used when the library is required with arequire
call. It should point to the CommonJS build.
Each condition has a types
field - necessary for TypeScript to provide the appropriate definitions for the module system. The type definitions have slightly different semantics for CommonJS and ESM, so it's important to specify them separately.
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.
You can also specify additional conditions for different scenarios, such as react-native
, browser
, production
, development
etc. Note that support for these conditions depends on the tooling you're using.
Guidelines
There are still a few things to keep in mind if you want your library to be ESM-compatible:
-
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 the library uses platform-specific extensions (e.g.,
.ios.js
or.android.js
), the ESM output will not be compatible with Node.js. 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. Bundlers such as Webpack (withresolve.fullySpecified: false
(opens in a new tab)) or Metro can handle this. It's still possible torequire
the CommonJS build directly in Node.js. -
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. -
Avoid using
"moduleResolution": "Node16"
or"moduleResolution": "NodeNext"
in yourtsconfig.json
file. They require file extensions in import statements which breaks platform-specific extension resolution. -
If you specify a
react-native
condition inexports
, make sure that it comes beforeimport
orrequire
. The conditions should be ordered from the most specific to the least specific:"exports": { ".": { "import": { "types": "./lib/typescript/module/src/index.d.ts", "react-native": "./lib/modules/index.native.js", "default": "./lib/module/index.js" }, "require": { "types": "./lib/typescript/commonjs/src/index.d.ts", "react-native": "./lib/commonjs/index.native.js", "default": "./lib/commonjs/index.js" } } }