Setting Vue up for TypeScript goodness
How and why of setting up a build that lets you write .vue components in TypeScript.
OK, maybe not a full day, but it still takes too long to set up the tooling for a new project. And some things are still way too hard for my taste. Not the good ‘hard’ as in challenging. More like outright tedious. Yes, I’m talking about webpack, TypeScript, and the .vue
files. Here, I want to show what I did, and why I still went though with it.
If you’re just looking for a quick TypeScript fix, that’s relatively easy. Grab webpack-blocks, and set it up in under 5 minutes. What I was trying to do was a bit more exotic if you will. I wanted to use Vue, and, specifically, the .vue
files, with TypeScript. Information is scattered all over the web, so I figured it might not be a bad idea to gather it all here as quick reference.
May 8th, 2017 UPDATE: Since I wrote this article, webpack-blocks 1.0.0 is almost released. It is already available on NPM, so I took the time to release some code discussed in this article. I will publish a separate post that outlines the changes compared to the original article.
Before we begin, forget about what you know about how to write Vue components. If you want to use .vue
files with TypeScript, you have to code the TypeScript way. You can code the old way, sure, but then you don’t benefit from type checking, so it’s just a waste of your time. You have been warned.
Why .vue files?
First off, let me explain why I wanted .vue
files to begin with (in case you don’t know what they are, look here). These single-file-multi-language packages are not just means for grouping together what would otherwise be a set of loose files. Nope, they have their own bells and whistles that go beyond simply bundling code together.
For example, HTML templates are coded in HTML, so you get syntax highlighting, context-aware auto-completion and/or emmet support. Great JavaScript developers are scarce, so coding plain HTML (as opposed to JSX and, even worse, hyperscript) can go a long way. Yes, I’m aware that there are stories about designers and newbies picking up hyperscript and similar, and actually liking it, but then you have to remember that even experienced JavaScript developers don’t give in without a fight sometimes.
Secondly, scoped CSS. If you still think BEM has a place in a properly tooled-up front-end framework, then good, you understand the importance and the challenges of well-organized CSS. You may get a kick out of scoped CSS. I’m not going to try and sell you scoped CSS. It’s like drugs: it sells itself. Just try it. I dare you.
All of this, and the fact that I’m dealing with one file instead of three or four, are good enough arguments to spend the extra time to get .vue
files working with TypeScript.
Why TypeScript?
This is something you have to arrive at on your own, if ever. People used untyped JavaScript in the past, and it actually works. In fact, I was trying to decide between TypeScript and LiveScript, which is an untyped functional-flavored version of CoffeeScript, with terse and somewhat weird syntax.
At the end of the day, it boils down to productivity. LiveScript provides less verbose syntax and shortcuts for many things I use on day-to-day basis. TypeScript will catch many coding mistakes I may make, and may provide additional information about the code that makes working with unfamiliar libraries and frameworks a lot easier. I chose latter over the former for arbitrary gut-feeling reasons.
If you must ask for a graphic account of what it’s like to use TypeScript in production, there is a great article from the Slack team.
Enough talk, let’s code… or maybe not
Before we can code, we need to set the tooling up.
First, pick up webpack-blocks. That’s the easiest way to configure webpack, that I know of. Well, easiest after simply copying and pasting existing configuration, but you get my point.
Now, right off the bat, webpack-blocks does not support .vue
files. There are two 3rd party packages that make using .vue
files possible.
The built-in TypeScript configuration in webpack-blocks uses awesome-typescript-loader. It’s a good loader, but it does not play nice with .vue
files. (It also has a weird name, but that’s my problem.) In addition to the package for configuring vue-loader
, we therefore also need one that will configure ts-loader
which has some provisions for working with container formats like .vue
.
npm i -D webpack-blocks-vue webpack-blocks-ts
You finally end up with something like this webpack.config.js
file. Important things to note:
- We are passing the
{ appendTsSuffixTo: [/\.vue$/] }
option to ts-loader because otherwise TypeScript compiler doesn’t want to cooperate. This causes.vue
files to be treated as.vue.ts
, just the way TypeScript compiler likes them. - We are not using Babel. If you want to use JSX, you may want to use it, but I don’t.
- We are passing
{ esModule: true }
to vue-loader. This option tells the loader to emit ES2015 modules instead of CommonJS ones. Apparently, TypeScript doesn’t like CommonJS modules.
You also need to let TypeScript know what .vue
files are and how to deal with them. For this purpose, we add a vue.d.ts
file in the source tree. This definition tells TypeScript what the type of the default export from .vue file will be like.
Can we code now?
Yes, we can. As I mentioned in the introduction, you can’t just code like you used to and expect things to work. Coding .vue
files for TypeScript requires a different approach. Fear not, though. The alternative syntax makes a lot of sense, and, in my opinion, makes the code a lot more readable (not to mention the type checking goodness).
You will first need to add an additional dependency: vue-property-decorator.
Your component will then look something like this:
<template>
<div class="hello">Hello, {{ name }}</div>
</template><script lang="ts">
import Vue from 'vue'
import { Component, Prop } from 'vue-property-decorator'@Component
export default class Hello extends Vue {
@Prop
name: string
}
</script><style scoped>
.hello {
font-size: 200px;
font-family: sans-serif;
}
</style>
I won’t go into too much detail here because both vue-property-decorator and vue-class-component, which it is based on, are fairly well documented and straightforward to use.
Main point to watch out for is that this style removes a lot of the magic/boilerplate (or hides it, anyway). Instead of defining watchers and methods under nested objects and then using this
to mean the outer object (which rightfully trips TypeScript’s type checker), all methods are defined on the object to which this
is bound. It makes everything a bit easier to understand and follow, makes things a bit closer to how JavaScript normally works.
You will find the full example in my vue-ts-sandbox repo.
As a side note, even if you don’t care about TypeScript, you can use these decorators with Babel.
Is it worth it?
As you can see, it’s a lot of hassle getting it to work. Even now, in hindsight, with the benefit of documented cummulative knowledge, it still takes a while to set things up. The question then is whether it’s worth it.
As I said in the Why TypeScript section, it’s something you have to arrive at on your own.
I believe that all the hassle of type annotations and build configuration is well worth it when the type checker lets you know that you have passed the wrong argument, or forgot to assign something to a property, or that the return type of some function is not what you thought it would be. You get the idea. Having to explicitly declare interfaces also helps me understand the code base a bit better, and slows me down just enough to avoid hasty mistakes.
Right now it does take a bit of extra effort to get there, but think about it: unit testing takes a bit of extra effort (far more than this!) and nobody will argue that unit testing is not worth it. Along the same lines, this is just another layer that contributes to code quality. That’s how I look at it anyway.