Project References
Project references của TypeScript là tính năng mạnh mẽ giúp quản lý hiệu quả các chương trình TypeScript quy mô lớn. Với tính năng này, developer có thể chia codebase lớn thành các phần nhỏ hơn và làm việc hiệu quả hơn.
Project References của TypeScript là gì
Project references là tính năng được giới thiệu trong TypeScript 3.0, cung cấp cách chia các TypeScript project liên quan thành các phần logic và định nghĩa rõ ràng mối quan hệ phụ thuộc giữa chúng. Nhờ đó có thể cấu trúc và quản lý các application hoặc library quy mô lớn dễ dàng hơn.
Ví dụ, một TypeScript project quy mô lớn kiểu monolith (cả frontend và backend) có thể được chia thành các project độc lập nhỏ hơn, và định nghĩa rõ ràng mối quan hệ phụ thuộc giữa chúng. Dưới đây là một ví dụ cấu trúc TypeScript project quy mô lớn sử dụng project references:
plaintext├── Frontend│ ├── UI│ └── Logic│├── Backend│ ├── API│ └── Database│└── Common
plaintext├── Frontend│ ├── UI│ └── Logic│├── Backend│ ├── API│ └── Database│└── Common
Trong đó, UI, Logic, API, Database, Common là các "project" trong project references. Mối quan hệ phụ thuộc giữa các project có thể được định nghĩa rõ ràng như sau:
- Frontend/UI phụ thuộc vào Frontend/Logic
- Frontend/Logic phụ thuộc vào Common
- Backend/API phụ thuộc vào Backend/Database
- Backend/Database phụ thuộc vào Common
Nhờ việc chia thành các đơn vị project, mỗi project có thể được build độc lập. Và việc build cũng được thực hiện tự động dựa trên mối quan hệ phụ thuộc.
Tại sao cần Project References
Project references mang lại các lợi ích quan trọng sau:
- Cải thiện đáng kể thời gian build
- Tăng cường sự phân tách logic giữa các component
- Thực hiện cách tổ chức code tốt hơn, mới hơn
- Tăng tốc độ type checking và compilation
- Giảm memory usage khi sử dụng editor
- Tăng cường nhóm các phần logic của chương trình
Nhờ các lợi ích này, việc phát triển và bảo trì TypeScript project quy mô lớn trở nên dễ dàng hơn đáng kể.
Khái niệm cơ bản về Project References
Để hiểu và sử dụng hiệu quả project references, cần nắm một số khái niệm cơ bản và tùy chọn cấu hình.
Property references trong tsconfig.json
Trung tâm của project references là property references trong file tsconfig.json. Property này được sử dụng để định nghĩa tham chiếu đến các project khác.
json{"compilerOptions": {// Các compiler option thông thường},"references": [{ "path": "../otherproject" }]}
json{"compilerOptions": {// Các compiler option thông thường},"references": [{ "path": "../otherproject" }]}
Ví dụ này cho thấy project hiện tại đang tham chiếu đến project ở ../otherproject.
Vai trò của option composite
Các project được tham chiếu cần bật option composite. Option này có các hiệu ứng sau:
- Nếu
rootDirkhông được thiết lập rõ ràng, mặc định sẽ là thư mục chứa filetsconfig.json. - Tất cả file implementation phải match với pattern
includehoặc được liệt kê trong mảngfiles. - Phải bật option
declaration.
json{"compilerOptions": {"composite": true,"declaration": true}}
json{"compilerOptions": {"composite": true,"declaration": true}}
Option declarationMap và lợi ích
Khi bật option declarationMap, source map của declaration file (.d.ts) sẽ được tạo ra. Điều này mang lại các lợi ích sau:
- Có thể sử dụng các tính năng của editor như "Go to Definition" hoặc "Rename" một cách trong suốt qua ranh giới project.
- Có thể nắm bắt chi tiết hơn mối quan hệ tương ứng giữa source code và code sau compile.
json{"compilerOptions": {"composite": true,"declaration": true,"declarationMap": true}}
json{"compilerOptions": {"composite": true,"declaration": true,"declarationMap": true}}
Bằng cách hiểu các thiết lập cơ bản này, bạn có thể sử dụng hiệu quả project references và quản lý hiệu quả các TypeScript project quy mô lớn.
Các vấn đề chính mà Project References giải quyết
Project references giải quyết nhiều vấn đề mà developer gặp phải trong các TypeScript project quy mô lớn. Dưới đây là các vấn đề chính và cách giải quyết:
- Thời gian build dài:
- Khi sử dụng project references, chỉ cần recompile các project đã thay đổi, giúp giảm đáng kể thời gian compilation trong project quy mô lớn.
- Độ phức tạp của cấu trúc code:
- Chia codebase lớn thành các phần logic và quản lý mỗi phần như một project độc lập, giúp cấu trúc tổng thể rõ ràng và dễ quản lý hơn.
- Quản lý dependency:
- Có thể định nghĩa rõ ràng dependency giữa các project trong file
tsconfig.json, làm rõ cấu trúc và mối quan hệ của toàn bộ codebase.
- Có thể định nghĩa rõ ràng dependency giữa các project trong file
- Recompilation không cần thiết:
- Có thể tránh recompilation các phần không thay đổi, giúp tăng đáng kể hiệu quả của build process.
- Type checking chậm:
- Chia project thành các phần nhỏ giúp tăng tốc type checking của mỗi phần, cải thiện performance tổng thể.
- Tham chiếu không phù hợp giữa các module:
- Định nghĩa rõ ràng ranh giới project giúp ngăn chặn tham chiếu không mong muốn giữa các module, cải thiện cấu trúc code.
- Độ phức tạp của build process:
- Sử dụng mode
tsc --buildgiúp giảm nhu cầu về nhiều config file hoặc build script phức tạp, đơn giản hóa build process.
- Sử dụng mode
Bằng cách giải quyết các vấn đề này, project references giúp cải thiện đáng kể hiệu quả phát triển và khả năng bảo trì của các TypeScript project quy mô lớn.
Lợi ích của Project References
Khi áp dụng project references, developer có thể hưởng nhiều lợi ích. Các lợi ích chính như sau:
Hiệu quả về Memory
Project references rất hiệu quả về memory so với phương pháp truyền thống:
- Giảm số lượng process:
- Phương pháp truyền thống: Khi chạy
tsc --watchcho mỗi package, cần 1 process cho mỗi package. - Project references: 1 process có thể cover tất cả các package.
- Phương pháp truyền thống: Khi chạy
- So sánh memory usage:
- Phương pháp truyền thống (
tsc --watch): 120MB~200MB mỗi process - Project references: Khoảng 70MB (không thay đổi nhiều theo số lượng package)
- Phương pháp truyền thống (
- So sánh với 10 project:
- Phương pháp truyền thống: 1.2GB~2.0GB
- Project references: Khoảng 70MB
Việc giảm memory usage đáng kể này cho phép sử dụng tài nguyên máy phát triển cho các task khác.
Cải thiện Developer Experience (DX)
Việc áp dụng project references mang lại những cải thiện về developer experience sau:
- Feedback loop nhanh:
- Rút ngắn thời gian build giúp nhận được feedback nhanh chóng sau khi thay đổi code.
- Có thể build từng phần, kiểm tra nhanh chỉ phần liên quan đến nơi đang làm việc.
- Cải thiện performance của IDE:
- Chia nhỏ project giúp tăng tốc type checking của mỗi phần, cải thiện response của IDE.
- Giảm memory usage giúp IDE hoạt động mượt mà ngay cả với project quy mô lớn.
- Cải thiện code navigation:
- Tính năng
declarationMapgiúp jump giữa các project (như Go to Definition) mượt mà hơn.
- Tính năng
- Làm rõ ranh giới module:
- Dependency giữa các project trở nên rõ ràng, giúp dễ hiểu architecture.
- Phát hiện sớm dependency không phù hợp, giúp duy trì chất lượng code dễ dàng hơn.
- Môi trường phát triển linh hoạt:
- Có thể làm việc chỉ với một phần của project quy mô lớn, developer có thể tập trung vào phần cần thiết.
- Đơn giản hóa build process:
- Mode
tsc --buildgiúp không cần build script phức tạp.
- Mode
- Cải thiện phát hiện lỗi:
- Phát hiện nhanh và chính xác hơn sự không khớp về type giữa các project, hoặc dependency không phù hợp.
- Cải thiện collaboration:
- Chia project rõ ràng giúp phân công công việc và phạm vi trách nhiệm giữa các team rõ ràng hơn.
Nhờ các lợi ích này, developer có thể làm việc hiệu quả hơn, ít stress hơn, cải thiện chất lượng code và tăng tốc độ phát triển.
So sánh phương pháp truyền thống và Project References
Bảng sau so sánh sự khác biệt chính giữa quản lý project kiểu monolith truyền thống và sử dụng project references:
| Đặc điểm | Phương pháp Monolith | Project References |
|---|---|---|
| Thời gian build | Build toàn bộ mỗi lần | Chỉ build phần thay đổi |
| Quản lý dependency | Ngầm định | Rõ ràng |
| Tham chiếu giữa module | Không hạn chế | Hạn chế theo ranh giới project |
| Memory usage | Cao | Thấp |
| Performance IDE | Giảm với project lớn | Tương đối ổn định |
| Partial build | Không thể | Dễ dàng |
| Config file | Đơn hoặc phức tạp | Nhiều config rõ ràng |
Từ so sánh này, có thể thấy project references rất hiệu quả cho quản lý project quy mô lớn.
Triển khai Project References
Sau khi hiểu khái niệm cơ bản về project references, hãy xem cách triển khai trong các tình huống phức tạp hơn. Ở đây sẽ giải thích triển khai project references trong cấu hình monorepo.
Monorepo là gì
Monorepo là phương pháp phát triển quản lý nhiều project hoặc package liên quan trong một repository duy nhất. Phương pháp này được ưa chuộng trong phát triển project quy mô lớn hoặc library có nhiều package vì dễ dàng chia sẻ code, quản lý dependency và điều phối release.
Thiết kế cấu trúc Project
Cấu trúc monorepo điển hình như sau:
plaintext.├── package.json├── packages (nơi đặt code chương trình)│ ├── cli│ ├── common│ └── web├── tsconfig.base.json (file mô tả cấu hình compile chung cho tất cả package)├── tsconfig.json (file mô tả cấu hình project references)
plaintext.├── package.json├── packages (nơi đặt code chương trình)│ ├── cli│ ├── common│ └── web├── tsconfig.base.json (file mô tả cấu hình compile chung cho tất cả package)├── tsconfig.json (file mô tả cấu hình project references)
Trong ví dụ này, có 3 package (cli, common, web) trong thư mục packages. Các package này có mối quan hệ phụ thuộc như sau:
- Package
cliphụ thuộc vào packagecommon - Package
webphụ thuộc vào packagecommon
Đây là cấu hình implement các tính năng chung trong package common, và sử dụng chúng trong cli (CLI application) và web (web application).
Cấu hình file tsconfig.json
Khi sử dụng project references trong monorepo, cần cấu hình phù hợp nhiều file tsconfig.json.
tsconfig.json ở root
tsconfig.json ở thư mục root đóng vai trò như "mục lục" của toàn bộ project:
json{"include": [],"references": [{"path": "packages/cli"},{"path": "packages/common"},{"path": "packages/web"}]}
json{"include": [],"references": [{"path": "packages/cli"},{"path": "packages/common"},{"path": "packages/web"}]}
Cấu hình này giúp TypeScript compiler nhận biết từng package trong monorepo và build theo thứ tự phù hợp.
tsconfig.base.json chung
Tập hợp cấu hình chung vào tsconfig.base.json giúp đơn giản hóa cấu hình của mỗi package:
json{"compilerOptions": {"module": "Preserve","moduleResolution": "Bundler","target": "ESNext","declaration": true,"composite": true,"strict": true,"esModuleInterop": true,"rootDir": "${configDir}/src","outDir": "${configDir}/dist"}}
json{"compilerOptions": {"module": "Preserve","moduleResolution": "Bundler","target": "ESNext","declaration": true,"composite": true,"strict": true,"esModuleInterop": true,"rootDir": "${configDir}/src","outDir": "${configDir}/dist"}}
Điểm quan trọng ở đây là cấu hình composite: true. Nhờ đó mỗi package được nhận biết như một "project" trong project references.
tsconfig.json của mỗi package
tsconfig.json của mỗi package kế thừa cấu hình chung và thêm tham chiếu khi cần:
json{"extends": "../../tsconfig.base.json","references": [{"path": "../common"}]}
json{"extends": "../../tsconfig.base.json","references": [{"path": "../common"}]}
Ví dụ này là cấu hình của package cli hoặc web, bao gồm tham chiếu đến package common.
Sử dụng option composite và declarationMap
Option composite là cấu hình bắt buộc trong project references. Nhờ đó project có thể được tham chiếu từ các project khác.
Option declarationMap dùng để bao gồm source map trong file .d.ts. Nhờ đó cải thiện navigation giữa các project:
json{"compilerOptions": {"composite": true,"declaration": true,"declarationMap": true}}
json{"compilerOptions": {"composite": true,"declaration": true,"declarationMap": true}}
Cấu hình tham chiếu giữa các Project
Tham chiếu giữa các project được cấu hình trong section references của tsconfig.json:
json{"references": [{"path": "../common"}]}
json{"references": [{"path": "../common"}]}
Ngoài ra, cần khai báo rõ ràng dependency trong package.json:
json{"dependencies": {"@company/common": "workspace:^"}}
json{"dependencies": {"@company/common": "workspace:^"}}
Cấu hình này giúp cả TypeScript compiler và package manager đều hiểu đúng dependency giữa các project.
Sử dụng Build Mode
Để sử dụng hiệu quả project references, việc hiểu build mode của TypeScript compiler là quan trọng.
Tổng quan về lệnh tsc --build
Lệnh tsc --build (hoặc tsc -b) build các project cần thiết theo đúng thứ tự, có xét đến project references.
Cách sử dụng cơ bản:
bashtsc -b # Sử dụng tsconfig.json ở thư mục hiện tạitsc -b src # Sử dụng src/tsconfig.jsontsc -b foo/tsconfig.json bar # Build nhiều project
bashtsc -b # Sử dụng tsconfig.json ở thư mục hiện tạitsc -b src # Sử dụng src/tsconfig.jsontsc -b foo/tsconfig.json bar # Build nhiều project
Incremental Build và Cleaning
tsc --build hỗ trợ incremental build, chỉ rebuild các file đã thay đổi và các dependency của chúng. Nhờ đó có thể giảm đáng kể thời gian build trong project quy mô lớn.
Để thực hiện clean build, sử dụng flag --clean:
bashtsc -b --clean
bashtsc -b --clean
Lệnh này xóa output của build, và ở lần build tiếp theo sẽ recompile tất cả các file.
Sử dụng Watch Mode
Watch mode theo dõi thay đổi của file và tự động rebuild:
bashtsc -b --watch
bashtsc -b --watch
Điều này rất tiện lợi trong quá trình phát triển, không cần thực thi build thủ công mỗi khi thay đổi code.
Công cụ quản lý Project References
Trong project quy mô lớn, việc quản lý project references có thể trở nên phức tạp. Vì vậy một số công cụ quản lý đã được phát triển:
Lưu ý và Trade-off
Khi áp dụng project references, cần lưu ý các điểm sau:
- Độ phức tạp của initial setup: Thiết lập ban đầu project references phức tạp hơn so với sử dụng một
tsconfig.jsonduy nhất. - Quản lý build output: Cần quản lý phù hợp output directory của mỗi project.
- Tương thích với build workflow hiện có: Có thể cần cập nhật build script hoặc CI/CD pipeline hiện có.
- Learning curve: Các thành viên trong team cần hiểu khái niệm và cách sử dụng project references.
Các vấn đề này thường được bù đắp bởi lợi ích thu được khi quy mô project tăng lên.
Project references là tính năng mạnh mẽ cải thiện đáng kể việc quản lý TypeScript project quy mô lớn. Triển khai phù hợp có thể cải thiện hiệu quả phát triển, rút ngắn thời gian build và tổ chức codebase tốt hơn. Chương tiếp theo sẽ xem các ví dụ ứng dụng cụ thể của project references.
Ứng dụng Project References: Monorepo
Giới thiệu
Tổng quan
Tutorial này hướng dẫn từng bước cách xây dựng monorepo sử dụng tính năng project references của TypeScript.
Mục đích
Mục đích của hướng dẫn này là xây dựng cấu trúc monorepo bao gồm các yếu tố sau:
- Project
commonchứa code chung - Project
clisử dụng projectcommon - Project
websử dụng projectcommon
Các project này được liên kết với nhau thông qua tính năng project references của TypeScript.
Cấu trúc Project cuối cùng
Khi hoàn thành tutorial, project sẽ có cấu trúc như sau:
plaintexttypescript-monorepo-example/ (workspace root)├── package.json├── tsconfig.json├── tsconfig.base.json├── .yarnrc.yml└── packages/├── common/ (project)│ ├── package.json│ ├── tsconfig.json│ └── src/│ └── index.ts├── cli/ (project)│ ├── package.json│ ├── tsconfig.json│ └── src/│ └── index.ts└── web/ (project)├── package.json├── tsconfig.json└── src/└── index.ts
plaintexttypescript-monorepo-example/ (workspace root)├── package.json├── tsconfig.json├── tsconfig.base.json├── .yarnrc.yml└── packages/├── common/ (project)│ ├── package.json│ ├── tsconfig.json│ └── src/│ └── index.ts├── cli/ (project)│ ├── package.json│ ├── tsconfig.json│ └── src/│ └── index.ts└── web/ (project)├── package.json├── tsconfig.json└── src/└── index.ts
Cấu trúc này cho phép chia sẻ code hiệu quả, type checking và build nhanh.
Code hoàn chỉnh
Code hoàn chỉnh của tutorial này có thể xem tại GitHub repository:
https://github.com/yytypescript/project-reference-samples/tree/main/02-monorepo
Giải thích thuật ngữ
Workspace
Chỉ toàn bộ project bao gồm nhiều project, được quản lý bằng tính năng workspace của Yarn.
Workspace Root
Chỉ thư mục cấp cao nhất của workspace. Trong ví dụ này, thư mục typescript-monorepo-example/ là workspace root.
Project
Chỉ đơn vị compilation của TypeScript. Trong ví dụ này, các thư mục common, cli, web là các project độc lập.
Project Root
Chỉ thư mục cấp cao nhất của mỗi project. Ví dụ, thư mục packages/common/ là project root của project common.
Yêu cầu
Trước khi bắt đầu tutorial này, hãy đảm bảo các công cụ sau đã được cài đặt:
- Node.js (phiên bản LTS mới nhất)
- Yarn (phiên bản 4.4.0 trở lên)
Ngoài ra, giả định bạn đã có kiến thức cơ bản về TypeScript.
ワークスペースの初期化
ディレクトリの作成
まず、新しいワークスペースのためのディレクトリを作成します。ターミナルを開き、次のコマンドを実行してください:
bashmkdir typescript-monorepo-examplecd typescript-monorepo-example
bashmkdir typescript-monorepo-examplecd typescript-monorepo-example
package.json の作成
次に、ワークスペースルートに package.json ファイルを直接作成します。次の内容で package.json ファイルを作成してください:
json{"name": "typescript-monorepo-example","private": true,"workspaces": ["packages/*"],"devDependencies": {"typescript": "^5.5.4"}}
json{"name": "typescript-monorepo-example","private": true,"workspaces": ["packages/*"],"devDependencies": {"typescript": "^5.5.4"}}
ここで重要なポイントは次の通りです:
"private": true: このフィールドは、このパッケージが誤って公開されることを防ぎます。モノレポのルートパッケージは通常公開されないため、このフラグを設定します。"workspaces": ["packages/*"]: このフィールドは、Yarnにワークスペースの場所を指示します。この設定により、packagesディレクトリ内のすべてのサブディレクトリがワークスペースのプロジェクトとして認識されます。
.yarnrc.yml の作成
続いて、Yarnの設定ファイル .yarnrc.yml をワークスペースルートに作成し、次の内容を追加します:
yamlnodeLinker: node-modules
yamlnodeLinker: node-modules
この設定により、Yarnがnode_modulesディレクトリを使用するようになります。
共通の TypeScript 設定
tsconfig.base.json の作成
TypeScriptの共通設定を作成します。ワークスペースルートに tsconfig.base.json ファイルを作成し、次の内容を追加してください:
json{"compilerOptions": {"module": "Preserve","moduleResolution": "Bundler","target": "ESNext","declaration": true,"composite": true,"strict": true,"esModuleInterop": true,"rootDir": "${configDir}/src","outDir": "${configDir}/dist"}}
json{"compilerOptions": {"module": "Preserve","moduleResolution": "Bundler","target": "ESNext","declaration": true,"composite": true,"strict": true,"esModuleInterop": true,"rootDir": "${configDir}/src","outDir": "${configDir}/dist"}}
tsconfig.base.json の役割は、すべてのプロジェクトで共有される基本的なTypeScript設定を提供することです。この設定の中で、プロジェクト参照において特に重要なポイントは次の通りです:
"composite": true: このオプションは、プロジェクト参照を使用する際に必須です。これにより、プロジェクトが他のプロジェクトから参照可能になります。"declaration": true: 宣言ファイル(.d.ts)を生成します。これは"composite": trueを使用する際に必要です。"rootDir"と"outDir": これらのオプションは、ソースファイルと出力ファイルの場所を指定します。プロジェクト参照を使用する際、これらの設定は一貫性を保つために重要です。
ワークスペースルートの tsconfig.json の作成
次に、ワークスペースルートに tsconfig.json ファイルを作成し、次の内容を追加します:
json{"include": [],"references": [{"path": "packages/common"},{"path": "packages/cli"},{"path": "packages/web"}]}
json{"include": [],"references": [{"path": "packages/common"},{"path": "packages/cli"},{"path": "packages/web"}]}
このワークスペースルートの tsconfig.json は、TypeScriptコンパイラーに向けた「目次」または「地図」のような役割を果たします。このファイルの "references" フィールドには、ワークスペース内のすべてのプロジェクトが列挙されており、これによってTypeScriptコンパイラーはワークスペース全体の構造を把握できます。ここでのプロジェクトの列挙順序は重要ではなく、TypeScriptコンパイラーが自動的に依存関係を解析し、適切なビルド順序を決定します。
このファイルには compilerOptions を含めていないことに注目してください。各プロジェクト固有の設定は、それぞれのプロジェクトの tsconfig.json で行います。このファイルの主な目的は、ワークスペース全体を一度にビルドできるようにすることです。ワークスペースルートで tsc -b を実行するだけで、すべてのプロジェクトを適切な順序でビルドできるようになります。
実際には、このファイルがなくても各プロジェクト(例:packages/web)で tsc -b を実行すれば、依存関係を解決しながらビルドすることは可能です。しかし、このファイルを用意することで、ワークスペース全体のビルドが簡単になります。このファイルは、大規模なプロジェクトの管理を容易にし、ビルドプロセスを効率化するための重要なツールです。コンパイルの制御ではなく、プロジェクト構造の定義に焦点を当てていることが特徴です。
プロジェクトの設定
プロジェクトの基本構造の作成
まず、プロジェクトの基本構造を作成します。packages ディレクトリを作成し、その中に3つのサブディレクトリ(common、cli、web)を作成します。次のコマンドを実行してください:
bashmkdir -p packages/{common,cli,web}
bashmkdir -p packages/{common,cli,web}
common プロジェクトの設定
まず、common プロジェクトの設定を行います。packages/common/package.json ファイルを作成し、次の内容を追加してください:
json{"name": "@company/common","type": "module","exports": "./dist/index.js"}
json{"name": "@company/common","type": "module","exports": "./dist/index.js"}
ここで、"exports" フィールドは、このパッケージが外部に公開するエントリーポイントを指定します。これにより、他のプロジェクトがこのパッケージをインポートする際の参照先が明確になります。
次に、packages/common/tsconfig.json ファイルを作成し、次の内容を追加します:
json{"extends": "../../tsconfig.base.json"}
json{"extends": "../../tsconfig.base.json"}
"extends" フィールドは、別の TSConfig ファイルから設定を継承することを指定します。この場合、ワークスペースルートの tsconfig.base.json から設定を継承しています。
最後に、packages/common/src/index.ts ファイルを作成し、次の内容を追加します:
packages/common/src/index.tstypescriptexport function helloWorld(): string {return "Hello World";}
packages/common/src/index.tstypescriptexport function helloWorld(): string {return "Hello World";}
cli プロジェクトの設定
次に、cli プロジェクトの設定を行います。packages/cli/package.json ファイルを作成し、次の内容を追加してください:
json{"name": "@company/cli","type": "module","dependencies": {"@company/common": "workspace:^"}}
json{"name": "@company/cli","type": "module","dependencies": {"@company/common": "workspace:^"}}
"dependencies" フィールドは、このプロジェクトが依存する他のパッケージを指定します。ここでは、@company/common パッケージへの依存を宣言しています。"workspace:^" は、このパッケージがワークスペース内の別のプロジェクトであることを示しています。
続いて、packages/cli/tsconfig.json ファイルを作成し、次の内容を追加します:
json{"extends": "../../tsconfig.base.json","references": [{"path": "../common"}]}
json{"extends": "../../tsconfig.base.json","references": [{"path": "../common"}]}
"references" フィールドは、このプロジェクトが参照する他のプロジェクトを指定します。これにより、TypeScriptコンパイラはプロジェクト間の依存関係を理解し、適切な順序でビルドを行うことができます。
最後に、packages/cli/src/index.ts ファイルを作成し、次の内容を追加します:
packages/cli/src/index.tstypescriptimport { helloWorld } from "@company/common";console.log(helloWorld());
packages/cli/src/index.tstypescriptimport { helloWorld } from "@company/common";console.log(helloWorld());
web プロジェクトの設定
最後に、web プロジェクトの設定を行います。packages/web/package.json ファイルを作成し、次の内容を追加してください:
json{"name": "@company/web","type": "module","dependencies": {"@company/common": "workspace:^"}}
json{"name": "@company/web","type": "module","dependencies": {"@company/common": "workspace:^"}}
次に、packages/web/tsconfig.json ファイルを作成し、次の内容を追加します:
json{"extends": "../../tsconfig.base.json","references": [{"path": "../common"}]}
json{"extends": "../../tsconfig.base.json","references": [{"path": "../common"}]}
最後に、packages/web/src/index.ts ファイルを作成し、次の内容を追加します:
packages/web/src/index.tstypescriptimport { helloWorld } from "@company/common";console.log(helloWorld());
packages/web/src/index.tstypescriptimport { helloWorld } from "@company/common";console.log(helloWorld());
依存関係のインストール
すべてのプロジェクトの設定が完了したら、依存関係をインストールします。ワークスペースルートで次のコマンドを実行してください:
bashyarn install
bashyarn install
プロジェクトのビルド
これで、ワークスペース全体をビルドする準備が整いました。次のコマンドをワークスペースルートで実行してプロジェクトをビルドしてください:
bashyarn tsc -b
bashyarn tsc -b
このコマンドをワークスペースルートで実行することが重要です。なぜなら、ワークスペースルートの tsconfig.json がプロジェクト参照の構造を定義しているからです。
このコマンドを実行すると、次のような処理が行われます:
- TypeScriptコンパイラ(tsc)は、まずワークスペースルートの
tsconfig.jsonを読み込みます。このファイルには"references"フィールドがあり、ここに列挙されているプロジェクトがビルドの対象となります。 "references"フィールドに記載されている順序は重要ではありません。TypeScriptコンパイラは、これらの参照を解析し、プロジェクト間の依存関係を自動的に判断します。- 依存関係グラフに基づいて、プロジェクトを適切な順序でビルドします。この例では、次の順序でビルドが行われます:
- まず
commonプロジェクト(他のプロジェクトに依存していないため) - 次に
cliとwebプロジェクト(両方ともcommonに依存しているため)
- まず
- 各プロジェクトのビルド時には、そのプロジェクトの
tsconfig.jsonが使用されます。これらの設定ファイルはtsconfig.base.jsonを継承しているため、共通の設定が適用されます。 - 各プロジェクトのソースファイルがコンパイルされ、指定された出力ディレクトリ(
dist)に JavaScript ファイルと型定義ファイル(.d.ts)が生成されます。 - プロジェクト参照の情報を含む
.tsbuildinfoファイルが各プロジェクトに生成されます。これにより、次回のビルド時に変更されたファイルのみを再コンパイルすることができ、ビルド時間が短縮されます。
このプロセスにより、プロジェクト間の依存関係が正しく解決され、必要な順序でビルドが行われます。また、変更があったプロジェクトとその依存先のみが再ビルドされるため、大規模なプロジェクトでも効率的なビルドが可能になります。
実行
ビルドが成功したら、作成したプロジェクトを実行してみましょう。まず、CLI プロジェクトを実行します:
bashnode packages/cli/dist/index.js
bashnode packages/cli/dist/index.js
次に、Web プロジェクトを実行します:
bashnode packages/web/dist/index.js
bashnode packages/web/dist/index.js
両方のコマンドで "Hello World" が出力されれば、セットアップは成功です。
まとめ
このチュートリアルでは、TypeScriptのプロジェクト参照機能を使用してモノレポを構築する方法を学びました。この構造により、大規模なTypeScriptプロジェクトを効率的に管理し、ビルド時間を短縮し、コードの再利用性を高めることができます。プロジェクト参照を活用することで、ビルド時間の短縮、型チェックの効率化、依存関係の明確化など、多くの利点を得ることができます。
プロジェクト参照の活用: ソースとテストの分離
はじめに
概要
このチュートリアルでは、TypeScriptのプロジェクト参照機能を使用して、ソースコードとテストコードを分離した構造を持つプロジェクトを構築する方法を、ステップバイステップで説明します。
目的
このガイドの目的は、次の要素を含むプロジェクト構造を構築することです:
- メインのソースコードを含む
srcディレクトリ - テストコードを含む
srcディレクトリ内の.test.tsファイル - ソースコードとテストコードを分離しつつ、プロジェクト参照を使用して関連つける
この構造により、ビルドの最適化と依存関係の明確化を実現します。依存関係の明確化は、次のような利点をもたらします:
- コードの構造が明確になり、開発者が全体像を把握しやすくなります。
- 不適切な依存関係(例:ソースコードがテストコードに依存する)を防ぎ、コードの品質を向上させます。
- ビルドプロセスの効率化につながり、大規模プロジェクトでのビルド時間を短縮します。
最終的なプロジェクト構造
チュートリアルの完了時、次のような構造のプロジェクトが完成します:
plaintexttypescript-source-test-separation/├── package.json├── tsconfig.json├── tsconfig.src.json├── tsconfig.test.json├── .yarnrc.yml└── src/├── hello-world.ts└── hello-world.test.ts
plaintexttypescript-source-test-separation/├── package.json├── tsconfig.json├── tsconfig.src.json├── tsconfig.test.json├── .yarnrc.yml└── src/├── hello-world.ts└── hello-world.test.ts
この構造により、効率的なコード管理と高速なビルドが可能になります。
完成形のコード
このチュートリアルの完成形のコードは、次のGitHubリポジトリで確認できます:
https://github.com/yytypescript/project-reference-samples/tree/main/01-source-test-separation
前提条件
このチュートリアルをはじめる前に、次のツールがインストールされていることを確認してください:
- Node.js(最新の LTS バージョン)
- Yarn(バージョン 4.4.0 以上)
また、基本的な TypeScript の知識があることを前提としています。
プロジェクトの初期化
まず、新しいプロジェクトのためのディレクトリを作成します。ターミナルを開き、次のコマンドを実行してください:
bashmkdir typescript-source-test-separationcd typescript-source-test-separation
bashmkdir typescript-source-test-separationcd typescript-source-test-separation
次に、package.json ファイルを手動で作成します。次の内容を package.json ファイルに追加してください:
json{"name": "typescript-source-test-separation","private": true,"devDependencies": {"@types/node": "^22.3.0","typescript": "^5.5.4","vitest": "^2.0.5"}}
json{"name": "typescript-source-test-separation","private": true,"devDependencies": {"@types/node": "^22.3.0","typescript": "^5.5.4","vitest": "^2.0.5"}}
続いて、.yarnrc.yml ファイルを作成し、次の内容を追加します:
yamlnodeLinker: node-modules
yamlnodeLinker: node-modules
この設定により、Yarnがnode_modulesディレクトリを使用するようになります。
最後に、依存関係をインストールします:
bashyarn install
bashyarn install
TypeScript設定ファイルの作成
ルートの tsconfig.json
プロジェクトのルートディレクトリに tsconfig.json ファイルを作成し、次の内容を追加します:
json{"files": [],"references": [{"path": "tsconfig.src.json"},{"path": "tsconfig.test.json"}]}
json{"files": [],"references": [{"path": "tsconfig.src.json"},{"path": "tsconfig.test.json"}]}
このファイルは、TypeScriptコンパイラに向けた「目次」または「地図」のような役割を果たします。"references" フィールドには、このプロジェクトが参照する他のTSConfigファイルを指定します。これにより、TypeScriptコンパイラはプロジェクト間の依存関係を理解し、適切な順序でビルドを行うことができます。
"files" フィールドを空に設定することで、このファイル自体はコンパイル対象にならず、純粋にプロジェクト参照の設定のみを行います。これにより、プロジェクト全体の構造を定義しつつ、実際のコンパイルは各サブプロジェクトの設定に委ねることができます。
ソースコード用の tsconfig.src.json
次に、ソースコード用の tsconfig.src.json ファイルを作成し、次の内容を追加します:
json{"compilerOptions": {"composite": true,"rootDir": "src","outDir": "dist"},"include": ["**/*.ts"],"exclude": ["**/*.test.ts"]}
json{"compilerOptions": {"composite": true,"rootDir": "src","outDir": "dist"},"include": ["**/*.ts"],"exclude": ["**/*.test.ts"]}
このファイルでは、次の設定が特に重要です:
"composite": true: このオプションは、プロジェクト参照を使用する際に必須です。これにより、このプロジェクトが他のプロジェクトから参照可能になります。"rootDir": ソースファイルのルートディレクトリを指定します。"outDir": コンパイル後のファイルの出力先を指定します。
その他の設定項目:
"include": コンパイル対象のファイルを指定します。"exclude": コンパイルから除外するファイルを指定します。ここではテストファイルを除外しています。
テストコード用の tsconfig.test.json
最後に、テストコード用の tsconfig.test.json ファイルを作成し、次の内容を追加します:
json{"compilerOptions": {"composite": true,"rootDir": "src","noEmit": true,"skipLibCheck": true},"references": [{"path": "tsconfig.src.json"}]}
json{"compilerOptions": {"composite": true,"rootDir": "src","noEmit": true,"skipLibCheck": true},"references": [{"path": "tsconfig.src.json"}]}
このファイルでは、次の設定が特に重要です:
"composite": true: ソースコードの設定と同様、プロジェクト参照を可能にします。"references": ソースコードのプロジェクトへの参照を定義します。これにより、テストコードからソースコードへの依存関係が明確になります。"noEmit": true: この設定により、テストコードのトランスパイル(JavaScriptへの変換)は行われませんが、型チェックは実行されます。これは、テストコードは直接実行されるため、トランスパイルする必要がない一方で、型の整合性は確認したいという要求に応えるものです。
その他の設定項目:
"skipLibCheck": true: 宣言ファイルの型チェックをスキップし、ビルド時間を短縮します。
この構成により、ソースコードからテストコードへの依存が不可能になります。これは、本番コードにテストコードが混入することを防ぎ、コードの品質と保守性を向上させる重要な利点です。
ソースコードとテストコードの作成
ソースコードの作成
src ディレクトリを作成し、その中に hello-world.ts ファイルを作成します:
typescriptexport function helloWorld(): string {return "Hello World";}
typescriptexport function helloWorld(): string {return "Hello World";}
テストコードの作成
同じく src ディレクトリ内に、hello-world.test.ts ファイルを作成します:
typescriptimport { expect, test } from "vitest";import { helloWorld } from "./hello-world";test("helloWorld function", () => {expect(helloWorld()).toBe("Hello World");});
typescriptimport { expect, test } from "vitest";import { helloWorld } from "./hello-world";test("helloWorld function", () => {expect(helloWorld()).toBe("Hello World");});
プロジェクトのビルドとテスト
これで、プロジェクトをビルドし、テストを実行する準備が整いました。
ソースコードのみをコンパイル
ソースコードのみをコンパイルするには、次のコマンドを実行します:
bashyarn tsc -b tsconfig.src.json
bashyarn tsc -b tsconfig.src.json
このコマンドは tsconfig.src.json で定義されたソースコードのみをコンパイルします。テストコードは除外されます。
ソースコードとテストコードの両方をコンパイル
ソースコードとテストコードの両方をコンパイルするには、プロジェクトのルートで次のコマンドを実行します:
bashyarn tsc -b
bashyarn tsc -b
このコマンドを実行すると、次のような処理が内部的に行われます:
- TypeScriptコンパイラは、まずルートの
tsconfig.jsonを読み込みます。 - 依存関係グラフの構築:
tsconfig.jsonのreferencesフィールドに基づいて、tsconfig.src.jsonとtsconfig.test.jsonが参照されます。- さらに、
tsconfig.test.jsonのreferencesフィールドにより、tsconfig.src.jsonへの依存関係が認識されます。 - これにより、
src→testという依存関係グラフが構築されます。
- コンパイルの実行:
- 依存関係グラフに基づいて、プロジェクトを適切な順序で処理します。
- まず
tsconfig.src.jsonが処理されます。これにより、ソースコードがトランスパイルされ、出力ファイルが生成されます。 - 次に
tsconfig.test.jsonが処理されます。この際、tsconfig.src.jsonの出力を参照します。 tsconfig.test.jsonの"noEmit": true設定により、テストコードのトランスパイルは行われませんが、型チェックは実行されます。
この過程により、ソースコードのトランスパイルとテストコードの型チェックが一度の操作で行われます。これにより、ソースコードとテストコード間の型の整合性が保証され、かつテストコードが不要にトランスパイルされることを防ぎます。
テストの実行
テストを実行するには、次のコマンドを使用します:
bashyarn vitest
bashyarn vitest
このコマンドは、Vitestを使用してテストを実行します。
プロジェクト参照の利点
このプロジェクト構造には、次のような利点があります:
- ビルドの最適化: ソースコードとテストコードが分離されているため、本番用のビルドにテストコードが含まれません。
- 依存関係の明確化: テストプロジェクトがソースプロジェクトに依存する構造により、不適切な依存関係を防ぎます。特に、ソースコードからテストコードへの依存が不可能になるため、本番コードの品質が向上します。
- 高速なフィードバック: 部分的なビルドが可能になり、変更された部分のみを素早くチェックできます。
- IDE パフォーマンスの向上: プロジェクトの分割により、各部分の型チェックが高速化され、IDEのレスポンスが向上します。
まとめ
このチュートリアルでは、TypeScriptのプロジェクト参照機能を使用して、ソースコードとテストコードを分離した構造を持つプロジェクトを構築する方法を学びました。この構造により、大規模なTypeScriptプロジェクトを効率的に管理し、ビルド時間を短縮し、コードの品質を向上させることができます。プロジェクト参照を活用することで、開発プロセスの効率化と保守性の向上を実現できます。
おわりに
TypeScriptのプロジェクト参照機能は、大規模プロジェクトの管理を効率化する強力なツールです。主な利点には、ビルド時間の短縮、コード構造の改善、型チェックの効率化があります。この機能を使うと、大きなコードベースを論理的に分割し、モジュール間の依存関係を明確にできます。プロジェクト参照の導入には学習が必要ですが、プロジェクトの規模が大きくなるほど、その価値は増大します。大規模なTypeScriptプロジェクトの開発者にとって、この機能の習得は生産性と品質を向上させる重要な投資となるでしょう。