Background

TypeScript 3.4 added a new compiler option I was very excited about: the incremental flag. See here for details. Faster builds is something I’m always going to be psyched about, and getting over the morning hump and making hopping back into development easier sounds fantastic.

Unfortunately, 3.4 also came with some performance issues for type checking, specifically for users of the styled-components library. This made me weary of upgrading, and I held off.

But lo and behold, 3.5 has arrived and with it not just fixes to these issues, but overall improvements over 3.3 all together. I immediately setup a branch on my personal project to check it out.

There are some backwards incompatible changes, which you’ll have to be aware of. Nothing too crazy, and I only needed a few changes (and actually ended up simplifying some types while I was at it, huzzah). Unfortunately, the excitement of those sweet cold builds fizzled nearly as soon as it started.

Utilizing the incremental flag

Like many, I’m using webpack to compile my clientside code. During development, I don’t ever straight compile TypeScript to Javascript files, but instead everything is done in memory through ts-node and ts-loader. In either case, currently, there aren’t any APIs to allow third party tools to hook into this incremental functionality (though it is coming here)

But I started to think – can I avoid the overhead of third party tools altogether and just use the TypeScript compiler directly? There’s additional overhead of file I/O, but then I’m just compiling all my TypeScript in one go for both the client and server. And with incremental rebuilds, I don’t even have to wait for that initial “big step” to start developing. Would it be faster? Let’s find out.

Again though, my dreams were dashed as I ran into the issue from my previous TypeScript webpack woes: code splitting. Basically, code splitting only works if I setup TypeScript to target esnext, so that async imports aren’t compiled out to promise-wrapped requires. But of course, if I try to run that esnext code on the server through Node.js, it chokes on that syntax.

Further options

Again, I knew that perhaps there was a path forward. Node.js now supports ECMAScript modules, at least in an experimental fashion. Maybe just for development I could utilize this. I know a stage 1 experimental API is going to bite me in the ass though, and further more I’d need to make sure my targets were mjs files, and who has time for that.

Instead, I decided to pivot my development pipeline – for development, I don’t actually need code splitting to work. Obviously latency for local machine development isn’t an issue, so while it would still be important to ensure code splitting works, I can do that as part of a testing workflow instead of development workflow.

Finally, my build workflow becomes:

graph LR;
  A[Source TypeScript] --> B[Compiled Javascript]
  B --> C[Webpack]
  C --> D[Client Payload]
  B --> E[Node.js]

The benefits of this are many:

  • remove dependency on ts-node and ts-loader
  • get to use --incremental for cold builds
  • compile TypeScript once for server and client