Optimizing multi-package apps with TypeScript Project References
A new utility for managing references in multi-package repos
Maybe you read my two previous articles about Modularizing Applications with Ease and/or Shaping an Application with Packages, where I described the benefits of using packages to build and structure a large application.
In this article, I want to describe how we set up our workspace in combination with TypeScript and share a new open-source utility we developed.
Note: The following GitHub repository shows how our application is configured:
Using TypeScript Project References
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.
Source: https://www.typescriptlang.org/docs/handbook/project-references.html
Projects are in our case packages, so each package will get its own tsconfig.json
file. Since we run a complete compile check over the whole application, we also added one in the root folder of the repository.
To enable project references, you need to set the following settings to true
:
composite
declaration
declarationMap
After that, you can connect/configure every package via the corresponding tsconfig.json
file and if you have a dependency on a package inside that workspace, you need to add a property called references to that configuration. The reference property includes an array of objects that specify paths to the tsconfig
files for other resources.
Here’s an example from the main package of the shell:
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"references": [
{
"path": "../../apps/inventory/main"
},
{
"path": "../../shared/contracts/dealerships"
}
]
}
Because the main package depends on the inventory app and on the dealership contract, it includes two references to their package folders via relative paths.
We can now run tsc --build path/to/the/package
for each package, but if we add a tsconfig
in the root folder as mentioned before, we can compile the whole application at once.
The following file shows how this looks in the example repository.
{
"extends": "./tsconfig.base.json",
"files": [],
"references": [
{
"path": "apps/inventory/features/promotions"
},
{
"path": "apps/inventory/main"
},
{
"path": "shell/main"
},
{
"path": "shared/contracts/dealerships"
}
]
}
The downside here is that even if we have already the information about which packages in the workspace depend on one another, we still have to add the path in the tsconfig
file again.
Welcome to the update-ts-references utility
We created the update-ts-references utility function to update references based on the package dependencies in different repositories. It supports both yarn workspaces and repositories that rely on lerna to manage multiple packages.
With this article, we’re releasing this utility function via npm
and open-sourcing the code for it, so you can use it in your own projects too. 🥳.
With this utility, you only need to run npx update-ts-references
on the root folder to update all the references in the tsconfig
files.
You can also install it via yarn add update-ts-references -W --dev
and add it to the postinstall script on the root packages.
"scripts": { "postinstall": "update-ts-references"},
Conclusion
Using packages with TypeScript project references is easy to set up, and our freshly-released tool now makes it just as easy to keep them up-to-date. ✨
This approach has a few added benefits:
- You can have TypeScript configurations for each package, which is quite helpful when you’re moving from JavaScript to TypeScript — just add
allowJs
to any packages you haven’t migrated yet. - Publishing packages is also very simple because you can run
tsc --build
on a single package to just compile the code of that one package.
If you find this useful, star the repo to let us know.