Now that TypeScript has become the de facto standard in frontend development, new projects and third-party libraries are mostly built on its ecosystem. For existing projects, TypeScript can also be applied gradually. Just add the toolchain, and start writing or rewriting part of your application. In this article, I will walk you through the steps of adding TypeScript to a Vue 2 project, since I myself is working on a legacy project, and TypeScript has brought a lot of benefits.
Prerequisites
For those who are new to TypeScript, I recommend you read the guide TypeScript for JavaScript Programmers. In short, TypeScript is a superset of JavaScript. It adds type hints to variables, as well as other syntax like class, interface, decorator, and some of them are already merged into ECMAScript. When compiling, TypeScript can do static type check. It will try to infer the variable type as much as possible, or you need to define the type explicitly. Here is the official TypeScript Cheat Sheet.
You should also be familiar with Vue, vue-loader, and webpack. Vue 2 already has good support for TypeScript, and the recently published Vue 2.7 backported a lot of useful features from Vue 3, like composition API, <script setup>
, and defineComponent
, further improving the developer experience of TypeScript in Vue.
Before you start, upgrade the existing tools to their latest version. vue-loader
v15 is the last version that supports Vue 2. Consult the official documents if you encounter migration issues.
1 | yarn add vue@^2.7.8 |
Install TypeScript and ts-loader
First, add typescript
and ts-loader
as development dependencies:
1 | yarn add -D typescript ts-loader |
Add ts-loader
to webpack config:
1 | const config = { |
Now .ts
files will go through ts-loader
to get compiled. vue-loader
will extract <script lang="ts">
blocks from SFC (Single-File Components) and they also get compiled. The resolve
and appendTsSuffixTo
options allow TypeScript to import .vue
files as modules. transpileOnly
tells TypeScript compiler not to do type checks during compiling. This is for performance reasons, and we will cover it later.
A TypeScript project should have a tsconfig.json
in the project root. A minimum example would be:
1 | { |
Options like baseUrl
and moduleResolution
tells TypeScript how to find and import a module. allowJs
allows you to import JavaScript modules in .ts
files. skipLibCheck
tells TypeScript to ignore type errors in node_modules
folder. strict
turns on extra type checks, such as no implict any
or this
.
Write Vue component with TypeScript
In Vue 2.7, we can use defineComponent
with Options API to get better type inference. The following example is taken directly from Vue 3 document. To enable type check in VS Code, install the Volar extension.
The count
variable in template is correctly inferred as number type. We can add more type hints to component properties, emits, and event handlers. Please refer to the document for further details.
Another example would be typing the API request and response data. Take Axios for an instance. This library is currently written in JavaScript, but comes with a type declaration file that adds type hints to the public API. We can combine it with our custom request/response types.
1 | interface LoginRequest { |
If you are using OpenAPI, you can generate typed clients from the specification file. I have written a blog on this topic: OpenAPI Workflow with Flask and TypeScript.
We can also add delaration file to our legacy JavaScript modules. Say there is a utils.js
module with some function:
1 | export function formatBytes(bytes) { |
Create a utils.d.ts
file with the following content:
1 | export function formatBytes(bytes: number): string |
Now TypeScript will be able to analyze the code:
1 | import { formatBytes } from '@/utils' |
Check types during development and build
As mentioned above, the transpileOnly
option tells ts-loader
to skip type check so as to speed up the bundling process, but obviously drops the benifit of static typing. Though IDEs like VS Code + Volar will identify the problems during development, we still need to check types when someone is not using an IDE, or before a pull request is merged. For this purpose, we shall add other two tools:
1 | yarn add -D fork-ts-checker-webpack-plugin@^7.2.13 vue-tsc@^0.39.0 |
The ForkTsCheckerWebpackPlugin, as its name suggests, forks a separate process from webpack and do the heavy lifting type check.
1 | webpack 5.73.0 compiled successfully in 4177 ms |
After yarn start
, local dev server will be available in 4s, and type check takes 11s to finish. The error message will also be displayed on the web page.
Add this plugin to webpack config, and turn on its support for Vue SFC.
1 | const { VueLoaderPlugin } = require('vue-loader') |
However, this plugin only solves the problem during development, we still need a way to do type check before someone merges his code. The solution is to put vue-tsc
in the lint phase of your project. tsc
is the TypeScript Compiler, and vue-tsc
is a wrapper of that to support compiling TS code block in SFC. Modify the lint
script in your package.json
and setup a proper CI pipeline.
1 | { |
More on code style and linting
We usually use eslint
to enforce various rules of coding convention, and prettier
for auto formatting. TypeScript also has dedicated lint rules and style guide. Install the necessary eslint plugins:
1 | yarn add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin |
To make it work with esling-plugin-vue
, use the following .eslintrc.js
config:
1 | module.exports = { |
Prettier also has built-in support for TypeScript. The prettier
plugin in extends
helps disabling some of the formatting rules. Here is an example of .prettierrc.json
:
1 | { |
And do not forget to add husky
and lint-staged
to your toolchain, that helps auto linting and formatting your code before it is committed.
Appendix: TypeScript transpilers
tsc
is the official compiler but it slows down the bundling. So we enable transpileOnly
option in ts-loader
and add ForkTsCheckerWebpackPlugin
to tackle this problem. There are other transpilers that understand TypeScript syntax, like @babel/preset-typescript
, esbuild, and SWC, but apparently none of them does type check so the checker plugin is still necessary.
Another note on TypeScript ESLint project. It does have some type-aware linting rules, but they are rather strict and will cause a lot of warnings. Use it judiciously.
1 | module.exports = { |
Some warnings on an existing project:
1 | /morph-fe/src/App.vue |