Search code examples
reactjsnext.jsstyled-componentssymlinkrollupjs

How to create a symbolic link from a react component library to a next.js application for testing


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:

  • React
  • Typescript
  • Styled-components
  • Storybook
  • Rollup

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.


Solution

  • Here is the approach I'm using with my libraries.

    1. Link the library
    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)

    1. Clear Next.js cache
    rm -rf .next
    
    1. Make changes to the library and re-build

    2. Start Next.js app

    npm run dev
    
    1. Cleanup when done
    rm -rf .next
    npm unlink --no-save my-library
    npm unlink -g my-library react react-dom
    npm install
    

    Alternative approach.

    1. Make changes to the library and re-build

    2. Copy the library into Next.js app

    rm -rf .next node_modules/my-library/dist
    cp -r ../my-library/dist node_modules/my-library/
    
    1. Start Next.js app
    npm run dev
    
    1. Cleanup when done
    rm -rf .next node_modules/my-library
    npm install