Sharing a custom common library to NextJS and React Apps, using Typescript with Yarn workspaces
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.grm -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.