TypeScript is an amazing language that helps developers be more productive when integrating new libraries, or avoiding mistakes in their own codebase. Modern IDEs come with built-in tooling for the language. However, things can get tricky when a more elaborate setup is required.
In this post we will configure TypeScript compiler to give us live feedback on type errors even when running in parallel with other tools in the terminal.
For this example project we are going to use Vite build tool that has started to gain popularity recently. Vite supports Typescript out-of-the-box, but only for transpiling, and not for type-checking that we want. For a package manager we will use Yarn, but any other would work as well.
Keep in mind that running commands from this post will install the latest versions of tools and libraries, which may be different at the time of reading.
We will use the
create feature of Yarn to quickly set up a new Vite project.
When prompted with the dialog, choose
vanilla option and then
Note, this command will install a global Yarn package
As a result we should get a simple project structure:
│ ├── main.ts
│ ├── style.css
│ └── vite-env.d.ts
After that we can follow commands suggested by
create-vite to install dependencies and start a dev server.
If all commands succeed, we should be able to navigate to the localhost to see the rendered version of our web page.
Moreover, if we change and save any files in our
src directory, the page will be automatically reloaded with fresh content.
Let’s modify the
main.ts file to make it even simpler than the template:
const app = document.querySelector<HTMLDivElement>("#app")!;
app.innerHtml = `<h1>Luck-typing!</h1>`;
Note that the
app variable has a type
If we make a mistake by accessing a wrong property
innerHtml of the
app, we will see the error in the editor.
However, Vite would happily reload the page without showing any errors, but the page would be empty.
This can be problematic if the change affects multiple components in other files.
We will not always know about the errors until we open those files or run a full build.
If there are hundreds of files in the project it will not be feasible to check each of them on every change. The only thing we can do now to be sure that nothing was broken is to run the full build.
Aha! Now we know where the error is. But can we avoid building the whole project just to see if some files were affected by our change?
Depending on the setup TypeScript can complain about the types of external artifacts, such as
One way to deal with this is to pass
--skipLibCheck flag to the
tsc when running a command that uses it.
However, it is better to avoid adding extra flags if there are no errors in the first place.
Depending on the setup Typescript compiler can start creating
.js files right next to
Since we are trying to only check types and not compile code, we can disable this behavior.
The simplest option is to add
noEmit field to the TypeScript config:
Another option is to pass
--noEmit flag to the compiler.
Humans are not adept at performing hundreds of routine tasks such as clicking on every file after every code change. Computers do this job much better, but only if we found a way to explain exactly what they need to do.
Luckily for us, TypeScript compiler includes just the right tool — a file watcher.
We can activate it by running the compiler with the
Instead of doing a one-pass check of the types, showing the errors and exiting, the watcher keeps running until stopped.
Whenever a file on disk is changed, the watcher re-runs the type check and prints the new error messages.
Similarly to the
build command, the output of the watcher is colored, which helps to identify errors visually.
Let’s create a new Yarn command in
package.json to make it easier to extend it later.
"watch-typecheck": "tsc --watch"
Nice! Now we can watch types just by running
The fact that Vite does not require the types to be correct reloading is actually a feature and not a bug. It gives the developers an option to check the types instead of breaking the whole UI because of a small error that is irrelevant for a task at hand.
However, sometimes it is good to know what effects a refactoring has on the rest of the codebase. How can we get this feedback without losing the comfort of the hot reloading? The answer is to run both tools at the same time!
There are many ways
to run Yarn commands in parallel.
In this project we will use a package called
concurrently, but any other will do as well.
We can add it to the project by running
yarn add -D concurrently.
Now let’s change the
dev command to do type checking together with hot reloading.
"dev": "concurrently --kill-others \"yarn watch-typecheck\" \"vite\""
This command will run
yarn watch-typecheck in parallel to
In case one of them fails
--kill-others flag makes sure to stop the other and exit.
Great job! Now we have a nice setup to comfortably do hot reloading and see type errors at the same time.
Although this setup works, there are a couple of problems with it.
When we save changes in any of the source files.
- Output provided by the dev server (or any other tool running in parallel) is overwritten by the TypeScript even if there are no errors.
- Errors in the terminal are not colored anymore, and it is much harder to make out their structure.
First, let’s deal with the overwritten output.
The TypeScript compiler assumes that it is the only program running in the terminal. Whenever the source files change, the type errors may change as well. This means that the previous errors may no longer exist or have a different reason to be shown. That is why the compiler would clear the contents of the terminal completely in order to avoid confusing the developer.
However, in our setup this is a problem since other tools running in parallel may produce valuable output that must not be lost.
In order to fix this we can pass
--preserveWatchOutput flag to the compiler to completely turn off terminal clearing.
Now, let’s make errors colorful again, so they are easier to spot in the output.
We have already seen that when running
watch-typecheck command without
concurrently the output was colored.
This is the default behavior when TypeScript compiler knows the output goes directly to the terminal.
However, when we wrap this command with
the compiler starts to think that the destination for the output may be a regular file or even another program.
The compiler disables the output coloring in order to avoid weird errors with terminal control characters downstream.
Since we know that the output will end up in the terminal anyway, we can force the compiler to preserve the colors.
This is done with the
Now we need to update our
watch-typecheck command to incorporate these changes.
This is how the commands look after we include all the modifications:
"dev": "concurrently --kill-others \"yarn watch-typecheck\" \"vite\"",
"watch-typecheck": "tsc --watch --pretty --preserveWatchOutput"
We can run the
yarn dev command again to make sure that both tools happily write colorful messages to the terminal.
In this post we learned how to combine live project type-checking with any other tool running together in the terminal. We also added some tweaks to preserve output of both tools and keep things colorful.