Type checking Vue component template

Some time ago I was talking with my friend about typescript adoption by popular frameworks. He, as a React developer, mentioned that he appreciates the fact that the entire application can be checked with a type checker. Since templates are written in TSX, they are passed through the typescript engine, which causes that not only the components and functions are checked for types correctness, but also the way they are used (e.g. passed props). It’s pretty easy to achieve in React since TSX is translated to TS and then it can be easily checked with typescript type checker.

Vue is a bit different. Obviously you can use TSX (is it obvious? 🤔) but lets forget about it as it’s not a standard way of writing Vue applications. The standard way of writing Vue components is writing Single File Components (SFC). A component has <template> block with html-like template, <script> tag with logic of Vue component and <style> block. We know how to check types in <script> right? – just use typescript there! Although some React devs might disagree with me, I think that in most cases type checking styles with typescript is not necessarily needed 😅 But what about <template>?

To be honest, I didn't feel the need to check it at all… Until this conversation. I could just tell him “Vue is better anyway” but he has planted the seed of doubt in my mind (not about Vue's superiority, but about value of type checking templates 😁).

Template is the place where we use our components (passing props, listening for events), use methods (with typed arguments), sometimes we even write arrow functions or making simple computations (that’s a different topic if we should do it).

Okay, maybe it’s worth to type check templates of Vue components maybe it’s not – how to check if it is worth doing? This is not very popular topic in Vue community so maybe it’s totally useless?

Hurry up, before we all come to our senses!

I decided to check it as follows:

  1. I will find out the way of type checking templates
  2. I will make a type check templates in several projects that I have access to and whose codebase I know.
  3. I will check how many errors it detects, and then I will think if it was worth it 😄

How to check types in templates?

vue-type-check

First source about this topic I found was vue-type-check: type checking in the template part. We can read there that:

Nowadays more people start trying to build Vue project with Typescript. Vue itself also provides better support to Typescript such as the vue-class-component lib and rewriting version 3.0's codebase in Typescript.

But the limitation of type checking in the template is still a big problem preventing Vue component from being type-safe.

So it is a “big problem”? Why nobody is talking about it?

We have just open-sourced an easy-to-use Vue type checker, vue-type-check, to help solve this problem. This type checker can do type checking on the template and script code of a Vue single-file-component.

And it also provides CLI and programmatical API usages with clear error messages which is helpful to integrate the existing workflows.

I like it! Additionally this article also describes how this tool works and what was other attempts to implement template type checking for Vue. Actually other attempts are described based on this post from katashin but I personally can't read Japanese very well.

tl;dr

Currently, vue-type-check is built on the top of Vetur's interpolation feature.

Cool. Are there other options?

I found and tested those:

VTI

VTI (Vetur Terminal Interface) is a CLI that exposes some of Vetur's language features […].

Sounds familiar, isn’t it? (vue-type-check is based on it) I tried this one but there is a little problem – it’s not configurable yet. I cannot explicitly ignore some components. You may be wondering why I want to ignore some components, and that’s a great question – thanks for asking! Mostly because vti does not handle all of them correctly. Especially it does not understand some features of functional components I’m using in projects where I tested these tools.

VueDX/TypeCheck

A command line tool to check types, functionally equivalent to tsc --noEmit but supports Vue.

That’s exactly what I wanted! But wait…

Class components and composition-api plugin are not supported yet.

Ohh… 😢 I have class components in my projects. Another problem is that, same as vti, it’s not yet configurable enough.

Was it worth check types in templates?

I tested it on 3 projects:

  • Pretty big real world project
  • Nuxt content blog with composition API plugin
  • Small side project in Vue 3

Pretty big real world project

And huge effect: vue-type-check found 116 errors! 🔥🔥🔥

Lets look closer…

And… 👩‍🚒🧯

Unfortunately some of them are completely incorrect. It turns out that vue-type-check does not work correctly for quite a few cases:

  • arrow functions
6:21 Property 'row' does not exist on type 'ResourceTable'.
  4 |   <DataTable
  5 |     :items="resources"
