Published on

Navigating React Native Module Resolution: Lessons from Yesterday's Battle

Authors

Yesterday, I was deep in the trenches grappling with module resolution in React Native. If you've been there, you know the feeling. If not, let me spare you the pain with some things I learned along the way.

1. Package exports need opting in.

If you're using newer npm libraries that use package exports, metro will complain that the module doesn't exist.

error: Error: Unable to resolve module ...

Infuriating, and confusing especially because VS Code autocomplete tells you the file is right there. Turns out, package exports are an unstable feature in Metro, which currently require explicit opt-in.

In your metro config, enable module resolution from package exports like this:

/**
 * 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. You can read more about it here.

Now, if you're using Expo (version 0.49 when I wrote this), there's another layer to this.

2. Expo doesn't enable ESM modules by default

I.e. if your packages have .mjs module files in their package.json, you've got a bit more to do, as Expo doesn't include .mjs files by default.

Here's how you direct Metro to read .mjs files:

/**
 * 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'),
  },
};

You might think that's it, but hang on...

3. Not all ESM syntax is supported as yet

For instance, packages using import.meta won't work in react native, resulting in undefined errors during compilation.

You've got some options:

  1. Either, turn off unstable_enablePackageExports, though this destroys the purpose if you really need package exports.
  2. Or, force react native to revert to commonjs for modules with unsupported ESM syntax. For the latter, tweak the package.json of the problematic npm package in node_modules. Direct the import property to the .js instead of the .mjs file:
".": {
      "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"
    },

After making the adjustments, give metro a refresh. Remember to use patch-package to keep these changes in your git repo.

You might still see some Metro warnings in your terminal, indicating it's falling back to filesystem resolution for packages without defined exports. Don't worry, it's harmless and works just fine.

I hope my day's struggles have paved a smoother path for you. Keep coding, and remember that every challenge is an opportunity to learn and grow!