I'm developing a react component library that is using axios:
I put axios in the devDependencies
in order to develope, and in the peerDependencies
.
In rollup.config.ts
, I put axios inside "external" props, so it won't get builded with my code.
export default [ {
input: "src/index.ts",
output: [
{
file: packageJson.main,
format: "cjs",
sourcemap: true,
},
{
file: packageJson.module,
format: "esm",
sourcemap: true,
},
],
plugins: [
resolve(),
commonjs(),
typescript({ tsconfig: "./tsconfig.json" }),
json(),
],
external: ['react-dom', 'axios', 'react-redux', 'react', '@reduxjs/toolkit', 'react-audio-player']
}, {
input: "dist/esm/index.d.ts",
output: [{ file: "dist/index.d.ts", format: "esm" }],
plugins: [dts()]
}, ];
Then from my component library I npm-linked react and axios:
npm link ../path-to-host/node_modules/react
npm link ../path-to-host/node_modules/axios
npm link
npm link <libray-name>
In my host project I do some setup to axios (interceptors and headers) that are not catched by my component library, which sends "raw" request. This makes me wander that the component library is still using its own axios instead of the one from the host project.
How can I force my component library to consume the same dependencies of the host project?
UPDATE
Somehow it starts to works correctly sometimes, and sometimes not: I symlinked axios and react but its like the link doesn't affect the project consistently, rather they works at blinks.
I don't think this is the correct way to achieve what you are after, but it has given me the best results with my rough investigating in the past.
Problem As I Understand It
Dependencies are first attempted to be resolved from the local node_module folder before checking the root node module folder.
For example, with this directory structure:
Host Project Root
|- src
|- node_modules
| |- react
| |- component-library
| | |- node_modules
| | | |- react
When bundling the component-library
package any import for react
will resolve to: ./node_modules/component-library/node_modules/react
Which is not the same as any import for react
from the Host Projects ./src
which would resolve to: ./node_modules/react
Why is this a problem?
This causes any constant values / objects from the duplicated import (react
in the above example) to be different for the imported package (component-library
) and the host code (.src
).
For React this means that things such as Contexts used by both host code (./src
) and imported package (component-library
) will not be able to see each others data as there state will be held in separate React constants that have no connection to each other in the generated bundle.
Any Solutions?
What about npm dedupe
?
Well if your goal is to run a watch on both host code (./src
) and imported package (component-library
) for concurrent development purposes then npm dedupe
will most likely mangle the node modules for the imported package (component-library
) and cause its build to fail, this is far from ideal.
A Solution (kind of): to make it so that it is possible to be able to run a watch on both host code (./src
) and the imported package (component-library
) and not cause an issue with unwanted duplicate packages in the resulting bundle it's possible to use aliases with some limitations.
Webpack example:
const fs = require('fs-extra');
const config = {
/* Other webpack config */
resolve: {
alias: {
}
}
}
// load in package.json
let packageJson = fs.readFileSync('./package.json');
let package = JSON.parse(packageJson);
// loop through all dependencies in package.json and add aliases
// The aliases added below will ensure that all imports for a given package will
// resolve to "./node_modules/package" instead of the possible
// "./node_modules/component-library/node_modules/package"
if (package?.dependencies) {
Object.keys(package.dependencies).forEach(key => {
config.resolve.alias[key] = path.resolve(`./node_modules/${key}`);
})
}
// This is the same as the snippet but for devDependencies
// This should not be needed (which is why it is commented out)
// devDependencies are intended to not make it into the resulting bundle
// and should therefore not cause any issues.
// It can be uncommented if you want a scorched earth approach.
/*
* if (package?.devDependencies) {
* Object.keys(package.devDependencies).forEach(key => {
* config.resolve.alias[key] = path.resolve(`./node_modules/${key}`);
* })
* }
*/
ViteJs example:
const fs = require('fs-extra');
const path = require('path');
// helper to resolve path names.
function pathResolve(dir) {
return path.resolve(__dirname, '.', dir)
}
// load in package.json
let packageJson = fs.readFileSync('./package.json');
let package = JSON.parse(packageJson);
// array to hold aliases to be added to vite config.
const resolveNodeModules: any[] = []
// loop through all dependencies in package.json and add aliases
// The aliases added below will ensure that all imports for a given package will
// resolve to "./node_modules/package" instead of the possible
// "./node_modules/component-library/node_modules/package"
if (packageJson?.dependencies) {
Object.keys(packageJson.dependencies).forEach(key => {
resolveNodeModules.push({
find: key,
replacement: pathResolve('node_modules') + '/' + key
});
})
}
// This is the same as the snippet but for devDependencies
// This should not be needed (which is why it is commented out)
// devDependencies are intended to not make it into the resulting bundle
// and should therefore not cause any issues.
// It can be uncommented if you want a scorched earth approach.
/* if (packageJson?.devDependencies) {
* Object.keys(packageJson.devDependencies).forEach(key => {
* resolveNodeModules.push({
* find: key,
* replacement: pathResolve('node_modules') + '/' + key
* });
* })
* }
*/
const config = {
resolve: {
alias: [
...resolveNodeModules
/* Other aliases here if required i.e. { find: /@\//, replacement: pathResolve('src') + '/' }
]
}
}
I haven't used rollup directly only via ViteJs which provides a preconfigured rollup. I think ViteJs just uses @rollup/plugin-alias
which should just be able to take the same resolveNodeModules
value from the ViteJs example above.
Limitations
All dependencies that are shared by the host code (./src
) and the imported package (component-library
) must be in the host projects package.json
in the dependencies
section. This includes packages that are dependencies of dependencies.
All dependencies have to be happy with the version that is being force resolved, i.e. if two packages use random-package
but one requires v1.0.0
and the other requires v8.0.0
usually without the forced resolution via the alias this would be bundled as two separate versions of the package, but with the aliases it would be bundled as a single version which might not work for both importing packages due to breaking changes between versions.
Conclusion
Is this a proper solution to your problem? No
Does this work? Yes/Sometimes - success rate is good for projects that have dependencies with versions that were created from around the same time period as each other. This usually means there is a smaller chance of occurrence for limitation 2.
Hopefully someone else will come along and have an easier more robust solution and we both will have learned something, but for now I hope this at least assists in understanding the problem.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With