Symlinking React libraries for local development
If you are maintaining multiple React libraries that cross-depend on each other, things tend to get cumbersome: you need to publish a new release of each library then pull the latest release everywhere it’s used. If you are actively developing features, it becomes frustrating and creates a lot of noise in your commit and release logs.
There are several ways to deal with the problem:
npm linkto reference a local instance of the dependency inside the application you are working on
- Using Lerna to maintain and resolve these references
Either way, you may encounter difficulties, if you have created your React projects using
Here is the workaround I have come up with based on several helpful resources out there.
Customize CRA (create-react-app)
First of all, we want to have some control over webpack/babel behavior in our React app. Ejecting the app is not the most desirable approach, hence I suggest installing
customize-cra, and update
scripts definition inside your
npm install --save-dev react-app-rewired customize-cra
Now let’s add
config-overrides.js inside the root of our project to customize CRA behavior.
Modify CRA scope
By default, CRA prevents imports from outside of the project root. There are various security and usability implications here, so be considerate about these changes.
When we use symlinks to link our dependencies, our assets (i.e. fonts, images, etc) fall outside of the project scope, and webpack fails to compile them.
Modify Babel includes
By default, Babel does not transpile your dependencies, i.e. anything inside node_modules, including symlinked packages, do not get Babel treatment.
We need to explicitly inform Babel that it needs to transpile our local dependency. Because the modules are symlinked, we want to use real paths, and not the paths inside our node_modules.
Hoist shared dependencies
If you are using Lerna, you can try and use
lerna bootstrap --hoist flag, but it may not be ideal for packages that were built at different times and rely on different versions of peer dependencies.
My preferred approach when building dependency libraries is to avoid using
package.json and instead list
peerDependencies and include required packages in
devDependencies. Using and locking
dependencies can lead to problems when you use the library in many different apps, as they may be running on different versions of shared dependencies. Using
peerDependencies however does not lock you into any specific version of the dependency, and will allow you and npm resolve the dependency tree to something more fitting for the specific app without having to experiment with version number and what effect they may have on your build at large.
One of the main problems you encounter when symlinking React applications is that they use different instances of React. Both the library and the app end up using their own version of React and all other packages. One workaround is to define webpack aliases, which ultimately tells webpack that when your dependency import react it should import it from the node_modules of the current app. Since react is not the only dependency that can spin up multiple instances, you could define aliases for all peerDependencies.
That’s it. You can now make changes in your library and watch them hot reload inside your application.