TypeScript Monorepo Mẫu: Hỗ trợ IDE Sẵn có và Cấu hình Xây dựng Tinh gọn

1. Tổng quan dự án: Một mẫu Monorepo TypeScript "sử dụng ngay lập tức"

Nếu bạn đang tìm kiếm giải pháp Monorepo cho dự án frontend hoặc full-stack và đã mệt mỏi khi dành hàng giờ để cấu hình chỉ để cho tính năng "nhảy đến định nghĩa" trong IDE hoạt động, hoặc phải vật lộn với các script xây dựng để xuất bản các gói sạch sẽ, thì dự án mẫu ts-monorepo này có thể chính là thứ bạn cần. Đây không phải là hướng dẫn dạy bạn xây dựng từ đầu, mà là một bộ khung công trình đã được kiểm nghiệm thực tế, có thể sử dụng ngay làm điểm khởi đầu. Mục tiêu cốt lõi của nó rất rõ ràng: loại bỏ hai vấn đề lớn nhất trong phát triển Monorepo - hỗ trợ IDE và đầu ra xây dựng.

Tôi đã thấy nhiều đội nhóm sau khi áp dụng Monorepo, trải nghiệm phát triển không được cải thiện mà còn giảm sút. Người mới nhân bản mã nguồn, việc đầu tiên họ làm không phải là npm installnpm start, mà cần thực hiện một chuệnh lệnh xây dựng bí ẩn, nếu không thì khi nhấp vào các gói cục bộ trong VSCode hay WebStorm, trình biên dịch chỉ chuyển đến một tệp khai báo .d.ts chứ không phải mã nguồn thực tế. Khi xuất bản còn là một cơn ác mộng, bạn có thể phát hiện trong sản phẩm xây dựng vô tình lẫn mã từ không gian làm việc khác, hoặc các đường dẫn tham chiếu hỗn độn. Mẫu này chính được tạo ra để giải quyết những vấn đề đó, với tất cả cấu hình cần thiết được thiết lập sẵn, cho phép bạn tập trung ngay vào logic nghiệp vụ thay vì công cụ xây dựng.

2. Triết lý thiết kế cốt lõi và phân tích kiến trúc

2.1 Mục tiêu hàng đầu: Trải nghiệm IDE liền mạch

Mẫu này đặt "trải nghiệm phát triển" ở mức độ ưu tiên cao nhất. Nguyên tắc thiết kế cơ bản là: sau khi nhân bản dự án, không cần thực hiện bước xây dựng nào, các tính năng điều hướng mã trong IDE (như Go to Definition, Find All References) phải hoạt động ngay lập tức và chính xác.

Cách thức thực hiện như thế nào? Chìa khóa nằm ở việc sử dụng thông minh cấu hình paths trong tsconfig.json. Cấu hình Monorepo truyền thống có thể khiến bạn tham chiếu đến thư mục dist đã được xây dựng trong quá trình phát triển, hoặc dựa vào project references, tất cả đều yêu cầu bạn phải thực hiện xây dựng trước. Mẫu này áp dụng chiến lược "tham chiếu trực tiếp đến mã nguồn". Trong tệp tsconfig.json ở thư mục gốc, đã cấu hình ánh xạ đường dẫn đến các thư mục mã nguồn (thường là src) của các gói con.

// Ví dụ đơn giản hóa tsconfig.json ở thư mục gốc
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@my-domain/*": ["packages/*/src"],
      "@client/*": ["apps/*/src"]
    }
  }
}

Khi đó, khi bạn viết import { helpers } from '@my-domain/common-tools' trong apps/web-client, TypeScript server sẽ trực tiếp giải quyết đến packages/common-tools/src/index.ts, cho phép bạn nhảy ngay đến mã nguồn thực. Cấu hình này tuy đơn giản nhưng cần rất nhiều công việc tích hợp để duy trì hành vi nhất quán trong môi trường nhiều công cụ (như Jest, Webpack, Babel), đó chính là giá trị mà mẫu này mang lại.

2.2 Mục tiêu thứ hai: Gói xuất bản sạch và có thể dự đoán

Bẫy phổ biến thứ hai của Monorepo là ô nhiễm xây dựng. Khi bạn xây dựng một gói để xuất bản lên npm, bạn mong muốn node_modules của nó phải sạch, và tất cả các tham chiếu đến gói anh em đều trỏ đến dependency bên ngoài (như "@my-domain/common-tools": "^1.0.0"), thay vì đường dẫn tệp cục bộ.

Mẫu này phân biệt cấu hình phát triểncấu hình xây dựng để giải quyết vấn đề. Mỗi không gian làm việc có loại package đều có hai tệp tsconfig:

  1. tsconfig.json: Kế thừa từ cấu hình gốc, dùng cho phát triển, hỗ trợ ánh xạ đường dẫn mã nguồn.
  2. tsconfig.build.json: Dùng cho xây dựng và xuất bản. Cấu hình quan trọng là "paths": {}, tức là xóa bỏ tất cả ánh xạ đường dẫn cục bộ, buộc trình biên dịch TypeScript coi các gói anh em như dependency bên ngoài, từ đó tạo ra các câu lệnh require chính xác trong dist.
// packages/common-tools/tsconfig.build.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./dist",
    // Quan trọng: Ghi đè và xóa paths, để tham chiếu trỏ đến node_modules
    "paths": {}
  },
  // Loại trừ các tệp kiểm thử
  "exclude": ["**/*.test.ts", "**/*.spec.ts"]
}

Đồng thời, mẫu này khuyến nghị sử dụng pnpm. Ngoài ưu thế về tốc độ, cơ chế liên kết tượng trưng của pnpm nghiêm ngặt hơn cơ chế nâng cấp dependency (hoisting) của npm/yarn, giúp tránh hiệu ứng "dependency ma" (ghost dependencies) - khi một gói có thể truy cập sai vào dependency của gói khác, từ đó đảm bảo môi trường xây dựng sạch sẽ.

2.3 Phân loại không gian làm việc: Triết lý giữa Packages và Apps

Mẫu này định nghĩa rõ ràng hai loại không gian làm việc, ảnh hưởng trực tiếp đến chiến lược xây dựng của chúng:

  • packages/: Đây là mã thư viện. Chúng được thiết kế để xuất bản lên npm hoặc được các ứng dụng khác cài đặt. Khi xây dựng, không nên bundle dependency (không đóng gói dependency), mà nên giữ chúng như dependency bên ngoài (externals). Đầu ra là các module độc lập, có thể xuất bản.
  • apps/

Thẻ: typescript monorepo IDE tsconfig pnpm

Đăng vào ngày 27 tháng 8 lúc 18:20