TypeScript became a standard for backend (Node.js) and frontend (Angular/React) development. The types it brings allow us to develop apps with the confidence that we won’t make any silly mistakes. It doesn’t relieve us from the responsibility of using best coding practices, but it does make developers’ lives a bit easier. Yet there is one downside of TS. It’s compile time. So, let’s talk about how to speed up your TypeScript project.

Introduction

The more code you have, the longer it takes to compile. I think we can all agree on that. In some cases, that’s also the reason why we split the application into smaller pieces (services), so we don’t need to compile everything.

Let’s take our Express Boilerplate (the starter we’re using to build apps for our clients). To speed up the development process, we’ve built a starter pack that reflects our way-to-go stack. 

This includes:

  • production-ready containerization,
  • TypeScript support,
  • Plop for code generation,
  • built-in support for Redis and PostgreSQL with TypeORM,
  • best practices like Command pattern or Dependency Injection with Awilix
  • REST/graphQL API support,

and more!

In its raw form, running the `npm run watch` command (which starts the TypeScript Compiler – tsc – in a watch mode) takes 28 seconds to have an environment ready to work with.

It doesn’t sound like a lot, but with more code to come, this number will slowly increase. So what happens during compilation?

In short, there are two major components:

  • types checking,
  • transpilation to JavaScript

At this point we cannot do much with the first one, however, there is a way to speed up the transpilation part.

TypeScript itself can’t change TS code into JS files. There is no such functionality. However, there is one that allows us to check types only – –noEmit flag.

So what if we use something faster for a transpilation part and use TSC only for TS types checking?

What is SWC?

SWC stands for Superfast JavaScript/TypeScript Compiler. It is a tool, created in Rust, that has one job – to transpile the code from TS/ES to a specific target. It won’t check the types (at least not yet), but because of that, it is much faster than TSC.

Our Boilerplate compiles in 7 seconds. That’s 4 times faster than using the built-in TypeScript tools. So how does it work?

SWC can be installed as standard devDependency with:
npm i -D @swc/core @swc/cli

And that’s all! At least for some basic features. 🙂

You can try it by running:
swc <some-directory> --out-dir build

and in a second you should get the code transpiled. If you need to have a watch mode, then just add the -w flag at the end.

Of course, it is a little bit more complicated for more complex projects. By default SWC has most of the features turned off and for many TypeScript applications, it will be necessary to add additional configuration.

This could be done with a special .swcrc file. 

Sadly there is no tool that creates swc configuration from tsconfig yet. Therefore, you should remember to keep both files in sync.

There are a few useful keys in the SWC config.

The first one is jsc. This key contains everything related to the parser type we’re going to use – for example typescript or ECMAScript, but also some information about the target js version we want our code to be converted to.

The second one is a module. This one is especially useful for Node.js apps, because of the CommonJS modules requirement.

For most of our apps, we also had to turn on the decorators support, otherwise you’ll get a nasty error message.

Obviously, those are only a few of the many possible configuration options. Feel free to check the remaining ones on the SWC configuration website.

What about types?

One question remains unanswered. Do we drop types checking? In fact, SWC doesn’t care about the code validity nor it cares about types. So in reality we’re losing the biggest reason to use TypeScript for. What the hell?! Don’t worry we are not doing that. 😊

At the beginning of this article, I mentioned that TS can work in two ways. It either generates the code and checks types or only checks types. Let’s use the second approach then!

The command we need is simple:

tsc -w --pretty --skipLibCheck --noEmit

Yet we still need to run both in parallel – SWC and TSC. We can either use a library for that (to have cross-operating system functionality) or we can just use and run both simultaneously.

swc src --out-dir build/src -w --sync & tsc -w --pretty --skipLibCheck --noEmit

What is the benefit of that approach?

The app is ready to develop in 7s, and at the same time, we’re going to get all of the information about possible errors in our code.

The larger our app gets, the bigger the difference between SWC and TSC is going to be, so having the possibility to develop earlier is a huge advantage.

Conclusions

So where is the catch? – you may ask.

The code generated with SWC is a little bit different from the one generated by TSC. In some cases, this leads us to some issues with our tsc-working application.

For example, depending on a configuration, decorated classes are returned as classes or objects (yet in tsc it is always going to be a class). It doesn’t sound bad, but if any of your functions requires the specific type (for example class), it will break. We had this problem with the Awilix helper asClass.The other problem is the support of new features. SWC is evolving pretty quickly, yet it still doesn’t support all of the features that TSC/Babel does. The most up-to-date list of features is available on the SWC website. But it is also worth checking the GitHub issues pages.

So would I recommend using SWC?

There is a bright side here. It is very easy to switch back and forth between SWC and TSC, so we all should give it a try. At The Software House we’re going to play with this a little bit more to see how stable it is in larger projects. However, at this point I’m very happy with the results!

Leave a Reply