Setting Up a Monorepo with Vite, TypeScript, and PNPM Workspaces: A Comprehensive Guide

  • by
  • 7 min read

In the ever-evolving landscape of modern web development, managing complex projects with multiple interconnected components can be a daunting task. Enter the monorepo: a revolutionary approach to project organization that's gaining significant traction among developers and tech giants alike. This comprehensive guide will walk you through the process of setting up a monorepo using Vite, TypeScript, and PNPM workspaces, providing you with a robust and efficient development environment for your projects.

Understanding the Monorepo Approach

Before we dive into the technical setup, it's crucial to understand what a monorepo is and why it's becoming increasingly popular. A monorepo, short for monolithic repository, is a version control strategy where multiple projects are stored in a single repository. Unlike traditional multi-repo setups, where each project has its own repository, a monorepo consolidates all related projects under one roof.

This approach offers several compelling advantages:

  1. Simplified dependency management: With all projects in one place, it's easier to manage and update shared dependencies.
  2. Streamlined code sharing: Developers can easily share and reuse code across different projects.
  3. Unified build and test processes: Monorepos allow for centralized build and test configurations, ensuring consistency across projects.
  4. Improved collaboration: Teams working on different projects can more easily coordinate and share knowledge.
  5. Atomic commits: Changes that span multiple projects can be committed together, maintaining a coherent history.

Companies like Google, Facebook, and Microsoft have adopted monorepos for their large-scale projects, demonstrating the scalability and efficiency of this approach.

Getting Started: Setting Up the Project Structure

Let's begin by creating the foundation for our monorepo. We'll use PNPM, a fast and disk-space efficient package manager, to manage our workspace.

Creating the Project Directory

First, open your terminal and create a new directory for your monorepo:

mkdir pnpm-monorepo
cd pnpm-monorepo

Initializing the Project

Now, let's initialize our project with PNPM:

pnpm init

This command creates a package.json file in your root directory, which will serve as the central configuration for your monorepo.

Setting Up TypeScript

TypeScript is a powerful superset of JavaScript that adds static typing and other features to enhance developer productivity and code quality. Let's add TypeScript to our project:

pnpm add -D typescript @types/node

Next, we'll create a base TypeScript configuration file that can be extended by individual packages:

echo '{
  "compilerOptions": {
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  }
}' > tsconfig.base.json

This configuration enables strict type checking, ES module interoperability, and other best practices for TypeScript development.

Configuring PNPM Workspace

To define our workspace structure, we'll create a pnpm-workspace.yaml file:

echo 'packages:
  - "packages/*"' > pnpm-workspace.yaml

This configuration tells PNPM to look for packages in the packages directory, allowing us to manage multiple projects within our monorepo.

Creating Packages

With our base structure in place, let's create two packages: a shared library and a main application. This setup demonstrates how monorepos can manage both reusable code and specific applications.

Creating the Shared Package

First, let's create a shared package that can be used across different projects:

mkdir -p packages/shared
cd packages/shared
pnpm init

Update the package.json in the shared package to include build scripts and TypeScript configurations:

{
  "name": "@pnpm-monorepo/shared",
  "version": "1.0.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "tsc"
  }
}

Create a simple TypeScript file with shared functionality:

mkdir src
echo "export const greet = (name: string) => \`Hello, \${name}!\`;" > src/index.ts

Add a tsconfig.json file for the shared package:

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

Creating the Main Application with Vite

Now, let's create our main application using Vite, a next-generation frontend build tool that provides an extremely fast development experience:

cd ..
pnpm create vite main --template react-ts
cd main

Update the package.json in the main application to include the shared package as a dependency:

{
  "name": "@pnpm-monorepo/main",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@pnpm-monorepo/shared": "workspace:*",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@types/react": "^18.0.28",
    "@types/react-dom": "^18.0.11",
    "@vitejs/plugin-react": "^3.1.0",
    "typescript": "^4.9.3",
    "vite": "^4.2.0"
  }
}

Configuring the Build Process

To streamline our development workflow, we'll set up some scripts in the root package.json:

{
  "name": "pnpm-monorepo",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "build": "pnpm -r build",
    "dev": "pnpm --filter @pnpm-monorepo/main dev"
  }
}

These scripts allow us to build all packages recursively and run the development server for the main application with simple commands.

Using the Shared Package in the Main Application

Now that we have our packages set up, let's demonstrate how to use the shared functionality in our main application. Update src/App.tsx in the main application:

import React from 'react'
import { greet } from '@pnpm-monorepo/shared'

function App() {
  return (
    <div>
      <h1>{greet('Monorepo Developer')}</h1>
    </div>
  )
}

export default App

This example shows how easily we can import and use functions from our shared package, demonstrating the power of code sharing in a monorepo setup.

Building and Running the Monorepo

With our setup complete, we can now build and run our monorepo:

To build all packages:

pnpm build

To start the development server for the main application:

pnpm dev

Advanced Monorepo Techniques

As your monorepo grows, consider implementing these advanced techniques to enhance your development workflow:

1. Implementing Changesets for Version Management

Changesets is a powerful tool for managing versions and changelogs in a monorepo. It allows developers to declare changes as they make them, automatically determining the appropriate version bumps and generating changelogs.

To implement Changesets:

pnpm add -D @changesets/cli
pnpm changeset init

This setup enables a more streamlined process for releasing updates across your packages.

2. Shared ESLint and Prettier Configurations

Maintaining consistent code style across all packages is crucial. Set up shared ESLint and Prettier configurations in the root of your monorepo:

pnpm add -D eslint prettier @typescript-eslint/eslint-plugin @typescript-eslint/parser

Create .eslintrc.js and .prettierrc.js files in the root directory with your preferred configurations.

3. Optimizing Build Performance with Turborepo

For larger monorepos, build times can become a bottleneck. Turborepo is a high-performance build system for JavaScript and TypeScript codebases that can significantly speed up your builds:

pnpm add -D turbo

Configure Turborepo in a turbo.json file to define your build pipelines and caching strategies.

4. Implementing Continuous Integration

Set up a CI/CD pipeline that understands your monorepo structure. GitHub Actions, GitLab CI, or CircleCI can be configured to build, test, and deploy your monorepo efficiently. Here's a basic GitHub Actions workflow:

name: CI
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: pnpm/action-setup@v2
      - uses: actions/setup-node@v2
        with:
          node-version: '14'
      - run: pnpm install
      - run: pnpm build
      - run: pnpm test

5. Documentation and Onboarding

As your monorepo grows, comprehensive documentation becomes crucial. Consider using tools like Docusaurus or VuePress to create a documentation site for your monorepo. Include:

  • Overall architecture overview
  • Package-specific documentation
  • Contribution guidelines
  • Setup and onboarding instructions

Conclusion

Setting up a monorepo with Vite, TypeScript, and PNPM workspaces provides a powerful foundation for managing complex projects. This approach offers numerous benefits, including simplified dependency management, improved code sharing, and streamlined build processes. As you continue to develop your monorepo, remember to keep your structure organized, maintain clear documentation, and leverage advanced tools and techniques to enhance your development workflow.

By embracing this monorepo structure, you're setting yourself up for a more efficient and scalable development process. Whether you're working on a small team project or a large-scale enterprise application, the monorepo approach can significantly improve your productivity and code quality.

As the web development landscape continues to evolve, staying adaptable and embracing efficient project structures like monorepos will be key to success. Happy coding, and may your monorepo journey be filled with clean code and smooth deployments!

Did you like this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.