Blog overview

Migrate your existing Vue2 app to Typescript

Sometimes it can get tricky to migrate an existing Javascript project to Typescript. This post will support you in this process with a step-by-step setup guide.

Introduction

This post is a step-by-step instruction on how to migrate an existing Vue project to a Typescript setup. This is not a beginner's guide or an introduction to Typescript. It reaches out to developers with complex Vue setups without the usage of Vue CLI. To migrate a Vue CLI project please refer to the corresponding docs. In this case, it sure makes sense to quickly set up a fresh Vue CLI project with Typescript support, so you have a default repo to copy-paste from.

Preparations

First of all, make sure all your dependencies are up to date. Migrating to Typescript is a good moment to review your dependencies, upgrade where possible and clean up.

Its highly recommended to use VS Code as your IDE, cause it has excellent out-of-the-box Typescript support. If you don't use it yet, it's worth a look anyway as Typescript support is only one out of 1000 nice features.

Installation

As a first step install all needed dev-dependencies:

yarn add -D typescript @types/node

If you use Eslint and Prettier, you should also install the Typescript parser and plugin. If not, I would highly recommend checking it out.

yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser

# if you don't use Eslint yet use this line instead
# yarn add -D eslint eslint-plugin-vue @typescript-eslint/eslint-plugin @typescript-eslint/parser

As configuring Eslint can get fairly complex I will not further cover it here, as it is not the main purpose of this post. Please do your research.

Compiler setup

As you're still reading I assume you are not using a template setup or Vue CLI for your project. Hence, you know how to configure Webpack or any other bundler like Parcel or Rollup. Just check the docs for your bundler, there will be some instructions how to implement Typescript into your bundler. For Webpack you can find it here.

If you don't use a bundler at all, you can also use the Typescript CLI to compile your code. More about that is written down in the Typescript CLI docs.

Setup Typescript in the project

First, we need to add a tsconfig.json file that holds our Typescript settings and configuration.

{
  "compilerOptions": {
    /* Basic Options */
    "target": "esnext" /* Specify ES target version */,
    "module": "esnext",
    "lib": [
      "ESNext",
      "DOM"
    ] /* Specify library files to be included in the compilation. */,
    "allowJs": true /* Allow javascript files to be compiled */,
    "jsx": "preserve",
    "sourceMap": true,
    "noEmit": true /* Do not emit outputs. Only choose when you use Babel setup */,
    "importHelpers": true,

    /* Strict Type-Checking Options */
    "strict": true,

    /* Module Resolution Options */
    "moduleResolution": "node",
    "baseUrl": ".",
    "paths": {
      "@/*": [
        "src/*" /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
      ]
    },
    "types": [
      "webpack-env",
      "jest"
    ] /* Type declaration files to be included in compilation. */,
    "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
    "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports */,

    /* Advanced Options */
    "skipLibCheck": true /* Skip type checking of declaration files. */,
    "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
  },
  "include": [
    "src/**/*.ts" /* path to your resources */,
    "src/**/*.tsx",
    "src/**/*.vue",
    "src/**/*.js" /* Important during migration */,
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ],
  "exclude": ["node_modules"] /* Exclude from compilation */
}

Some annotations to the config:

types: does not define if types are loaded or not

Alternatively, you can run tsc --init to initialize a Typescript project in your root folder. All it will do is creating the tsconfig.json for you with some default configuration. Additionally, you can see a lot of possible config options right inside the file. This can really help start you off to get to know the config options.

Important to mention is, that you need to adjust the paths to match your directory structure. The example here is a default Vue setup. Also the "@/*" is an alias that is common in Vue projects. If you have a "self-made" setup as we do, you have to configure these in your Webpack config or replace it with your own alias.

For a full reference of the Typescript config please head over to the official docs. The here shown example is a starting point with best practices and some personal preferences. I recommend you change the most important things like target and path and adjust the config on the go. Every project is different and the config will need to adjust to yours over time.

After the config is complete you need to add shims for Vue. Create a file called shims-vue.d.ts with the following content along with your app source code, alongside the main.js file.

// shims-vue.d.ts

declare module '*.vue' {
  import Vue from 'vue'
  export default Vue
}

If you use lint-staged adjust your config to include Typescript files:

{
  "lint-staged": {
    // ... other config
    "*.{js,ts,vue}": "eslint --fix"
  }
}

That's really it, you are ready to use Typescript in your Vue app. Let's see it in action.

Migrate components to Typescript

To use Typescript in a component you have to change the language in the <script></script> tag to ts

<script lang="ts">
// ...your script
</script>

This only change gives you already some new things like Type Inference for your constants and variables which helps you prevent errors in your script. To use more of the powerful features Typescript offers we're gonna add some code to our Typescript component.

To make use of the Vue Types for component options, for example, we always have to use Vue.component or Vue.extend instead of a regular default export. This results in the following <script> setup

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({})
</script>

As a next step, we would like to annotate the return Types inside our methods and computed properties.

<script lang="ts">
export default Vue.extend({
  // ...
  computed: {
    fullname(): string {
      return `${this.firstname} ${this.lastname}`
    },
  },
  // ...
})
</script>

As you can see we added a type definition after the name of the computed property.

To go even further we can easily define types for more complex data like objects and use them in props. Let's assume we have the following setup of a prop called user which is set to required. As you can see other than that there is no further support to prevent errors in the code caused by wrong prop data passed into this component

<script>
export default {
  // ...
  props: {
    user: {
      type: Object
      required: true
    }
  }
  // ...
}
</script>

To improve this code with Typescript we could set up an interface that describes our user object in more detail. This will also give us hints and autocompletion in VS Code, which is pretty nice.

<script lang="ts">
import Vue, { PropType } from 'vue'

interface User {
  name: string,
  email: string,
  isAdmin: boolean
}

export default Vue.extend({
  // ...
  props: {
    user: {
      type: Object as PropType<User>
      required: true
      validator (user: User) {
        return !!user.email;
      }
    }
  }
  // ...
})
</script>

You can see that we first have to import PropType alongside Vue itself. After that, we define our interface for the prop user with three properties, name, email, and isAdmin. All these properties also get a type. Then in the prop definition, we can use the interface with the PropType inside of our regular (none Typescript) prop type definition.

Further, you can see, that we can use our interface in the validator function, where we make sure the prop validation only passes when the user has at least an email set.

To get a bit more information on how to use Typescript in Vue and use it in plugins head over to the official Typescript page in the Vue docs.

Extras

During my migration I was facing issues with the Vetur extension in VS Code showing me that the imported Vue component does not exist. Path alias was correctly set in tsconfig and webpack.config.js I had to change to relative paths, which sucks, but fixes the problem for now.

Closing thoughts

This post is not meant to be a Typescript tutorial itself but should help you kick off the migration to Typescript. As you could read above, you are not forced to migrate your whole codebase in one go. It is possible to migrate to Typescript on a component basis, the here shown config takes into consideration that Javascript and Typescript work along without problems. You can take as much time as you need to migrate your codebase, or just create new components in Typescript and leave the existing ones in Javascript. But believe me, once you tried it and got familiar, you don't want to go back 😎

Blog overview