I've created a React component library that is running as a micro frontend service. I am able to build it and have it running using these technologies:
I am also able to publish the component library as a package to GitHub/npm and import the library into a next.js application I'm also running locally.
How do I create a symbolic link from the next.js application to the react component library that is running locally on my machine?
Right now, the process is to test and develop the component in storybook, then I have to publish the library to GitHub, then I have to update the local package to the new version in GitHub to see those new changes.
What I'd like to be able to do is run the react component library inside the next.js app so I can build and test my components inside the application and know that everything is working as expected.
React Component Library Rollup Config:
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import typescript from "@rollup/plugin-typescript";
import dts from "rollup-plugin-dts";
import postcss from 'rollup-plugin-postcss';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import packageJson from "./package.json" assert { type: "json" };
export default [
{
input: "src/index.ts",
external: ["react-dom", "styled-components"],
output: [
{
file: packageJson.main,
format: "cjs",
sourcemap: true,
},
{
file: packageJson.module,
format: "esm",
sourcemap: true,
},
],
plugins: [
peerDepsExternal(),
resolve(),
commonjs(),
typescript({ tsconfig: "./tsconfig.json" }),
postcss()
],
globals: { "styled-components": "styled" },
},
{
input: "dist/esm/types/index.d.ts",
output: [{ file: "dist/index.d.ts", format: "es" }],
plugins: [dts()],
external: [/\.(css|less|scss)$/],
},
{
input: "src/themes/index.ts",
output: [{ file: "dist/themes/index.js", format: "cjs" }],
plugins: [commonjs(), resolve(), typescript({ tsconfig: "./tsconfig.json" })]
}
];
React Component Library package.json
{
"name": "xxxxx",
"version": "0.2.11",
"description": "React Component Library",
"scripts": {
"rollup": "rollup -c",
"test": "jest",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"chromatic": "npx chromatic --project-token=xxxxx",
"clean": "rm -rf node_modules && rm -rf package-lock.json && npm i",
"generate": "node ./src/util/create-component"
},
"keywords": [],
"author": {
"name": "xxxxx"
},
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.22.5",
"@babel/preset-env": "^7.22.5",
"@babel/preset-react": "^7.22.5",
"@babel/preset-typescript": "^7.22.5",
"@rollup/plugin-commonjs": "^25.0.1",
"@rollup/plugin-node-resolve": "^15.1.0",
"@rollup/plugin-terser": "^0.4.3",
"@rollup/plugin-typescript": "^11.1.1",
"@storybook/addon-essentials": "^7.0.21",
"@storybook/addon-interactions": "^7.0.21",
"@storybook/addon-links": "^7.0.21",
"@storybook/addon-styling": "^1.3.0",
"@storybook/blocks": "^7.0.21",
"@storybook/react": "^7.0.21",
"@storybook/react-webpack5": "^7.0.21",
"@storybook/testing-library": "^0.0.14-next.2",
"@testing-library/react": "^14.0.0",
"@types/jest": "^29.5.2",
"@types/react": "^18.2.12",
"@types/styled-components": "^5.1.26",
"babel-jest": "^29.5.0",
"babel-plugin-styled-components": "^2.1.3",
"chromatic": "^6.19.8",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rollup": "^3.25.1",
"rollup-plugin-dts": "^5.3.0",
"rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-postcss": "^4.0.2",
"storybook": "^7.0.21",
"styled-components": "^6.0.0-rc.3",
"tslib": "^2.5.3",
"typescript": "^5.1.3"
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"styled-components": "^6.0.0-rc.3"
},
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"files": [
"dist"
],
"types": "dist/index.d.ts",
"publishConfig": {
"registry": "xxxxx"
},
"readme": "ERROR: No README data found!",
"_id": "[email protected]"
}
Next.js App Next Config:
const path = require("path");
/** @type {import('next').NextConfig} */
const nextConfig = {
webpack(config) {
config.experiments = {
asyncWebAssembly: true,
layers: true,
};
config.resolve.alias = {
...config.resolve.alias,
"styled-components": path.resolve("./node_modules/styled-components"),
"react-dom": path.resolve("./node_modules/react-dom"),
"react": path.resolve("./node_modules/react")
};
return config;
},
reactStrictMode: true,
swcMinify: true,
compiler: {
styledComponents: true,
},
};
module.exports = nextConfig;
Things I've tried:
npm link
Run npm link
inside my component library
Inside my next.js application I run npm link <package-name> which successfully updates the package. But when I go to run my next.js application using
npm run dev` I don't get any errors but I don't see any of the updates I've made locally to the component library.
I had to update the next.config.js to include the resolvers for the styled-components, react-dom, and react packages in order to get it running
npm install
I've run npm install --no-save <path/to/package-name>
inside the next.js application to create a symbolic link which is successful as well.
Same problem though, when I go to my next.js application I don't get any errors but I'm not seeing the updates to my component library that I am able to see in while running storybook locally.
It's also important to note that inside my component library I am using peerDependencies for react, react-dom, and styled-components.
I expect the host application to have those packages already installed in order to run the component library.
I'm not sure if this may be causing any issues.
Also is there any way to physically see the symlink setup?
Whenever I run these above commands there are no errors or anything but nothing significant in the terminal that shows that there actually is a link that is overriding the packages.
Because what it looks like to me is that its still using the installed packages from the latest version that is on GitHub, rather than using the symlink locally for the local changes I'm making.
Here is the approach I'm using with my libraries.
npm link ../my-library ../my-library/node_modules/react ../my-library/node_modules/react-dom
(you may not need to link react
and react-dom
if you already setup resolve aliases in your Next.js config)
rm -rf .next
Make changes to the library and re-build
Start Next.js app
npm run dev
rm -rf .next
npm unlink --no-save my-library
npm unlink -g my-library react react-dom
npm install
Alternative approach.
Make changes to the library and re-build
Copy the library into Next.js app
rm -rf .next node_modules/my-library/dist
cp -r ../my-library/dist node_modules/my-library/
npm run dev
rm -rf .next node_modules/my-library
npm install