Shane Yu

Building a High-Performance Monorepo with a Modern Tech Stack

Published
Read time
6m read
Topics
TypeScript and Node JS

Objective

To build a monorepo using a high-performance, modern tech stack, designed to deliver a better developer experience and enhance efficiency across the board.

Historically, developer tooling has lagged behind in terms of both performance and usability. Many of these tools have been JavaScript-based, limiting the potential for significant speed improvements. There's a ceiling to the performance achievable when JavaScript is used to handle tasks like building, linting, and formatting… until now.

Enter Rust-based tooling. With its exceptional performance and system-level capabilities, Rust has rapidly gained popularity for powering new, high-performant tools like rsbuild, biome, and turbo. These tools outperform their predecessors, providing greater customization and performance that developers need.

In this monorepo, we'll be using pnpm as our package manager, taking advantage of its workspace capabilities to manage multiple projects seamlessly. While other package managers like yarn and npm also support workspaces, pnpm stands out by not hoisting packages to the root and keeping workspace packages isolated. This isolation also optimizes installation by symbolically linking dependencies, minimizing redundancy and improving efficiency.

Tech stack

In this monorepo, we’ll be using a carefully selected tech stack that prioritizes speed, efficiency, and developer experience. While I recommend following along with these specific tools, you can substitute them based on your own preferences or requirements later.

  • Pnpm: A fast, efficient package manager with robust workspace features.
  • TypeScript: A typed superset of JavaScript that provides static type checking for improved reliability.
  • BiomeJS: A high-performance formatter and linter for JavaScript, TypeScript, and more.
  • Simple Git Hooks: A lightweight tool for managing Git hooks with zero dependencies.
  • Lint-Staged: Enables linting and formatting of staged files before committing to ensure clean, consistent code.
  • CommitLint: Ensures commit messages adhere to a defined convention for better Git history.
  • Rsbuild: A Rust-based, high-speed build tool optimized for performance.
  • Rslib: A build tool for libraries, based on rsbuild.
  • TurboRepo: An incremental build system written in Rust, optimized for JavaScript and TypeScript.

Prerequisites

To follow along, ensure that you have the following installed on your machine:

  • Git
  • Node.js: Use Node.js v20, as we'll create an AWS Lambda function that requires Node.js v20.x for compatibility with AWS runtimes.

Installing PNPM

You can install and enable PNPM using Corepack:

corepack enable
corepack prepare pnpm@latest --activate

Caution

Note: Node.js is moving toward removing Corepack from its distribution in a future major release. If this happens, you'll need to install Corepack manually before enabling PNPM. More details can be found here.

Initial setup

Let's start by creating a new folder named monorepo. Inside this folder, run the following commands to initialize PNPM and Git:

# Initialize PNPM and Git
pnpm init
git init

After running the these commands, a package.json configuration file will appear in your project's root directory. Open this file to make a few adjustments.

./package.json
{
  "name": "monorepo",
  "version": "1.0.0",
  "description": "",
  "type": "module",
  "private": true,
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
    },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Code formatting and linting

To ensure consistent code styling across the team and reduce stylistic changes during Git pull request reviews, we'll set up biome for code linting and formatting. Biome's fast, Rust-based performance makes it an ideal choice for keeping our codebase clean and developer-friendly.

Installing and setting up Biome

Let's start by installing Biome as a development dependency:

pnpm add -D @biomejs/biome

Next, initialize Biome in the project:

pnpm biome init

After running the init command, a biome.json configuration file will appear in your project's root directory. Open this file to make a few adjustments to better suit our team's coding standards.

Biome is designed to align closely with Prettier's style defaults, so if you've used Prettier, much will feel familiar. However, for modern, larger monitors, I find a maximum line length of 140 characters more practical than the traditional 80 characters. This allows for readable code without excessive line wrapping, especially on high-resolution displays.

Here's the configuration we'll use in biome.json, with adjustments to the formatter and JavaScript settings:

./biome.json
{
  "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
  "vcs": {
    "enabled": true,
    "clientKind": "git",
    "useIgnoreFile": true
    },
  "files": {
    "ignoreUnknown": false,
    "ignore": []
    },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 140,
    "attributePosition": "auto"
    },
  "organizeImports": {
    "enabled": true
    },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
        }
    },
  "javascript": {
    "formatter": {
      "quoteStyle": "single"
        }
    }
}

Automating Biome in our workflow

While you can run Biome manually using commands from its documentation, we want it integrated into our development workflow to automatically lint and format code before each commit. This approach minimizes code style discrepancies in the codebase.

