Alpha

Back to posts

Monorepo: Setup with PnPM

25 May 2023 — 12 min read

Post Image

Contents

Chapter 1: Introduction

Chapter 2: Setup with PnPM

In previous article, we already learnt what monorepo is. In this article, we will create a monorepo project using PnPM workspaces.
 

 

 

Install and initialize PnPM

 
PnPM is a pacakge management just like npm and yarn. It is an alternative tool to those ones.
 
First, we need to install PnPM if you haven't installed it before. Using this url for more detail.
 
With PnPM installed, we can initilize a new project using this command:

pnpm init 

 
We will have a package.json file in root folder. The content will be like this:

{ 
  "name": "monorepo", 
  "version": "1.0.0", 
  "description": "", 
  "private": true, 
  "scripts": { 
    "test": "echo \"Error: no test specified\" && exit 1" 
  }, 
  "keywords": [], 
  "author": "", 
  "license": "ISC" 
} 

 
Next, create a file called pnpm-workspace.yaml at root folder. This file is a configuration to define where to put all projects we have. Open it and add those lines:

packages: 
  - "libs/**" 
  - "workspaces/**" 

The config tells PnPM that we will have two places to store our projects. All projects should be put inside those two folders.
 
After that, install TypeScript to root package:

pnpm -w add -D typescript 

 

All the installed packages at root package will apply for all the mono projects

 
Create tsconfig.json at root folder:

{ 
  "compilerOptions": { 
    "allowJs": true, 
    "allowSyntheticDefaultImports": true, 
    "esModuleInterop": true, 
    "experimentalDecorators": true, 
    "forceConsistentCasingInFileNames": true, 
    "importHelpers": true, 
    "isolatedModules": true, 
    "module": "commonjs", 
    "moduleResolution": "node", 
    "noUncheckedIndexedAccess": true, 
    "removeComments": true, 
    "resolveJsonModule": true, 
    "skipLibCheck": true, 
    "sourceMap": false, 
    "strict": true, 
    "target": "esnext" 
  }, 
  "exclude": [ 
    "**/node_modules/*", 
    "**/dist/*" 
  ] 
} 

This file acts like global tsconfig for all projects. Every tsconfig of mono project should inherit from this file.
 

 

 

Create projects

 
Access to the libs folder, create a folder called commons, then run init command to init the project. We will package.json like this:

{ 
  "name": "@libs/commons", 
  "version": "1.0.0", 
  "description": "", 
  "main": "index.js", 
  "scripts": { 
    "test": "echo \"Error: no test specified\" && exit 1" 
  }, 
  "keywords": [], 
  "author": "", 
  "license": "ISC" 
} 

 
Create tsconfig.json inside libs/commons:

{ 
  "extends": "../../tsconfig.json", 
  "compilerOptions": { 
    "baseUrl": "src", 
    "outDir": "lib" 
  }, 
  "include": [ 
    "src" 
  ], 
  "exclude": [ 
    "lib", 
    "node_modules" 
  ] 
} 

 
Similar to the previous step, access to workspaces folder and create folder called major-app. Init and create tsconfig file:

{  
  "name": "major-app",  
  "version": "1.0.0",  
  "description": "",  
  "main": "index.js",  
  "scripts": {  
    "test": "echo \"Error: no test specified\" && exit 1"  
  },  
  "keywords": [],  
  "author": "",  
  "license": "ISC"  
}  

 

{ 
  "extends": "../../tsconfig.json", 
  "compilerOptions": { 
    "baseUrl": "src", 
    "outDir": "dist" 
  }, 
  "include": [ 
    "src" 
  ] 
} 

 

 

Install and use libs commons to major-app

 
First, create a method for commons lib.
 

interface ICombineOptionsProps { 
  joinWith: string; 
} 
 
function compact(source: (string | undefined)[]): string[] { 
  return source.filter((s) => !!s) as string[]; 
} 
 
export function combine( 
  opts: ICombineOptionsProps | string | undefined = '', 
  ...params: (string | undefined)[] 
): string { 
  let options: ICombineOptionsProps = { joinWith: ' ' }; 
  
  if (typeof opts === 'object') { 
    options = opts; 
  } else { 
    params = [opts, ...params]; 
  } 
 
  const { joinWith } = options; 
 
  return compact(params).join(joinWith); 
} 

 
Change the package.json to export the method for other project:

1{ 
2  "name": "@libs/commons", 
3  "version": "1.0.0", 
4  "description": "", 
5  "main": "index.js", 
6  "main": "lib/index.js", 
7  "types": "lib/index.d.ts", 
8  "scripts": { 
9    "test": "echo \"Error: no test specified\" && exit 1" 
10    "build": "tsc" 
11  }, 
12  "keywords": [], 
13  "author": "", 
14  "license": "ISC" 
15} 

 
Next, we will build the commons project:

pnpm --filter=@libs/commons build 

 
After build, install the @libs/commons to major-app:

pnpm --filter=major-app add @libs/commons 

 

Update: for PnPM version 9 (9.0.6), it changes how it will fetch package. You should do the step below. Check here for more info.

 

pnpm --filter=major-app add @libs/commons@workspace: 

 

Now the content of package.json of major-app will change similar to:

1{   
2  "name": "major-app",   
3  "version": "1.0.0",   
4  "description": "",   
5  "main": "index.js",   
6  "scripts": {   
7    "test": "echo \"Error: no test specified\" && exit 1"   
8  },   
9  "keywords": [],   
10  "author": "",   
11  "license": "ISC", 
12  "dependencies": { 
13    "@libs/commons": "workspace:^" 
14  } 
15}   

 

You will receive a difference content if you use an older PnPM (below 8)

 

1{   
2  "name": "major-app",   
3  "version": "1.0.0",   
4  "description": "",   
5  "main": "index.js",   
6  "scripts": {   
7    "test": "echo \"Error: no test specified\" && exit 1"   
8  },   
9  "keywords": [],   
10  "author": "",   
11  "license": "ISC", 
12  "dependencies": { 
13    "@libs/commons": "workspace:^1.0.0" 
14  } 
15}   

You should change the version like the below code to make sure the major-app always uses the latest version of commons:

1{   
2  "name": "major-app",   
3  "version": "1.0.0",   
4  "description": "",   
5  "main": "index.js",   
6  "scripts": {   
7    "test": "echo \"Error: no test specified\" && exit 1"   
8  },   
9  "keywords": [],   
10  "author": "",   
11  "license": "ISC", 
12  "dependencies": { 
13    "@libs/commons": "workspace:^1.0.0" 
14    "@libs/commons": "workspace:*" 
15  } 
16}   

 
Finally, create a file and use the code inside major-app

import { combine } from '@libs/commons'; 
 
const combinedString = combine({ joinWith: ' - ' }, 'Alpha', 'de Lucifer'); 
 
console.log(combinedString); // will print Alpha - de Lucifer 

 

 

Wrap up

 
In this article, we explore how to setup a monorepo using PnPM. Within this comprehensive guide, we not only cover the essential steps to establish a monorepo architecture but also reveal the invaluable technique of code sharing between the various projects within the monorepo.


Or

Alpha

Fullstack developer who converts pepsi into code.


Or

Contact

General