Search code examples
react-nativebabeljsbabel-loadermetro-bundlerbabel-plugin-module-resolver

React Native - babel.config.js alias not seeing files outside the current working directory


So, my project is monorepo. There are folders for web application, mobile application, etc. And there is also a common folder, where gathered all the reusable components and utils.

<repo-name>
├── app // mobile app
│   ├── node_modules
│   ├── src
│   │   ├── views
│   │   │   ├── Homepage
│   │   │   │   └── index.tsx
│   │   └── App.tsx
│   ├── babel.config.js
│   ├── tsconfig.json
│   └── ...
├── common
│   ├── utils
│   │   └── format.ts
├── web
│   ├── app
│   │   ├── tsconfig.json
│   │   └── ...
│   ├── landing
│   │   ├── tsconfig.json
│   │   └── ...

App.tsx has schematically the next content:

import React from "react"
import Homepage from "views/Homepage"
import { someFunction } from "@common/utils/format"

export default
class App
extends React.Component<any, any> {
    render() {
        return <>
            <h1>{someFunction("kappa")}</h1>
            <Homepage />
        <>
    }
}

Mobile app's tsconfig.json looks like this:

{
    "extends": "@tsconfig/react-native/tsconfig.json",
    "compilerOptions": {
        "baseUrl": ".",
        "experimentalDecorators": true,
        "useDefineForClassFields": true,
        "strictNullChecks": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "paths": {
            "@common/*": [
                "../common/*",
            ],
            "*": [
                "./src/*",
                "node_modules",
            ],
        }
    },
}

and the babel.config.js is this:

module.exports = {
    presets: ['module:metro-react-native-babel-preset'],
    plugins: [
        [
            "@babel/plugin-transform-flow-strip-types",
            { legacy: true },
        ],
        [
            "@babel/plugin-proposal-decorators",
            { legacy: true },
        ],
        [
            "@babel/plugin-proposal-class-properties",
            { "loose": false }
        ],
        [
            'module-resolver',
            {
                root: ['./src'],
                extensions: ['.ios.js', '.android.js', '.js', '.ts', '.tsx', '.json'],
                alias: {
                    "@common": "../common",
                }
            }
        ]
    ]
};

Basically, what's going on.
I've created alias for common folder content to be imported as @common/...
Also, all content from src folder can be imported without relative paths.

This aliases are configured in both tsconfig.json and babel.config.json. I'm using VSCode, and autocomplete for @common alias works flawlessly.

But in runtime (inside Metro CLI) I'm getting this error:

Error: Unable to resolve module ../../common/utils/format from /home/<username>/projects/<project>/<repo-name>/app/src/App.tsx: 

None of these files exist:
  * ../common/utils/format(.native|.android.js|.native.js|.js|.android.jsx|.native.jsx|.jsx|.android.json|.native.json|.json|.android.ts|.native.ts|.ts|.android.tsx|.native.tsx|.tsx)
  * ../common/utils/format/index(.native|.android.js|.native.js|.js|.android.jsx|.native.jsx|.jsx|.android.json|.native.json|.json|.android.ts|.native.ts|.ts|.android.tsx|.native.tsx|.tsx)

As you can see, path for @common is resolved perfectly, but for some reason babel does not see format.ts file.

What I'm doing wrong? Is there any caveats, any workarounds? Any help appreciated. Spent abount 4 hours on this problem and found nothing useful. Thanks in advance


Solution

  • Okay, so, basically, this is not a problem of the Babel. Turns out that Metro does not know a thing about folders outside the project.

    Well, all you need is to add watchFolders property to metro.config.js:

    /**
     * Metro configuration for React Native
     * https://github.com/facebook/react-native
     *
     * @format
     */
    
    const path = require("path")
    
    module.exports = {
      transformer: {
        getTransformOptions: async () => ({
          transform: {
            experimentalImportSupport: false,
            inlineRequires: true,
          },
        }),
      },
      // here we should set folders outside of the project folder
      // to be able to import modules from 'em
      watchFolders: [
        path.resolve(__dirname + "/../common")
      ]
    };
    

    Two days searching and the solution is, as always, just 4 rows

    The beautiful world of javascript bundlers.
    That's all folks!
    ¯\_(ツ)_/¯