Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

metro-file-map: error "Unable to resolve module" while attempting to import a file from outside the project directory #1047

Open
retyui opened this issue Jul 26, 2023 · 10 comments

Comments

@retyui
Copy link
Contributor

retyui commented Jul 26, 2023

Do you want to request a feature or report a bug?

bug

What is the current behavior?

My files structure

├── app-info-package-2/
│   ├── package.json
│   └── src/
│       └── index.ts
└── SampleApp/
    ├── android/
    ├── ios/
    ├── node_modules/
    ├── app.json
    ├── App.tsx
    ├── babel.config.js
    ├── index.js
    ├── metro.config.js
    ├── package.json
    ├── tsconfig.json
    └── yarn.lock

And when I try to import files outside SampleApp metro build is fail

import * as AppInfo2 from '../app-info-package-2/src/index';

with an error

error: Error: Unable to resolve module ../app-info-package-2/src/index from /home/i/tmp/demo-metro-bug/SampleApp/App.tsx: 

None of these files exist:
  * ../app-info-package-2/src/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)
  * ../app-info-package-2/src/index/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)

BUT THIS file exist :

123


Demo Repo: https://github.com/retyui/demo-metro-bug

What is the expected behavior?

the exists method in demo-metro-bug/SampleApp/node_modules/metro-file-map/src/lib/TreeFS.js file

shouldn't return null for files outside directory

Please provide your exact Metro configuration and mention your Metro, node, yarn/npm version and operating system.

  • Versions:

      Node: 20.4.0 - /usr/local/bin/node
      Yarn: 1.22.19 - ~/.yarn/bin/yarn
      npm: 9.7.2 - /usr/local/bin/npm
    ...
    ├─ metro-file-map@0.76.7
    ├─ metro@0.76.7
    ├─ react-native@0.72.3
    └─ react@18.2.0
  • metro.config.js

@robhogan
Copy link
Contributor

robhogan commented Jul 26, 2023

Hi @retyui - sorry, we should document this better, and the error should be clearer (working on it!).

metro-file-map is an in-memory "map", it's only aware of files Metro is configured to watch. By default, that's everything under your projectRoot (your RN app directory) - if you need to resolve files outside that, such as up one or two levels, you'll need to configure the top-level Metro config option watchFolders to include those directories. watchFolders: [path.resolve(__dirname, '..')] should work in your case. If you have a workspace-type setup with symlinks in node_modules, you'll also want the new resolver.unstable_enableSymlinks option:

// in metro.config.js
const config = {
  watchFolders: [require('node:path').resolve(__dirname, '..')],
  resolver: {
    unstable_enableSymlinks: true,
    // ...
  },
  // ...
};

Let us know if that doesn't help.

@retyui
Copy link
Contributor Author

retyui commented Jul 27, 2023

@robhogan Now It fail with other error:

error: Error: Unable to resolve module @babel/runtime/helpers/interopRequireDefault 
  from /home/i/all_work/ck/bridging-tutorial/app-info-package/src/index.ts: 
    @babel/runtime/helpers/interopRequireDefault could not be found within the project.

@robhogan
Copy link
Contributor

@robhogan Now It fail with other error:

error: Error: Unable to resolve module @babel/runtime/helpers/interopRequireDefault 

  from /home/i/all_work/ck/bridging-tutorial/app-info-package/src/index.ts: 

    @babel/runtime/helpers/interopRequireDefault could not be found within the project.

We've had a couple of reports of this error. Is there still nothing else in your metro.config.js?

@huntie - do you have any context here? I recall looking into resolution failures for this package but I don't know whether there were any behaviour changes without unstable_enablePackageExports?

@retyui
Copy link
Contributor Author

retyui commented Jul 27, 2023

@robhogan my metro.config.js

const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');

/**
 * Metro configuration
 * https://facebook.github.io/metro/docs/configuration
 *
 * @type {import('metro-config').MetroConfig}
 */
const config = {
 watchFolders: [require('node:path').resolve(__dirname, '..')],
  resolver: {
    unstable_enableSymlinks: true,
  },
};

