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

I can't use a symlinked package #1029

Open
CodeWhisperer opened this issue Jul 10, 2023 · 7 comments
Open

I can't use a symlinked package #1029

CodeWhisperer opened this issue Jul 10, 2023 · 7 comments

Comments

@CodeWhisperer
Copy link

Description

Hello everyone, after the new update of react native with version 0.72, and support for Symlink, I wanted to create a small project to test these features, but when importing a package created with lerna for a monorepo, I can't import it and use it correctly.

React Native Version

0.72.1

Output of npx react-native info

info Fetching system and libraries information...
System:
OS: Linux 5.19 Ubuntu 22.04.2 LTS 22.04.2 LTS (Jammy Jellyfish)
CPU: (12) x64 AMD Ryzen 5 5600X 6-Core Processor
Memory: 5.50 GB / 15.54 GB
Shell:
version: 5.1.16
path: /bin/bash
Binaries:
Node:
version: 18.16.1
path: ~/.nvm/versions/node/v18.16.1/bin/node
Yarn:
version: 1.22.19
path: /home/linuxbrew/.linuxbrew/bin/yarn
npm:
version: 9.5.1
path: ~/.nvm/versions/node/v18.16.1/bin/npm
Watchman:
version: 2022.06.20.00
path: /home/linuxbrew/.linuxbrew/bin/watchman
SDKs:
Android SDK: Not Found
IDEs:
Android Studio: Not Found
Languages:
Java:
version: 11.0.19
path: /usr/bin/javac
Ruby:
version: 3.0.2
path: /usr/bin/ruby
npmPackages:
"@react-native-community/cli": Not Found
react:
installed: 18.2.0
wanted: 18.2.0
react-native:
installed: 0.72.1
wanted: 0.72.1
npmGlobalPackages:
"react-native": Not Found
Android:
hermesEnabled: true
newArchEnabled: false
iOS:
hermesEnabled: Not found
newArchEnabled: Not found

Steps to reproduce

First I create an empty folder
mkdir monorepo && cd monorepo

I create a react native app
npx react-native@latest init AwesomeProject

I start lerna
lerna init

I create new package with lerna
lerna create package1

I modify packages/package1/package.js with this code:

import React from 'react';
import { Text, TouchableOpacity } from 'react-native';

export const CustomButton = ({ onPress, title }) => {
  return (
    <TouchableOpacity onPress={onPress}>
      <Text>{title}</Text>
    </TouchableOpacity>
  );
};

I run react native in RN project (still without adding the lerna package)

/monorepo/AwesomeProject$ npm start
/monorepo/AwesomeProject$ npm run android
  • The project runs correctly

I install my lerna package
/monorepo/AwesomeProject$ npm install ../packages/package1

I check that it has been installed in package.json

  "dependencies": {
    "package1": "file:../packages/package1",
    "react": "18.2.0",
    "react-native": "0.72.1"
  },

I go to App.tsx and import the package:
import {CustomButton} from 'package1';

And I use it:

  <View>
    <CustomButton
      title="Press me!"
      onPress={() => console.log('Hello!')}
    />
  </View>

Snack, code example, screenshot, or link to a repository

App.spx full code:

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 */

import React from 'react';
import type {PropsWithChildren} from 'react';
import {
  SafeAreaView,
  ScrollView,
  StatusBar,
  StyleSheet,
  Text,
  useColorScheme,
  View,
} from 'react-native';

