Project References
Project references allows you to structure your TypeScript programs into smaller pieces, available in TypeScript 3.0 and newer.
By doing this, you can greatly improve build times, enforce logical separation between components, and organize your code in new and better ways.
We’re also introducing a new mode fortsc, the--build flag, that works hand in hand with project references to enable faster TypeScript builds.
An Example Project
Let’s look at a fairly normal program and see how project references can help us better organize it.Imagine you have a project with two modules,converter andunits, and a corresponding test file for each:
/├── src/│ ├── converter.ts│ └── units.ts├── test/│ ├── converter-tests.ts│ └── units-tests.ts└── tsconfig.json
The test files import the implementation files and do some testing:
ts// converter-tests.tsimport*asconverterfrom"../src/converter";assert.areEqual(converter.celsiusToFahrenheit(0),32);
Previously, this structure was rather awkward to work with if you used a single tsconfig file:
- It was possible for the implementation files to import the test files
- It wasn’t possible to build
testandsrcat the same time without havingsrcappear in the output folder name, which you probably don’t want - Changing just theinternals in the implementation files requiredtypechecking the tests again, even though this wouldn’t ever cause new errors
- Changing just the tests required typechecking the implementation again, even if nothing changed
You could use multiple tsconfig files to solvesome of those problems, but new ones would appear:
- There’s no built-in up-to-date checking, so you end up always running
tsctwice - Invoking
tsctwice incurs more startup time overhead tsc -wcan’t run on multiple config files at once
Project references can solve all of these problems and more.
What is a Project Reference?
tsconfig.json files have a new top-level property,references. It’s an array of objects that specifies projects to reference:
js{"compilerOptions": {// The usual},"references": [{"path":"../src" }]}
Thepath property of each reference can point to a directory containing atsconfig.json file, or to the config file itself (which may have any name).
When you reference a project, new things happen:
- Importing modules from a referenced project will instead load itsoutput declaration file (
.d.ts) - If the referenced project produces an
outFile, the output file.d.tsfile’s declarations will be visible in this project - Build mode (see below) will automatically build the referenced project if needed
By separating into multiple projects, you can greatly improve the speed of typechecking and compiling, reduce memory usage when using an editor, and improve enforcement of the logical groupings of your program.
composite
Referenced projects must have the newcomposite setting enabled.This setting is needed to ensure TypeScript can quickly determine where to find the outputs of the referenced project.Enabling thecomposite flag changes a few things:
- The
rootDirsetting, if not explicitly set, defaults to the directory containing thetsconfigfile - All implementation files must be matched by an
includepattern or listed in thefilesarray. If this constraint is violated,tscwill inform you which files weren’t specified declarationmust be turned on
declarationMap
We’ve also added support fordeclaration source maps.If you enabledeclarationMap, you’ll be able to use editor features like “Go to Definition” and Rename to transparently navigate and edit code across project boundaries in supported editors.
Caveats for Project References
Project references have a few trade-offs you should be aware of.
Because dependent projects make use of.d.ts files that are built from their dependencies, you’ll either have to check in certain build outputsor build a project after cloning it before you can navigate the project in an editor without seeing spurious errors.
When using VS Code (since TS 3.7) we have a behind-the-scenes in-memory.d.ts generation process that should be able to mitigate this, but it has some perf implications. For very large composite projects you might want to disable this usingdisableSourceOfProjectReferenceRedirect option.
Additionally, to preserve compatibility with existing build workflows,tsc willnot automatically build dependencies unless invoked with the--build switch.Let’s learn more about--build.
Build Mode for TypeScript
A long-awaited feature is smart incremental builds for TypeScript projects.In 3.0 you can use the--build flag withtsc.This is effectively a new entry point fortsc that behaves more like a build orchestrator than a simple compiler.
Runningtsc --build (tsc -b for short) will do the following:
- Find all referenced projects
- Detect if they are up-to-date
- Build out-of-date projects in the correct order
You can providetsc -b with multiple config file paths (e.g.tsc -b src test).Just liketsc -p, specifying the config file name itself is unnecessary if it’s namedtsconfig.json.
tsc -b Commandline
You can specify any number of config files:
shell> tsc -b# Use the tsconfig.json in the current directory> tsc -b src# Use src/tsconfig.json> tsc -b foo/prd.tsconfig.json bar# Use foo/prd.tsconfig.json and bar/tsconfig.json
Don’t worry about ordering the files you pass on the commandline -tsc will re-order them if needed so that dependencies are always built first.
There are also some flags specific totsc -b:
--verbose: Prints out verbose logging to explain what’s going on (may be combined with any other flag)--dry: Shows what would be done but doesn’t actually build anything--clean: Deletes the outputs of the specified projects (may be combined with--dry)--force: Act as if all projects are out of date--watch: Watch mode (may not be combined with any flag except--verbose)
Caveats
Normally,tsc will produce outputs (.js and.d.ts) in the presence of syntax or type errors, unlessnoEmitOnError is on.Doing this in an incremental build system would be very bad - if one of your out-of-date dependencies had a new error, you’d only see itonce because a subsequent build would skip building the now up-to-date project.For this reason,tsc -b effectively acts as ifnoEmitOnError is enabled for all projects.
If you check in any build outputs (.js,.d.ts,.d.ts.map, etc.), you may need to run a--force build after certain source control operations depending on whether your source control tool preserves timestamps between the local copy and the remote copy.
MSBuild
If you have an msbuild project, you can enable build mode by adding
xml<TypeScriptBuildMode>true</TypeScriptBuildMode>
to your proj file. This will enable automatic incremental build as well as cleaning.
Note that as withtsconfig.json /-p, existing TypeScript project properties will not be respected - all settings should be managed using your tsconfig file.
Some teams have set up msbuild-based workflows wherein tsconfig files have the sameimplicit graph ordering as the managed projects they are paired with.If your solution is like this, you can continue to usemsbuild withtsc -p along with project references; these are fully interoperable.
Guidance
Overall Structure
With moretsconfig.json files, you’ll usually want to useConfiguration file inheritance to centralize your common compiler options.This way you can change a setting in one file rather than having to edit multiple files.
Another good practice is to have a “solution”tsconfig.json file that simply hasreferences to all of your leaf-node projects and setsfiles to an empty array (otherwise the solution file will cause double compilation of files). Note that starting with 3.0, it is no longer an error to have an emptyfiles array if you have at least onereference in atsconfig.json file.
This presents a simple entry point; e.g. in the TypeScript repo we simply runtsc -b src to build all endpoints because we list all the subprojects insrc/tsconfig.json
You can see these patterns in the TypeScript repo - seesrc/tsconfig-base.json,src/tsconfig.json, andsrc/tsc/tsconfig.json as key examples.
Structuring for relative modules
In general, not much is needed to transition a repo using relative modules.Simply place atsconfig.json file in each subdirectory of a given parent folder, and addreferences to these config files to match the intended layering of the program.You will need to either set theoutDir to an explicit subfolder of the output folder, or set therootDir to the common root of all project folders.
Structuring for outFiles
Layout for compilations usingoutFile is more flexible because relative paths don’t matter as much.The TypeScript repo itself is a good reference here - we have some “library” projects and some “endpoint” projects; “endpoint” projects are kept as small as possible and pull in only the libraries they need.
The TypeScript docs are an open source project. Help us improve these pagesby sending a Pull Request ❤
Last updated: Dec 16, 2025