Vue 3 with TypeScript setup

This page collects the setup steps for using TypeScript in a Vue 3 project. It covers the project scaffold, the editor configuration, where type checking actually runs, and the two places where types are declared most often, props and emits. The compiler configuration is at the end.

Start from create-vue

create-vue is the official scaffolding tool, and it asks whether the project should use TypeScript. Taking that option gives a Vite powered project with the type declarations, the tsconfig files and the type checking script already wired, which is less work than retrofitting TypeScript into a running Vue project.

Editor setup

Visual Studio Code is the recommended editor, and TypeScript support inside single file components comes from the extension Vue - Official, which was previously named Volar. Install it and disable Vetur, because Vetur is the Vue 2 extension and the two conflict in a Vue 3 project.

The extension is what gives instant feedback while you type, and it is worth the setup time because of the next point. JetBrains IDEs also ship Vue and TypeScript support, and since 2023.2 they bundle the Vue language server as well.

Type checking does not run in the bundler

A Vite based setup transpiles and bundles, it does not type check. That is why the dev server stays fast, and the trade off is that a type error does not stop the dev server or the build. The command line check is vue-tsc, which wraps tsc and adds support for single file components.

npx vue-tsc --noEmit

Run that the same way you would run a test suite, so that a type error fails the pipeline instead of reaching a reviewer. Watch mode can run alongside the dev server, and vite-plugin-checker runs the same checks in a worker thread if you prefer them in the dev loop.

Enabling TypeScript in a component

Add lang="ts" to the script block. With lang="ts" present, the template expressions are type checked as well, which is the part people often miss.

<script setup lang="ts">
import { ref } from 'vue'

const count = ref(1)
</script>

<template>
  {{ count.toFixed(2) }}
</template>

The template also accepts inline casts when a value has a wider type than the expression needs, for example (value as number).toFixed(2).

Typing props and emits

In <script setup>, props and emits are declared with the compiler macros, and the type argument is what both the script and the template see.

<script setup lang="ts">
const props = defineProps<{
  title: string
  count?: number
}>()

const emit = defineEmits<{
  (event: 'submit', id: number): void
}>()
</script>

A type only declaration cannot be resolved when the type comes from another module at runtime, in which case the prop needs a runtime declaration with PropType instead of the type argument. For the common case of a type declared next to the component, the type argument is the shorter form and there is no reason to reach for the runtime version.

The compiler configuration

The tsconfig.json in a create-vue project extends @vue/tsconfig and uses project references so that app code and test code get different global types. Three options are worth knowing when you configure it by hand.

  • isolatedModules is set to true, because Vite transpiles file by file and cannot rely on cross file type information. verbatimModuleSyntax is a superset of it and is what @vue/tsconfig uses.
  • If you use the Options API, strict has to be on, or at least noImplicitThis, otherwise this inside component options is typed as any and you lose the checking you set the project up for.
  • An alias configured in the bundler has to be declared again for TypeScript, for example the @/* alias that create-vue configures by default maps to compilerOptions.paths.

Type errors then surface in three places, the editor while you type, vue-tsc on the command line, and the template expressions once lang="ts" is present. The setup is complete when all three agree, and if only the editor is happy, the command line check is the one that will fail in the pipeline.

You also might like

About this post

This post was written entirely by a human, without AI assistance.