Faster TypeScript build with project references

This weekend I have finally got to optimizing the TypeScript build for my side project. Because page bundles included both shared modules — one for the code shared between the backend and the frontend, and one for the code shared between the pages — they were getting longer to compile.

The plan was conceptually quite straight-forward: have each of the shared modules as their own bundle, and let the pages reference them instead of including them. Ideally, this would mean:

  • any page bundle would only re-compile when its own code has changed;
  • changes in the two shared modules wouldn’t require pages re-compilation.

All in all, the outcome that I was after was a faster build.

On the downside, because the two shared modules would now be bundled separately, I’ll now have two more requests for every page, but I decided it’s worth the trade, and I will return to this later.

In a nutshell, I went through all the 10 tsconfig.json files in the project and did 2 changes:

  • use references instead of include where it made sense — this had the biggest impact;
  • set baseUrl to the project root — this results in uniform and explicit imports across all of the project modules, although just a tiny bit longer;
  • drop paths settings — because I couldn’t get them to work with project references.

Although it’s only three points, the commit itself touched 88 files because it has lead to changing import paths everywhere. Despite the scary number of files, VSCode made it quite manageable: I think it took me a few hours on a cozy Saturday.

Now the watching build is faster, which means better frontend development experience, the server start is faster which means better backend development, and the pre-commit hook is faster.

A collateral change that I’ve done during this work was that I have switched the two shared modules to the AMD module format because I need to load them into the browser, and then switched all the rest of the modules to AMD format too, just to have less variation across the project. I expected this to degrade the build speed because now all of each module’s TypeScript files are bundled into a single JS file instead of each file separately, but a quick measurement didn’t show any difference.

To get the Node tooling working with the AMD module format, (mocha and the backend server Node) I brought in the amd-loader NPM module.

I have attempted this build optimization a couple of times before, and couldn’t get it to a working state, which made it even more exciting for me.

The next technical tweak that I have in mind is to optimize the loading of shared and page bundles. Stay tuned.