75

I am developing a button ui package for react native. I try to build an example project to test this button. The directory structure is as follows:

my-button/
    package.json
    index.js
    example/
        package.json
        index.js

I try to use npm link:

cd my-button
npm link

cd example
npm link my-button

In example/node_modules/ I can see my-button symlink, VSCode also can auto complete function in my-button package.

But execute example app will show error:

Unable to resolve module my-button ...
Module does not exist in the module map or in these directories: ...

But the path in the error message is correct.

Don't know where I was wrong, or in React-Native have any special way to deal with link local dependency?

I also tried npm install file:../.. It works fine in this way, but not easy to update dependency in example/ after I edited my-button.

Rick Liao
  • 968
  • 1
  • 7
  • 11

13 Answers13

75

The npm link command doesn't work because React Native packager doesn't support symlinks.

After a little research, I discovered that there are two ways to go about it.

  1. Use haul packager in the example app. Haul supports symlinks, so you can use npm link as usual.
  2. Use local dependency via file:../ and then edit files in node_modules folder or reinstall every time you make changes.

I found Haul to work great for this use-case and even set-up a little starter project that also includes storybook, which is really helpful if you have many components to switch between.

pavloko
  • 970
  • 9
  • 8
55

Try wml (https://github.com/wix/wml)

It's an alternative to npm link that actually copies changed files from source to destination folders

# add the link to wml using `wml add <src> <dest>`
wml add ~/my-package ~/main-project/node_modules/my-package

# start watching all links added
wml start
Asaf Katz
  • 4,608
  • 4
  • 37
  • 42
  • 3
    Be sure to shutdown your existing Metro Bundler packager instance and let React Native startup a new instance upon your next `react-native run-*****` command. Otherwise, you'll keep getting the error that the OP described. – Dave Koo Jan 23 '19 at 03:10
  • 6
    Is wml still maintained? The repository does not have any contributions since 2017. – tjeisenschenk Aug 25 '20 at 08:38
  • In case anyone is using this solution, and have problems with `node_modules` being linked too, before starting with `wml start` make sure to remove "node_modules" from the ignore list in `.watchmanconfig` file that is created by `wml` – pmiranda Oct 29 '20 at 17:44
  • 2
    @pmiranda were you able to make this work? I think the answer is outdated now. – Alwaysblue Feb 03 '21 at 10:53
  • WML worked for me in 2022, However had initial issue with it not doing anything which I fixed with [this issue](https://github.com/wix/wml/issues/48) – squareborg Jun 29 '22 at 15:51
19

I couldn't always make it work with yarn link. What i found extra useful is yalc:

First install it globally once forever:

npm install -g yalc

In the local library/package (i'll call it my-local-package), and run:

yalc publish

Then in your project which uses my-local-package as a dependency, run: (if you already have added it with any other way, first uninstall it (npm uninstall -S my-lockal-package)

yalc add my-local-package
npm install

If my-local-package is a native module, then run react-native run-android to link the dependency. (or run-ios)

If you make any change in the my-lockal-package, then:

cd path/of/my-local-package
yalc push //updates the local package
cd path/to/my-project
npm install
react-native run-android (or run-ios)

In case the update hasn't been applied, try to cd android && ./gradlew clean && cd .. and then rerun: react-native run-android.

angelos_lex
  • 1,593
  • 16
  • 24
9

I'm having the same issue while developing a native module wrapper around an existing native SDK. At first I followed @aayush-shrestha's suggestion to install the package locally. Like this:

npm install ../<package-folder> --save

This works as long as I reference the module via NativeModules. Import it:

import { NativeModules } from 'react-native';

And then access a module called ActualModuleName like this:

NativeModules.ActualModuleName

But it fails when I attempt to import the module by name:

import { ActualModuleName } from 'react-native-actualmodulename'

To make that work I had to first pack the package. Run this in the package's root directory:

npm pack

This generates a gzipped tarball:

react-native-actualmodulename-1.0.0.tgz

Now install that in your app:

npm install <path/to>/react-native-actualmodulename-1.0.0.tgz

An enormous downside to this is that you have to re-pack the package every time you make a change to the module. The only workaround I know of is to modify the package's files in node_modules directly and then copy those changes back to your repo when you're done.

But the upside is that your app's source can import ActualModuleName the same way you'll import it once it's released via npm; no environment-specific code necessary.

Wayne
  • 59,728
  • 15
  • 131
  • 126
  • It fail to install pod dependencies, when npm install .tgz file. – girish_pro Feb 12 '20 at 07:30
  • @girish_pro pod dependencies still need to be installed separately; run `pod install` in the `ios` dir before packing the extension – Wayne Feb 12 '20 at 19:25
  • @wayne_burkett `pod install` not installing sub dependencies. My .tgz file depend on `react-native-branch` which need both node_module and Pod. But when I directly add .tgz file and `pod install` in ios folder, then it not installing `react-native-branch` pod, only node_module. – girish_pro Feb 13 '20 at 07:18
7

You can use npm link using Metro. Just add the source folder of the linked package to watchFolders in your metro.config.js.

Marces Engel
  • 685
  • 6
  • 6
  • 1
    this is the best answer. when you install a local package via npm install 'path/to/local/package', or in your package.json with 'file://path/to/local/package', npm links to the local package without copying. metro bundler does not know how to import these packages properly. you can update metro bundler config to do this. I cannot put much detail in comments but it's a 3 line addition to metro.config.js and you can read more details in this excellent write up (not by me): https://medium.com/@alielmajdaoui/linking-local-packages-in-react-native-the-right-way-2ac6587dcfa2 – Alex Choi Sep 08 '22 at 16:39
3

Change your package.json

//...
"dependencies": {
   //...
    "my-button" : "file:../"
  },
//...
KimHau
  • 330
  • 4
  • 14
  • 3
    I tried this method already, this way will copy entire project to `example/node_modules`, not just a symlink. It's not very convenient to update dependency. – Rick Liao May 19 '17 at 07:18
  • 2
    This solution would be perfect for us but we still get the same `module not found` error. Any idea? – Egidio Caprino Feb 19 '19 at 09:02
3

Ran into the same problem. While I could not make npm link work as it should, I worked around it by installing the local package in the project folder

npm install ../<package-folder> --save

This will install the package like a regular package but from the local folder. The downside is that the changes you make on the package will not be reflected. You will have to npm install after every change.

aayush shrestha
  • 1,858
  • 2
  • 17
  • 33
  • 26
    It doesn't work. I see the package included in `node_modules` but when I run the app, it gives error `Module 'xyz' does not exist in the Haste module map` – Sarneet Kaur Jun 07 '18 at 21:56
  • 2
    It might be that this command created a symlink under `node_modules` and for example React Native does not work with them. Instead, try this: ```npm install $(npm pack | tail -1)```. Source: https://stackoverflow.com/a/54588310 – iqqmuT Aug 06 '19 at 23:21
2

I also came across this problem. After visiting the below link, I came to know that react- native does not support symlinks.[Click here][1]

However, I have solved this by adding these lines in the metro.config.js file. Please replace your_module_name with your module name.

const path = require('path');

const thirdPartyPath = path.resolve(__dirname + '/../your_module_name/');  // Path of your local module

const thirdParty= {
  'your_module_name': thirdPartyPath,
  };

const watchFolders = [ thirdPartyPath];

module.exports = {
// existing dependencies
resolver: {
  thirdParty,
  },
  watchFolders
};
Ameer Asif
  • 41
  • 3
0

I ran into the same problem.

I tried to install a local module using npm, and kept running into the issue of not being able to resolve the module, even though I could see the folder in node_modules and autocomplete of class and method names worked.

I was able to bypass it by installing the local library using yarn instead of npm after seeing this open issue on github. Issue was opened September 2020 and no comment from Facebook as of yet.

Marline
  • 269
  • 3
  • 10
0

This work for me:

step 1 go to package:

npm link packageNameHere

This will link this package to global node_module

step 2 go to directory which you want to use this package and run these

npm link pathToPackageDirectory
npm install pathToPackageDirectory

ex: npm link ~/myDemoPackage

This will link global node_moudle to this project

If you want to import package to file, USE FILE PATH INSTEAD OF PACKAGE NAME !

ex:

my package name is stripe-api-helper. my code are in src/index.ts then I need to resolve like this:

import { postStripe, Item } from '@aliciaForDemo/stripe-api-helper/src'

if u use '@aliciaForDemo/stripe-api-helper' it will fail.

JHIH-LEI
  • 104
  • 6
0

Could never get my own environment working using any other suggestions, but found a hack that works well (though not ideal) that can be easily set up in just a few lines of code and without changing your RN project configuration.

Use fs.watch for changes recursively in the directory where you're working on your library, and copy the updates over whenever there's been a change:

import fs from 'fs'

const srcDir = `./your-library-directory`
const destDir = `../your-destination-directory`

fs.watch("./src/", {recursive: true}, () => {
  console.log('copying...')
  fs.cp(srcDir, destDir, { overwrite: true, recursive: true }, function() {
    console.log('copied')
  })
})
Nader Dabit
  • 52,483
  • 13
  • 107
  • 91
-3

For those still looking for a simple solution without other dependency, try this:

yarn --version
1.21.1

npm --version
6.13.4
  1. Install in project root
cd my-button
yarn install or npm install
  1. register linking in my-button
yarn link or npm link
  1. Install example project
cd example
yarn add ../ or npm add ../
  1. link to my-button
yarn link my-button or npm link my-button
  1. complete pod installation (if necessary)
cd ios
pod install
Mazin Luriahk
  • 828
  • 8
  • 9
-6

Try to run

npm run watch

inside the button package. Currently, I'm using this to apply changes from the library to my main project. Please let me know if it works!

Bala Krishna
  • 154
  • 2
  • 10