pnpm: [bug] - Pnpm not working with expo

pnpm version:

6.28.0

Code to reproduce the issue:

expo init pnpm_expo # Choose Typescript tab template
cd pnpm_expo
rm -rf node_modules yarn.lock
pnpm i
pnpm start
# Go to the expo app, and try it

Expected behavior:

Be able to run an expo app without any issue.

Actual behavior:

Error: EISDIR: illegal operation on a directory, read
    at Object.readSync (node:fs:723:3)
    at tryReadSync (node:fs:433:20)
    at Object.readFileSync (node:fs:479:19)
    at UnableToResolveError.buildCodeFrameMessage (/home/olyno/Documents/projects/pnpm_expo/node_modules/.pnpm/metro@0.64.0/node_modules/metro/src/node-haste/DependencyGraph/ModuleResolution.js:347:17)
    at new UnableToResolveError (/home/olyno/Documents/projects/pnpm_expo/node_modules/.pnpm/metro@0.64.0/node_modules/metro/src/node-haste/DependencyGraph/ModuleResolution.js:333:35)
    at ModuleResolver.resolveDependency (/home/olyno/Documents/projects/pnpm_expo/node_modules/.pnpm/metro@0.64.0/node_modules/metro/src/node-haste/DependencyGraph/ModuleResolution.js:211:15)
    at DependencyGraph.resolveDependency (/home/olyno/Documents/projects/pnpm_expo/node_modules/.pnpm/metro@0.64.0/node_modules/metro/src/node-haste/DependencyGraph.js:413:43)
    at /home/olyno/Documents/projects/pnpm_expo/node_modules/.pnpm/metro@0.64.0/node_modules/metro/src/lib/transformHelpers.js:317:42
    at /home/olyno/Documents/projects/pnpm_expo/node_modules/.pnpm/metro@0.64.0/node_modules/metro/src/Server.js:1471:14
    at Generator.next (<anonymous>)
› Stopped server

Additional information:

  • node -v prints: 16.13.2
  • Windows, macOS, or Linux?: Linux, Deepin

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 4
  • Comments: 15 (2 by maintainers)

Most upvoted comments

@byCedric now that Expo SDK 50 uses React Native 0.73 (which made the Metro symlinks support stable, which has been reported as working by the community without Expo), would it be considered a bug in Expo for pnpm support to not work out of the box (without node-linker=hoisted or any other workarounds) with a new create-expo app?

The way I understand it, the work involved on Expo’s side for supporting symlinks would be refactoring the following:

  • relative paths from files within node_modules
  • any other code that relies on a fixed node_modules structure

Would be great to be able to use Expo + the new Metro symlink support without the node-linker=hoisted workaround!

@zkochan alternatively, if Expo will not do the work above to support symlinks, would pnpm consider adopting into core some public-hoist-pattern[] hoisting exceptions config for Expo and related packages which rely on a fixed node_modules directory structure?

Some users have reported public-hoist-pattern[] configurations working for them:

eg. the following (not tested as working yet)

public-hoist-pattern[]=*expo*
public-hoist-pattern[]=*react-native-gradle-plugin*
public-hoist-pattern[]=*@react-native-community/cli-platform-android*
public-hoist-pattern[]=*@react-native-community/cli-platform-ios*

I know that pnpm already has some public-hoist-pattern[] configuration in core for some other popular packages in the ecosystem which also rely on node_modules directory structure - so I’m asking whether pnpm would also consider this for Expo, if they will not do the work to refactor.

That’s not an issue related to pnpm, or Expo. It’s just how Metro explains it can’t find packages. I got it working with the --shamefully-hoist flag, and going over the steps in Working with Monorepos, in case it helps. https://github.com/byCedric/eas-monorepo-example/pull/19

Thanks. I actually looked into that just now and these are the specific changes necessary for pnpm: https://github.com/rhyek/expo-monorepo-issue/compare/pnpm...pnpm-fixed

It works this way, but isn’t it contrary to the principle of operation of pnpm? The purpose of pnpm is to use symlinks to reduce the size of node_modules, isn’t it?

@wesleyfreit @watadarkstar these two configurations are not following my suggested public-hoist-pattern[]= config above:

  1. Your config above includes Babel, ESLint and Prettier dependencies, which are unrelated to React Native / Expo hoisting problems
  2. Your config above uses node-linker=hoisted, which is a workaround for the issue and avoids the problem which should actually be fixed by Expo / React Native / pnpm (description on option: “A React Native project will most probably only work if you use a hoisted node_modules.”)

The point of issues like this are to try to move past the historical limitation of React Native to not work with symlinks and pnpm’s default isolated linker, now that Expo SDK 50 uses React Native 0.73, which made the Metro symlinks support stable:

Yes, @karlhorky is correctly, my project is working good using public-hoist-pattern[] pnpm configuration in .npmrc and other configurations in metro.config.js.

I’m using a default expo project created inside the monorepo using turborepo with pnpm.

My .npmrc:

public-hoist-pattern[] = @babel/*
public-hoist-pattern[] = *eslint* // if you use eslint
public-hoist-pattern[] = *prettier* // if you use prettier

In my expo project, I have the @rnx-kit/metro-resolver-symlinks package installed.

And my metro.config.js:

const { getDefaultConfig } = require('expo/metro-config');
const MetroSymlinksResolver = require('@rnx-kit/metro-resolver-symlinks');
const path = require('path');

// Find the workspace root
const workspaceRoot = path.resolve(__dirname, '../..');
const projectRoot = __dirname;

const config = getDefaultConfig(projectRoot);

// Watch all files within the monorepo
config.watchFolders = [workspaceRoot];

// Let Metro know where to resolve packages, and in what order
config.resolver.nodeModulesPaths = [
  path.resolve(projectRoot, 'node_modules'),
  path.resolve(workspaceRoot, 'node_modules'),
];

// Force Metro to resolve (sub)dependencies only from the `nodeModulesPaths`
config.resolver.disableHierarchicalLookup = true;

// Using the Metro Symlinks Resolver config of the `rnx-kit`
config.resolver.resolveRequest = MetroSymlinksResolver();

module.exports = config;

In my project, the typescript path mapping configuration (ex: @/), did not work with the described configuration, so I had to remove it. Maybe I should add another public pattern for typescript, but I preferred not to add it.

Right. But we can’t fix issues in third party libs. The global store is still used. Even with hoisted linker

Try with the node-linker=hoisted option https://pnpm.io/npmrc#node-linker