> 6 |     :get-key="row => row.id"
    |                      ^^^
  7 |     :query="query"
  8 |   />
  • duplicated event listener with different modifiers
27:5 Duplicate identifier '"keydown"'.
  25 |     @keydown.esc.stop="closeMenu"
  27 |     @keydown.left.stop.prevent="focusLeft(index)"
  27 |     @keydown.right.stop.prevent="focusRight(index)"
     |      ^^^^^^^
  28 |     @keydown.up.stop.prevent="focusUp(index)"
  29 |     @keydown.down.stop.prevent="focusDown(index)"
  • functional component context e.g. data , listeners, injections, props
4:12 Property 'data' does not exist on type 'ResourceSearch'.
  2 |     :is="injections.components.ListItem"
  3 |     class="resource-list-item"
> 4 |     v-bind="data.attrs"
    |             ^^^^
  5 |     v-on="listeners"
  6 |   >

35 errors left.

Another problem is that in template you cannot use typescript. Especially you cannot use as operator. It's usually not a good idea to overuse this operator, but it can be useful sometimes (don’t judge me). But it’s worth noting that "type guards" work fine with v-if so e.g. this code works as expected (without type errors):

<template>
  <div>
    <template v-if="hesitant.type === 'x'">
      {{ hesitant.propertyOfX }}
    </template>
    <template v-else>
      {{ hesitant.propertyOfY }}
    </template>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'

type Hesitant =
  { type: 'x', propertyOfX: string } |
  { type: 'y', propertyOfY: string }

@Component
export default class AComponent extends Vue {
  private hesitant: Hesitant = { type: 'x', propertyOfX: 'something' }
}
</script>

Unfortunately typescript is not always that smart. If I create a computed value isHesitantX and I check this value in v-if I’ll get errors:

3:18 Property 'propertyOfX' does not exist on type 'Hesitant'.
  Property 'propertyOfX' does not exist on type '{ type: "y"; propertyOfY: string; }'.
  2 |   <div>
  3 |     <template v-if="isHesitantX">
> 4 |       {{ hesitant.propertyOfX }}
    |                   ^^^^^^^^^^^
  5 |     </template>
  6 |     <template v-else>

7:18 Property 'propertyOfY' does not exist on type 'Hesitant'.
  Property 'propertyOfY' does not exist on type '{ type: "x"; propertyOfX: string; }'.
  5 |     </template>
  6 |     <template v-else>
> 7 |       {{ hesitant.propertyOfY }}
    |                   ^^^^^^^^^^^
  8 |     </template>
  9 |   </div>

I don’t consider this kind of errors as bugs at this moment. So lets check how many “real bugs” I’ll find if I ignore this kind of issues.

🥁 🥁 🥁

The answer is: 4.

Two of them were syntax errors (not dangerous because vue-template-compiler can handle them) and two of them were actual lack of property on a given model. Those syntax errors should have been caught by our linter, so it prompted me to review the current setup 😬 Another two errors were invalid keys for v-for (evaluated to undefined for all items).

How it went for another two projects?

Nuxt content blog with composition API plugin

  • 3 errors
  • 2 of them were related to lack of <script> tag in a component – yet another incorrectly handled case.
  • last one could be fixed by adding type guard or using as operator in script (just type errors, not bugs yet)

Small side project in Vue 3

  • 15 errors
  • 6 incorrect errors
  • remaining 9 errors were all in one component and again they were just type errors (not bugs yet)

Conclusions

I did a lot of unnecessary work again! 🎉

What else… I’ve found 4 minor bugs and some type errors in the 3 projects.

Was it worth it? I don’t think so.

Does it mean that type checking template is not needed? Again, I don’t think so.

Due to the fact that described tools (at least for Vue 2) are not mature enough yet, it’s hard to include them in CI pipeline. Such a testing step need to be maintained and a decision must be made whether the value they give is worth the effort.

Perhaps a better idea is to periodically check and manually evaluate which type errors can cause some issues in the future. Another idea might be to check the code base after some refactoring.

On the other hand, if the number of actual bugs found by such a tool in your project is much greater, maybe it is worth putting in extra effort and keeping the setup so that the tests are always green?