For IDE integration, I'll be using Visual Studio Code (VSCode) as my primary editor in this monorepo. I'll walk through some recommended extensions and workspace settings that make Biome's linting and formatting visible directly within the editor, enhancing the development experience and catching issues in real-time.

Let's configure some workspace settings for VSCode by creating a new file at ./.vscode/settings.json with the following content:

./.vscode/settings.json
{
  "editor.codeActionsOnSave": {
    "source.organizeImports.biome": "explicit",
    "quickfix.biome": "explicit"
    },
  "editor.defaultFormatter": "biomejs.biome",
  "editor.formatOnSave": true,
  "editor.tabSize": 2,
  "editor.autoIndent": "advanced",
  "editor.insertSpaces": true,
  "editor.detectIndentation": false,
  "editor.rulers": [
        {
      "column": 140,
      "color": "#ff000066"
        }
    ],
  "html.format.wrapLineLength": 140
}

This configuration sets VSCode to use 2 spaces for indentation instead of tabs, enables advanced auto-indentation, and explicitly designates biome as the default formatter. It also establishes a maximum line length of 140 characters and adds a subtle red ruler as a visual guide to indicate where the maximum line length ends.

Next, we'll add simple-git-hooks and lint-staged to automatically run biome checks and fixes whenever we attempt to commit our code.

Installing required packages

Start by installing the necessary packages as development dependencies:

pnpm add -D simple-git-hooks lint-staged

Then, make the following updates to the package.json file in the root of your repository:

./package.json
{
  "name": "monorepo",
  "version": "1.0.0",
  "description": "",
  "type": "module",
  "private": true,
  "scripts": {
    "format": "biome format --write",
    "lint": "biome lint --write",
    "check": "biome check --write"
    },
  "packageManager": "pnpm@9.5.0",
  "devDependencies": {
    "@biomejs/biome": "^1.9.4",
    "simple-git-hooks": "^2.11.1"
    },
  "simple-git-hooks": {
    "pre-commit": "pnpm dlx lint-staged"
    },
  "lint-staged": {
    "*.{js,cjs,mjs,ts,mts,json,jsonc}": "pnpm dlx biome --check --write"
    }
}

With this setup, whenever you run git commit, the biome check command will automatically lint and format any modified files included in that commit. Any files that receive automatic fixes will be added to the commit for you.

If you wish to run biome linting and formatting manually at any time, you can use the following commands:

# Formats files and directories using the format command with the --write option
pnpm format
 
# Lint and apply "safe fixes" to files and directories using the lint command with the --write option
pnpm lint
 
# Runs both format and lint commands with the --write option
pnpm check

Before we start making commits, it's also a good idea to install the Biome VSCode extension. This extension enhances your development experience by providing integrated linting and formatting directly within the editor.

To install the Biome extension, go to the Extensions view in VSCode (you can access it by clicking the Extensions icon in the sidebar or using the shortcut Ctrl+Shift+X). Search for “Biome” and install the extension by BiomeJS.

Additionally, you can add Biome as a recommended extension for your workspace. Create or update the ./.vscode/extensions.json file with the following content:

./.vscode/extensions.json
{
  "recommendations": [
    "biomejs.biome"
    ]
}

This ensures that anyone who clones the repository will be prompted to install the Biome extension, keeping the development environment consistent across the team.

Before we make our first Git commit, we need to create a .gitignore file to specify which files and folders Git should exclude from tracking and committing. Create a new file in the root of the repository at ./.gitignore and add the following content:

./.gitignore
# Build output
dist/
build/
 
# Test output
coverage/
test-reports/
 
# Generated outputs / caches
.rsbuild/
.rslib/
.turbo/
.pnpm_store/
 
# Dependencies
node_modules/
 
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
 
# IDE settings
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
 
# Local .env files
.env*.local
 
# Miscellaneous
.DS_Store
*.local
*.log*
Thumbs.db

This basic .gitignore file will exclude generated folders and files that don't need to be committed to Git. Instead, these should be generated by each engineer who clones the repository or when running as part of a CI/CD pipeline.

Now we can proceed to make our first commit. Run the following commands:

# Stage all changed files
git add .
 
# Commit the changed files with a descriptive commit message
git commit -m "feat: initial setup with biome and pnpm"

Note

  1. Next add instructions to setup CommitLint with conventional commits
  2. Afterwards add instructions to convert out repo into a monorepo and begin by adding a new apps/lambda/