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

Unable to resolve module when using symlinks. #1204

Open
samsnori opened this issue Feb 1, 2024 · 6 comments
Open

Unable to resolve module when using symlinks. #1204

samsnori opened this issue Feb 1, 2024 · 6 comments

Comments

@samsnori
Copy link

samsnori commented Feb 1, 2024

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

I've got a question or otherwise a bug report.

What is the current behavior?

I'm trying to import files from a shared library. I've got a git repository in which I store sources that I like to use throughout several applications. This is just a collection of Javascript files. This is not a package (e.g. doesn't have a package.json). This library is stored at <home>/shared-lib. I'm now building a React Native app in <home>/main-app/app and in this main app, I would like to use a file, e.g. <home>/shared-lib/src/components/Button.jsx.

After doing some research it seems that you should be able to get this to work via metro.config.js as mentioned:

I've created the following files and directory hierarchy. Note that there are several symlinks as I've been experimenting to get this to work. For example notice the app/shared-lib, app/shared-lib-nosymlink, but also main-app/shared-lib and main-app/shared-lib-no-symlink. Note the different values for dir_name in the metro.config.js below, which I've used to test different things in the metro.config.js below. Interestingly, when I use the main-app/app/shared-lib-no-symlink version everything works fine. But when I try to use a component from any of the symlink directories, I run into an error like: Unable to resolve module shared-lib/src/components/Button.

<home>
├── main-app
│   ├── app
│   │   ├── android
│   │   ├── ios
│   │   ├── node_modules
│   │   ├── shared-lib -> /home/shared-lib/
│   │   ├── shared-lib-no-symlink
│   │   └── __tests__
│   ├── shared-lib -> /home/shared-lib/
│   └── shared-lib-no-symlink
│       └── src
└── shared-lib
    └── src
        └── components  

The contents of the shared-lib-* directories only contains:

.
└── src
    └── components
        └── Button.jsx

I've updated my metro.config.js, from the <home>/main-app/app/ directory in such a way that it should work with symlinks. E.g. <home>/main-app/app/mettro.config.js looks like:


/* file: <home>/main-app/app/metro.config.js */

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

/* ------------------------------------------------------- */

let dir_name = null;

/* Testing different approaches. */
dir_name = '/../shared-lib-no-symlink'; /* No symlink, outside project (FAILS) */
dir_name = '/../shared-lib';            /* Symlink, outside project */
dir_name = '/shared-lib-no-symlink';    /* No symlink, inside project (WORKS) */
dir_name = '/shared-lib';               /* Symlink, inside project (FAILS) */

let my_shared_lib_path = path.resolve(__dirname +dir_name);

/* ------------------------------------------------------- */

const config = {
  resolver: {
    extraNodeModules: {
      'shared-lib': my_shared_lib_path,
    },
    unstable_enableSymlinks: true /* Should not be necessary as it defaults to true: https://metrobundler.dev/docs/configuration/#unstable_enablesymlinks-experimental */
  },
  watchFolders: [
    my_shared_lib_path,
  ],
  resetCache: true,  /* https://metrobundler.dev/docs/configuration/#resetcache */
};

/* ------------------------------------------------------- */

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

/* ------------------------------------------------------- */

My goal is to include a source file into my project that is stored in another git repository outside the current React Native project. From my understanding, this should be possible using the metro.config.js as posted above. Obviously this doesn't work. What am I doing wrong?

If the current behavior is a bug, please provide the steps to reproduce and a minimal repository on GitHub that we can yarn install and yarn test.

I'm not sure if this is a bug or if I've misunderstood how to do this correctly.

What is the expected behavior?

I would love to be able to use source files that are located somewhere else into my project. These source files do not belong to a package per se; just a repository with sources.

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

$ npx react-native info
info Fetching system and libraries information...
System:
  OS: Linux 6.6 Arch Linux
  CPU: (24) x64 Intel(R) Core(TM) i9-10920X CPU @ 3.50GHz
  Memory: 38.13 GB / 62.49 GB
  Shell:
    version: 5.2.26
    path: /bin/bash
Binaries:
  Node:
    version: 18.12.1
    path: ~/.nvm/versions/node/v18.12.1/bin/node
  Yarn:
    version: 1.22.19
    path: ~/.nvm/versions/node/v18.12.1/bin/yarn
  npm:
    version: 8.19.2
    path: ~/.nvm/versions/node/v18.12.1/bin/npm
  Watchman: Not Found
SDKs:
  Android SDK: Not Found
IDEs:
  Android Studio: AI-231.9392.1.2311.11330709
Languages:
  Java:
    version: 17.0.10
    path: /usr/bin/javac
  Ruby:
    version: 3.0.6
    path: /usr/bin/ruby
npmPackages:
  "@react-native-community/cli": Not Found
  react:
    installed: 18.2.0
    wanted: 18.2.0
  react-native:
    installed: 0.73.3
    wanted: 0.73.3
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: false
iOS:
  hermesEnabled: Not found
  newArchEnabled: false
  
@samsnori
Copy link
Author

samsnori commented Feb 2, 2024

Workaround

For now, I'm creating a mount point which solves this issue.

Great thing about this solution is that you don't have to change any of the configs either.

cd <home>/main-app
cd app
mkdir shared-lib-mounted

sudo mount --bind ../../shared-lib shared-lib-mounted

I'm very curious why symlinks aren't working though. I'm sure it turns out to be something silly

@robhogan
Copy link
Contributor

robhogan commented Feb 2, 2024

Hi @samsnori - thanks for the report and particularly for all the detail here.

Could you clarify what import you're using in your JS/TS source, or what you'd like that to look like?

I'm also curious about the use of extraNodeModules - normally the values there would be expected to be directories containing package.json files (ie, valid packages). It sounds like you might be wanting to use something like import {Button} from 'shared-package', or are you just using relative paths and it's not working when the relative path includes a symlink?

@samsnori
Copy link
Author

samsnori commented Feb 2, 2024

Hi @robhogan thanks for you reply. In my shared-lib directory, I've got a couple of JS files that I like to use across projects. These do not belong to a package so there is no index.js or package.json. E.g. for this test I created a file shared-lib/src/components/Button.jsx which contains:

/* ------------------------------------------------------- */

import { View, Text } from 'react-native';

/* ------------------------------------------------------- */

function Button(props) {
  return (
    <View>
      <Text>This is my fake button</Text>
    </View>
  );
}

/* ------------------------------------------------------- */

export {
  Button
};

Then I would like to import this anywhere in my main-app like:

import { Button } from 'shared-lib/components/Button'

This works when I use a plain directory (or mount point) but not when using a symlink. This is why I suspect that the symlink is causing this.

Tbh, the extraNodeModules is something that I copied from one of the many articles I found about this. I haven't tested if things will work when I remove this, I wouldn't think so, but I'll do a test.

@robhogan
Copy link
Contributor

robhogan commented Feb 2, 2024

Interesting, thanks - tbh I’m surprised that works at all without a package.json, but the inconsistency is surprising in any case. An import that isn’t a relative or absolute file path should only resolve under those settings if it begins with a package name.

I don’t think we’ve done a lot of testing on the interaction between extraNodeModules and symlinks, what you’re seeing does seem like a bug one way or another. I’ll see if I can reproduce.

@vitorsilvalima
Copy link

Even more weird,

I have the following dependencies in my shared-lib and it does not work at all giving the following error:

Error: Unable to resolve module @babel/runtime/helpers/asyncToGenerator from /Users/vitorsilvalima/projects/diggy/packages/core/node_modules/zod/lib/helpers/parseUtil.js:

"dependencies": {
    "dayjs": "1.10.8",
    "mongoose": "^6.7.5",
    "zod": "^3.22.4"
  }

However, it works if I remove zod or if I install babel-runtime in my shared-lib which makes no sense at all.

@samsnori
Copy link
Author

Yes, I've experienced that too. The state of RN tooling is extremely sad.

To be honest I think the issue is not entirely the fault of Metro, it's the whole "npm" / package / tooling space. The biggest flaw I see which causes -a lot- of these issues, is that each bundler or packager has it's own module resolution solutions. NPM should have provided a solution that could be used by bundlers instead.

It's also good to realize that Metro is most likely not going to spend time on this; the very first issue that was posted already mentioned similar issues. I understand the main developers have other priorities.

For me, I found a solution which "works" but is ugly.

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

3 participants