Sharing a custom common library to NextJS and React Apps, using Typescript with Yarn workspaces

Sharing a custom common library to NextJS and React Apps, using Typescript with Yarn workspaces

Featured on Hashnode

This approach has served Google well for more than 16 years, and today the vast majority of Google's software assets continue to be stored in a single, shared repository. See Figure 1.

This blog post will be part of another series. πŸš€

Let's be honest, everyone starts learning NextJS using the monolith approach, which is ok, but what happens when your codebase starts growing and duplicating patterns, hooks, or the same components over and over again. It gets painful to have to repeat the code, that's where monorepo architecture comes to help, to have reusable code in the same repo, and also avoiding to reference company packages from a different repo (which gets messy). Given this introduction, imagine having independent NextJS and React apps or native depending and linking the same hook, both apps living in the same repository but isolated.

Yarn is not the only tool to achieve this, but it is great because of its simplicity. There are other vendors in the business such as Lerna, Nx, or even the latest version of npm.

Summary

We'll be creating a common library, will be shared to a NextJS site and also to a single page React application.

In this blog post 😊 I will show you:

  • setup yarn for monorepo
  • create 2 independent apps a NextJS and another React SPA
  • create a common custom library
  • add typescript
  • set dependency from the apps to the common library and link together
  • recommendations and conclusion 😎.

Setup

First we'll be using yarn workspaces to implement our monorepo (primarly responsible for linking the packages together), let's begin, from terminal run each of the following commands:

Create our monorepo folder and initialize:

mkdir learning-monorepo && cd learning-monorepo
yarn init

Configure package.json for workspaces, adding these, and removing main if present:

"private": true,
"workspaces": ["packages/*", "apps/*"]

Create these folders

mkdir packages && mkdir apps

NextJS Site

Create NextJS site

cd apps && yarn create next-app nextjs-site

Support typescript and remove git references (by default create next-app starts with git repo)

cd nextjs-site && touch tsconfig.json && rm -rf .git
yarn add -D typescript @types/react @types/node

Also need to rename all the .js files containing react components to .tsx, if they don't contain react components, just renamed to .ts in the case of hook files or any API function.

Some errors might appear but is OK, after the first run, after typescript gets ready.

At this point we can try to run it, to confirm is ok. yarn dev . Then stop it, will work again here later.

Single Page React Application

Go to apps folder and create the single page react application, using the CRA, let's call this app sample-spa:

yarn create react-app  sample-spa --template typescript

Ok at this point we have 2 working apps ready, now we can proceed to create the library. Please check if there is any .git folder on these apps folders, either way, you can do something like rm -rf .git on each of them.

Common libraryπŸ”₯

Go to the packages folder of your project and now let's create a library (we can use create-react-library but for this time, we'll do it manually)

mkdir common && cd common && yarn init

*Fill the information, for this tutorial we have to use the package name as @learning-monorepo/common ( but also you can try in your own projects like @acme/common or something that differentiates your context, like company name, organization, plugins name, etc), entry point as dist/index.js or also editing the main property on the package.json.

Add dev dependencies

yarn add @types/node @types/react typescript -D

Because this is a library, we might want to use the app's provided version, that's why we have to use peerDependencies for react here :

yarn add react react-dom -P

The common package also is going to use typescript, create tsconfig.json :

touch tsconfig.json

And add the following configuration

{
    "compilerOptions": {
        "outDir": "dist",
        "module": "esnext",
        "lib": [
            "dom",
            "esnext",
            "dom.iterable"
        ],
        "moduleResolution": "node",
        "jsx": "react-jsx",
        "sourceMap": true,
        "declaration": true,
        "esModuleInterop": true,
        "noImplicitReturns": true,
        "noImplicitThis": true,
        "noImplicitAny": true,
        "strictNullChecks": true,
        "suppressImplicitAnyIndexErrors": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "allowSyntheticDefaultImports": true
    },
    "include": [
        "src"
    ],
    "exclude": [
        "node_modules", "dist"
    ]
}

Create our index.tsx, this will be the entry point to our common library :

mkdir src && cd src && touch index.tsx

Go ahead and open the index.tsx and put some react sugar!! there :P, a classic hello world component:

interface Props {
  name: string
}

export const HelloWorld = ({ name }: Props) => {
  return <div>Hello {name}</div>
}

Another stuff, go to common folder, we have to configure scripts to transpile our typescript source files, let's add them to package.json, we added a single build and watch for dev purposes, now if we run any, we'll see an output folder called dist, where our applications will reference the dependency:

...
"scripts": {
    "build": "tsc",
    "dev": "tsc -w"
  },

These scripts can be run directly from the common folder: yarn dev or yarn build or from monorepo root like yarn workspace @learning-monorepo/common dev or yarn workspace @learning-monorepo/common build

Finally where the magic happens, run yarn from the root of the monorepo, this will read the workspaces array we added before on the root package.json and create symlinks on node_modules to our workspaces packages

Set up the 2 applications in order to use the common package

NextJS Site

We could use next-transpile-modules, to make NextJS take care of typescript transpilation, it works, but I had issues when adding a 2nd app that is no NextJS created with CRA, can not be configured to transpile ts from node_modules. Then better to provide transpiled version from the common package using the script we created before.

Update package.json to reference the common package

"dependencies": {
    ...
    "@learning-monorepo/common": "*"
  },
`

We are gonna try the component located on common, which can be referenced from our NextJS site, for this we'll modify the index page:

import { HelloWorld } from '@learning-monorepo/common';

and use the component

<HelloWorld name="Guest"/>

Run it from the app directly and see it in your browser:

yarn dev

Or from the root

yarn workspace nextjs-site dev

dev is a provided package.json script created by NextJS setup script we ran at the beginning.

If there is an error when running this, try removing .next folder and start again the server.

Single page React app

Go to sample-spa app and update package.json to reference the common package

"dependencies": {
    ...
    "@learning-monorepo/common": "*"
  },
`

Open the App.tsx and add the following:

import { HelloWorld } from '@learning-monorepo/common';

and use the component

<HelloWorld name="Guest"/>

OK, let's try running this app too to verify if also shows app our shared component properly:

Run it from the app directly and see it in your browser:

yarn start

Or from the root

yarn workspace sample-spa start

Conclusions

Hope this helps you πŸ˜‰ . Feel free to comment and give feedback, try out yourself. Source: https://github.com/asotog/learning-monorepo-yarn

Your homework:

Recommended stuff pending:

  • as mentioned before, remove all .git folders (e.g rm -rf .git inside 2 apps) and just keep one at the root of the repo and setup .gitignore files properly.
  • formatting ( prettier )
  • linting ( eslint )
  • testing ( react-scripts or jest ).
  • styling... (your preference ;))
  • publishing scripts for the common library such as microbundle for react is pretty cool when you want to publish your libraries to npm registries, most of the time you don't need this for non-open-source projects.