React Native module resolution: three fixes
I lost a day to module resolution in React Native. Here is what actually fixed it: package exports, .mjs files in Expo, and ESM syntax that React Native will not take.
Yesterday I spent the whole day on module resolution in React Native. Here is what I found, so you do not have to lose the same day to it.
1. Package exports need a bit of extra care
If you are using newer npm libraries that ship package exports, Metro may tell you the module does not exist:
error: Error: Unable to resolve module ...
Which is particularly annoying when VS Code's autocomplete is showing you the file sitting right there. Enable module resolution from package exports in your metro config:
/**
* Metro configuration
* https://facebook.github.io/metro/docs/configuration
*
* @type {import('metro-config').MetroConfig}
*/
const config = {
resolver: {
unstable_enablePackageExports: true,
},
};
This works for react native 0.72 and up. There is more detail in the Metro docs.
If you are on Expo, there is another layer to it. I was on version 0.49 when I wrote this.
2. Expo and .mjs files
Expo does not include .mjs files by default, so packages with .mjs file paths in their package.json need more work. This tells Metro to read them:
/**
* Metro configuration
* https://facebook.github.io/metro/docs/configuration
*
* @type {import('metro-config').MetroConfig}
*/
const config = {
resolver: {
unstable_enablePackageExports: true,
sourceExts: config.resolver.sourceExts.push('mjs'),
},
};
3. Not all ESM syntax works
Packages using import.meta can fail on react native and give you undefined errors during compilation. You have two options:
- Turn off
unstable_enablePackageExports, though that is no good if you need package exports. - Force react native back to
commonjsfor the modules with unsupported ESM syntax.
For the second, edit the package.json of the problematic npm package in node_modules and point the import property at the .js file instead of the .mjs one:
".": {
"types": "./index.d.ts",
"import": {
"types": "./esm/index.d.mts",
// "default": "./esm/index.mjs",
"default": "./esm/index.js"
},
"module": "./esm/index.js",
"default": "./index.js"
},
Give metro a refresh after that, and use patch-package so the change survives in your git repo.
You may still see Metro warnings in your terminal saying it is falling back to filesystem resolution for packages without defined exports. Those are harmless and it works fine.