module.exports = mergeConfig(getDefaultConfig(__dirname), config);

you can use https://github.com/retyui/demo-metro-bug to investigate this issue

@robhogan
Copy link
Contributor

Ah, this is due to @babel/plugin-transform-runtime. By default, transformed code is injected with dependencies on @babel/runtime/helpers/... to reduce duplication, which is why RN projects need to specify a dependency on @babel/runtime.

The problem is that that dependency isn't sufficient when transformed code is outside the project root. Metro can't resolve @babel/runtime from the transformed version of app-info-package-2/src/index.js because @babel/runtime isn't installed or listed as a dependency of app-info-package-2.

As a workaround, you can add @babel/runtime as a (dev) dependency of that project, using the same version as your RN app.

We've already been discussing better approaches to Babel helpers but I hadn't made the connection with workspaces and code sharing, so we might bring that plan forward. Thanks for bringing this up.

byCedric added a commit to expo/expo that referenced this issue Sep 22, 2023
#24561)

# Why

This is the last remaining issue to build the Android app.

# How

- Used full dependency chains when resolving packages in Android native
files.

# Test Plan

- `$ pnpm create expo -t tabs@50`
- `$ pnpm expo prebuild`
- Update all files from this PR
- Enable symlinks in **metro.config.js**
  > `config.resolver.unstable_enableSymlinks = true;`
- Manually work around pending Metro issues
- Add `@babel/runtime` as project dependency: `$ pnpm add --save-dev
@babel/runtime`
([issue](facebook/metro#1047 (comment)))
  - Add **metro.config.js** settings:
    ```js
    config.transformer.asyncRequireModulePath = require.resolve(
      'metro-runtime/src/modules/asyncRequire',
      { paths: [require.resolve('react-native/package.json')] },
    );
    ```
- `$ pnpm expo run:android` should work

> [See this example project](https://github.com/byCedric/expo-50-pnpm)

# Checklist

<!--
Please check the appropriate items below if they apply to your diff.
This is required for changes to Expo modules.
-->

- [x] Documentation is up to date to reflect these changes (eg:
https://docs.expo.dev and README.md).
- [ ] Conforms with the [Documentation Writing Style
Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md)
- [ ] This diff will work correctly for `npx expo prebuild` & EAS Build
(eg: updated a module plugin).
@1mike12
Copy link
Contributor

1mike12 commented Nov 3, 2023

@robhogan with regard to this issue with the bable plugin transform, does that mean that a lot of the solutions for doing outside imports discussed in #7 are moot? I've tried everything suggested there to get imports working but all I get is errors talking about the same plugin. I tried the suggestion to add babel runtime as a dependency and it wasn't working

on RN: 0.71.7

@1mike12
Copy link
Contributor

1mike12 commented Nov 13, 2023

I was fiddling around with importing outside of the root and what I found out that wasn't clear to me before is that if we just want to have a structure like

ROOT
--react_native
--shared

We don't actually need symlinks per se, and only need to add the shared folder to the watchFolders. But the only caveat here is that everything works except importing a class. IE, if you import functions, constants, etc, that should be fine. But exporting/ importing a class from the shared folder will result in the error NVM this is working fine

but I tried @robhogan suggestion and it works.

here's an example barebones 0.72 repo that shows how this works
https://github.com/1mike12/rn_monorepo_test

@witekmikolajczak
Copy link

@robhogan my metro.config.js

const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');

/**
 * Metro configuration
 * https://facebook.github.io/metro/docs/configuration
 *
 * @type {import('metro-config').MetroConfig}
 */
const config = {
 watchFolders: [require('node:path').resolve(__dirname, '..')],
  resolver: {
    unstable_enableSymlinks: true,
  },
};

module.exports = mergeConfig(getDefaultConfig(__dirname), config);

you can use https://github.com/retyui/demo-metro-bug to investigate this issue

For me, I needed to run:
npx react-native start --reset-cache

@meer4283
Copy link

npx react-native start --reset-cache

Solved this by just doing this.

@shovel-kun
Copy link

Or the equivalent if you're using expo:

npx expo start --clear

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants