Upgrading To Typescript 3.5 And Incremental Builds
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
andts-loader
- get to use
--incremental
for cold builds - compile TypeScript once for server and client