Symlinking React libraries for local development

Ismayil Khayredinov
3 min readNov 28, 2020

--

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:

  • Using npm link to 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 create-react-app.

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 react-app-rewired and customize-cra, and update scripts definition inside your package.json.

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 dependencies in package.json and instead listpeerDependencies and include required packages in devDependencies. Using and lockingdependencies 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.

Enjoy

That’s it. You can now make changes in your library and watch them hot reload inside your application.

--

--

Ismayil Khayredinov

Full-stack developer, passionate about front-end frameworks, design systems and UX.