Optimizing multi-package apps with TypeScript Project References

A new utility for managing references in multi-package repos

--

Photo by Clint Adair on Unsplash

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/packagefor 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.

--

--