What is composite project?
TypeScript composite projects provide a way to break down large TypeScript applications into smaller, more manageable projects that can be built incrementally. This feature is crucial for improving build performance and maintaining a well-organized codebase in monorepo setups or large-scale applications.
What are Composite Projects?
A composite project in TypeScript is essentially a project that explicitly declares itself as a 'composite' and can reference other TypeScript projects. This allows the TypeScript compiler (tsc) to understand the dependencies between different parts of your application, enabling faster, incremental builds and better project organization.
The core idea is to break a large codebase into smaller, independent TypeScript projects, each with its own tsconfig.json. These projects can then depend on each other, and tsc will ensure that referenced projects are built before their dependents, only rebuilding what has changed.
Key Features and Benefits
- Improved Build Performance:
tsc --build(ortsc -b) can incrementally build projects. If a referenced project hasn't changed, its output will be reused, significantly speeding up build times. - Better Project Organization: Encourages breaking down large applications into logical modules, making the codebase easier to understand, navigate, and maintain.
- Enhanced Dependency Management: The TypeScript compiler correctly resolves types and dependencies across project boundaries, providing accurate type checking and IDE support.
- Clearer Module Boundaries: Forces explicit dependency declarations, which helps in maintaining well-defined API surfaces between different parts of your application.
- Monorepo Support: Particularly useful in monorepos where multiple packages might depend on each other.
- No
outFile: Note thatcomposite: truecannot be used with theoutFilecompiler option.
How to Configure Composite Projects
To set up composite projects, you need to configure both the referenced projects and the referencing projects.
1. Referenced Projects (`tsconfig.json` of a library or common module)
A project that is intended to be referenced by other projects must have composite: true and declaration: true (or declarationMap: true) set in its tsconfig.json. The declaration option is required because the referencing projects need to know the types declared in the referenced project without rebuilding its source files.
{
"compilerOptions": {
"composite": true, // Mark this project as composite
"declaration": true, // Required for composite projects
"outDir": "./dist",
"rootDir": ".",
"strict": true,
"target": "ES2019",
"module": "commonjs",
"moduleResolution": "node"
},
"include": ["src"]
}
2. Referencing Projects (`tsconfig.json` of an application or another library)
A project that depends on other composite projects will list those dependencies in its references array within its tsconfig.json. Each reference points to the tsconfig.json file of the dependent project.
{
"compilerOptions": {
"outDir": "./dist",
"rootDir": ".",
"strict": true,
"target": "ES2019",
"module": "commonjs",
"moduleResolution": "node"
},
"references": [
{ "path": "../shared-ui" }, // Relative path to a referenced project's tsconfig.json
{ "path": "../utils" }
],
"include": ["src"]
}
Building Composite Projects
To build composite projects, you use the tsc --build (or tsc -b) command. This command is designed to intelligently build the project graph, respecting dependencies and performing incremental builds.
tsc --build
Running tsc --build in the root of your referencing project will:
1. Check if any referenced projects need to be rebuilt.
2. Rebuild any out-of-date referenced projects.
3. Build the current project, leveraging the declaration files of the referenced projects.
You can also specify a target project to build, for example: tsc --build packages/my-app.
Example Scenario
Consider a monorepo structure with three packages:
packages/shared-ui: Contains common UI components (referenced byapp).packages/utils: Contains utility functions (referenced byshared-uiandapp).packages/app: The main application (referencesshared-uiandutils).
In this setup:
- packages/shared-ui and packages/utils would both have composite: true in their tsconfig.json.
- packages/shared-ui would have a reference to ../utils.
- packages/app would have references to ../shared-ui and ../utils.
When you run tsc --build packages/app, TypeScript will first build packages/utils (if needed), then packages/shared-ui (if needed, using utils's declarations), and finally packages/app (using declarations from both shared-ui and utils).