import {
  Colors,
  DebugInstructions,
  Header,
  LearnMoreLinks,
  ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';
import {CustomButton} from 'package1';

type SectionProps = PropsWithChildren<{
  title: string;
}>;

function Section({children, title}: SectionProps): JSX.Element {
  const isDarkMode = useColorScheme() === 'dark';
  return (
    <View style={styles.sectionContainer}>
      <Text
        style={[
          styles.sectionTitle,
          {
            color: isDarkMode ? Colors.white : Colors.black,
          },
        ]}>
        {title}
      </Text>
      <Text
        style={[
          styles.sectionDescription,
          {
            color: isDarkMode ? Colors.light : Colors.dark,
          },
        ]}>
        {children}
      </Text>
    </View>
  );
}

function App(): JSX.Element {
  const isDarkMode = useColorScheme() === 'dark';

  const backgroundStyle = {
    backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
  };

  return (
    <SafeAreaView style={backgroundStyle}>
      <StatusBar
        barStyle={isDarkMode ? 'light-content' : 'dark-content'}
        backgroundColor={backgroundStyle.backgroundColor}
      />
      <ScrollView
        contentInsetAdjustmentBehavior="automatic"
        style={backgroundStyle}>
        <Header />
        <View
          style={{
            backgroundColor: isDarkMode ? Colors.black : Colors.white,
          }}>
          <Section title="Step One">
            Edit <Text style={styles.highlight}>App.tsx</Text> to change this
            screen and then come back to see your edits.
          </Section>
          <View>
            <CustomButton
              title="Press me!"
              onPress={() => console.log('Hello!')}
            />
          </View>
          <Section title="See Your Changes">
            <ReloadInstructions />
          </Section>
          <Section title="Debug">
            <DebugInstructions />
          </Section>
          <Section title="Learn More">
            Read the docs to discover what to do next:
          </Section>
          <LearnMoreLinks />
        </View>
      </ScrollView>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  sectionContainer: {
    marginTop: 32,
    paddingHorizontal: 24,
  },
  sectionTitle: {
    fontSize: 24,
    fontWeight: '600',
  },
  sectionDescription: {
    marginTop: 8,
    fontSize: 18,
    fontWeight: '400',
  },
  highlight: {
    fontWeight: '700',
  },
});

export default App;

Screen error:

Screenshot_1688972880

@cortinico cortinico transferred this issue from facebook/react-native Jul 10, 2023
@robhogan
Copy link
Contributor

robhogan commented Jul 12, 2023

Hi @CodeWhisperer - in a monorepo setup, Metro should be configured to watch your workspace root. You'll need to make sure that's added to watchFolders in your metro.config.js, for example:

watchFolders: [path.join(__dirname, '..', '..')]

You also need to have symlinks enabled in your Metro config:

resolver: { 
  unstable_enableSymlinks: true
}

If that doesn't work, can you share your metro.config.js?

@CodeWhisperer
Copy link
Author

Hello @robhogan , thank you very much for your answer, as you comment, I forgot to add the configuration in metro.config.js, but I have added what you have commented and I still receive the same error.

My metro.config.js

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

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

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

I leave you the folder structure to add more information.

monorepo/
├── AwesomeProject
│   ├── android
│   ├── app.json
│   ├── App.tsx
│   ├── babel.config.js
│   ├── Gemfile
│   ├── index.js
│   ├── ios
│   ├── jest.config.js
│   ├── metro.config.js
│   ├── node_modules
│   ├── package.json
│   ├── package-lock.json
│   ├── README.md
│   ├── __tests__
│   ├── tsconfig.json
│   └── yarn.lock
├── lerna.json
├── package.json
└── packages
    └── package1

If you need more information or any proof, let me know.

@robhogan
Copy link
Contributor

Hi @CodeWhisperer. Looks like the main issue there is that watchFolders needs to be a top-level config option, not under resolver.

Also, if your project structure is <monorepo root>/AwesomeProject/metro.config.js, you probably only want to go up one directory for your watchFolders - the idea is to watch your monorepo/workspace root.

Try this:

const config = {
  watchFolders: [path.resolve(__dirname, '..')],
  resolver: {
    unstable_enableSymlinks: true,
    unstable_enablePackageExports: true,
  },
};

@CodeWhisperer
Copy link
Author

Hi @robhogan , Now I have metro.config.js file like this:

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

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

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

And I'm getting this new error

error: Error: Unable to resolve module @babel/runtime/helpers/interopRequireDefault from /home/user/monorepo/packages/package1/lib/package1.js: @babel/runtime/helpers/interopRequireDefault could not be found within the project or in these directories:
  ../../node_modules

I have checked my version of babel and they are these in the package.json:

    "@babel/core": "^7.20.0",
    "@babel/preset-env": "^7.20.0",
    "@babel/runtime": "^7.22.6",

With that configuration, you can run it without problem? could you try it?

@takameyer
Copy link

takameyer commented Oct 12, 2023

Interesting, I'm hitting this issue as well.

@robhogan
I'll add more context. I'm hosting a react native example in my library's (realm/realm-js) monorepo. I have chosen not to include this example in my npm workspaces, as the amount of custom configuration required for that to work makes upgrading RN versions non-trivial.

In my example, I would like to use npm link to create a symlink to realm. I have applied the following configuration to make this work:

const config = {
  watchFolders: [path.resolve(__dirname, '../../../packages/realm')],
  resolver: {
    unstable_enableSymlinks: true,
  },
};

The symlinks are actually resolving correctly, however it appears with by adding this watchFolders configuration, the local node_modules are no longer being resolved:

Error: Unable to resolve module @babel/runtime/helpers/getPrototypeOf from /******/packages/realm/dist/bundle.react-native.js: @babel/runtime/helpers/getPrototypeOf could not be found within the project or in these directories:
  ../../../packages/realm/node_modules
  ../../../node_modules

It appears to be searching for @babel/runtime/helpers/getPrototypeOf in the configured watch folder and in the root of the monorepo. This babel package is in the current projects node_modules, so I'm unsure why it's not searching for it there.

I have tried setting the rootProject to __dirname and so, but this doesn't seem to work. I have also considered trying out yarn workspaces with no-hoist, but I believe this will not solve the issue, as I am essentially not hoisting the example by not including it as part of my npm workspace.

Any clue here? I can also open a new issue if that helps. I have also tried linking realm from a React Native project outside the mono-repo and ran into the same issue.

@takameyer
Copy link

takameyer commented Oct 16, 2023

I figured out why this is happening. Since npm workspaces hoists the dependencies of the packages I'm trying to link to the node_modules existing in the root of the mono-repo, metro is not able to determine the location of the deps for the linked package.
I can, however, add the node_modules at the root of the packages to the watchfolders, but there is a mismatch between react versions, so this causes more errors.

@mjvestal
Copy link

mjvestal commented Mar 21, 2024

I ran into issues with symlinks in a monorepo when upgrading from 0.71.* to 0.72.*. This was working previously with rnx-kit and I was unsure what my new metro config should be. I got it working again by keeping my nodeModulesPaths. Without it, metro couldn't find react when resolving a monorepo dependency that also depended on react.

// Find the project and workspace directories
const projectRoot = __dirname
const workspaceRoot = path.resolve(projectRoot, '../..')

const config = {
  resolver: {
    // For monorepo
    unstable_enableSymlinks: true,
    // For monorepo: let Metro know where to resolve packages and in what order
    nodeModulesPaths: [
      path.resolve(projectRoot, 'node_modules'),
      path.resolve(workspaceRoot, 'node_modules'),
    ],
  },
  // For monorepo: Watch all files within the monorepo
  watchFolders: [workspaceRoot],
}

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

No branches or pull requests

4 participants