Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to tell lerna/npm to 'really look in my own node_modules for those peer dependencies'?

I have a fairly standard lerna monorepo that will look like this:

packages/
    main/  - This is the main deployable application, it depends on both dep and react-dep
    dep/   - Just some pure functions, no problems here
    react-dep/ - A design system built with react, this is where we have problems. 

So a really common problem that happens, is as soon as you start using hooks in your dependency library, you get this message:

Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/warnings/invalid-hook-call-warning.html for tips about how to debug and fix this problem.

Which is because there are two version of React in your app, one from the main application, and one from the dependency.

Now - a common solution, that I've used, and works, is to declare react and any other shared/peer dependencies as externals in your webpack configuration. Eg, as suggested here. or look at this Github issues thread from react.

However, I'm not a fan of this solution, firstly, what if I'm not using webpack, and secondly I shouldn't have to manually keep track of which dependencies I need to mark as external.

I think what should work is:

In react-dep I declare react in both devDependencies and peerDependencies. The reason I put it in devDependencies is because my dependency library is likely using storybook or similar to develop the components, so I do need react around in development.

I think that this should work if I'm publishing react-dep to npm and consuming the compiled code from npm in main, because only dependencies are going to be fetched.

However, I think due to the lerna symlinking, what happens in this case is that the dev dependency is still around and we get this error.

Is there a way to solve this issue for a lerna monorepo?

Here's github repo that demonstrates this issue: https://github.com/dwjohnston/lerna-react-monorepo

like image 950
dwjohnston Avatar asked May 28 '20 01:05

dwjohnston


People also ask

What is NPM Peer dependency?

Peer Dependencies are used to specify that our package is compatible with a specific version of an npm package. Good examples are Angular and React. To add a Peer Dependency you actually need to manually modify your package.json file.

How do you resolve peer dependencies?

Solution 1: Ignore the peerDependencies The easiest way to fix the issue is to pass an additional parameter –legacy-peer-deps to npm install. The --legacy-peer-deps tells the npm to ignore the peer dependencies and continue the installation of the package.

How do you handle peer dependencies when developing modules?

Ask user to install a dependency your module needs to work without specifying a version in particular. Prevents having multiple version of a same module in user's app node_modules. Reduce javascript files size to load on browser side particularly useful for mobile users.

Are peer dependencies installed automatically?

In both cases, when you install a package, its dependencies and devDependencies are automatically installed by npm. peerDependencies are different. They are not automatically installed.


Video Answer


1 Answers

As I see this problem potentially can be solved using lerna, npm, yarn or webpack.
I want to propose one more webpack solution there, opened a pr to your repo. If a webpack solution is a bad fit for you - just ignore this answer.

It is a little better than externals mechanism, because it will track overlapping peer dependencies automatically.

module.exports = function(config) {
    config.plugins.push(
        new NormalModuleReplacementPlugin(re, function(resource) {
            // first, remove babel and other loaders paths
            const requestWithoutLoaderMeta = resource.request.split('!');
            const requestPath = requestWithoutLoaderMeta.length && requestWithoutLoaderMeta[requestWithoutLoaderMeta.length - 1];

            if (requestPath) {
                // looking for a dependency and package names
                const packagesPath = resolve(__dirname, '../') + '/';
                const requestPathRel = requestPath.replace(packagesPath, '');
                const [packageName, _, depName] = requestPathRel.split('/');

                // if the main package has this dependency already - just use it
                if (dependencies[packageName]) {
                    console.log('\x1b[35m%s\x1b[0m', `[REPLACEMENT]: using dependency <${depName}> from package [main] instead of [${packageName}]`);
                    resource.request = resource.request.replace(`${packageName}/node_modules/${depName}`, `main/node_modules/${depName}`)
                }
            }
        })
    );

    return config;
}

This code will resolve your peer dependencies from main package using webpack.NormalModuleReplacementPlugin.

Note about webpack: since all three frontend kings use it in their CLI (angular, react, vue) I think you can use it easily and safely for such customizations.

I'm happy to hear about alternative technologies (eg. yarn) to solve this problem.

Give @nrwl/nx a try for your next monorepo instead of lerna.
The main difference there is that nrwl projects usually have ONE package.json only (using Google experience), so you need to install the dependencies once for all packages and will not face the problem you described.

like image 196
nickbullock Avatar answered Oct 29 '22 11:10

nickbullock