I created a Github Repository to make it easier to follow the following code description:
Code Description
TypeScript Project References make it possible to compile a TypeScript project that consist of multiple smaller TypeScript projects, each project having a tsconfig.json file. (Source: Project References Documentation)
TypeScript Setup
We have a root tsconfig.json file that only manages its sub-projects. The references property specifies the directories that each contain a valid tsconfig.json file. If we now build the project with the --build option (tsc --build tsconfig.json) then we specified the projects which should be compiled, but we didn’t specified the build order in which the projects should be compiled.
{
"references": [
{ "path": "./client" },
{ "path": "./cmd" }
],
"files": [],
"include": [],
"exclude": ["**/node_modules"]
}
To correctly specify the build order we need to add a references property to the cmd/tsconfig.json file. This tells the compiler that it first needs to compile client/ before we compile cmd/:
cmd/tsconfig.json:
{
"extends": "../tsconfig.packages.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
},
"references": [
{
"path": "../client"
}
]
}
Build order
client/
^
|
|
cmd/
Node Setup
Best practice is that each sub-project has its own package.json file with the main property and the name set. In our example both packages (cmd/ and client/) have a main property pointing to the index.js file in the TypeScript outDir directory (cmd/dist/index.js and client/dist/index.js).
Project structure:
tsconfig.json
cmd/
tsconfig.json
package.json
src/
index.ts
dist/ #artifacts
index.js
client/
tsconfig.json
package.json
src/
index.ts
dist/ #artifacts
index.js
client/packages.json
{
"name": "client",
"version": "1.0.0",
"main": "dist/index",
...
}
It is important that we add the client/ as dependency to the cmd/packages.json so the module resolution algorithm can find the client/dist/index.js when we import it in our TypeScript code import Foo from 'client';:
cmd/packages.json
{
"name": "cmd",
"version": "1.0.0",
"main": "dist/index",
"dependencies": {
"client": "1.0.0" // important
}
}
cmd/src/index.ts
import Foo from 'client';
console.log(Foo())
Yarn Setup
The yarn setup is easy. Yarn adds all packages under node_modules instead of:
cmd/node_modulesclient/node_modules
To enable yarn workspaces add the workspaces property and the private: true property to the <root>/package.json file.
<root>/package.json
{
"private": true,
"workspaces": [
"cmd",
"client"
],
"name": "yarn_workplace",
"version": "1.0.0"
...
}
The cmd/ and client/ packages are symlinked under the <root>/node_modules/ directory:

Notes
- To enable code navigation one has to first build the project
- Every package lives on its own. The
cmd/package uses the definition fileclient/dist/index.d.tsfor type information instead of using the the TypeScript files directly.