What is vue-tsc, and why can't plain tsc type-check Vue single-file components and their templates?
answer
- tsc knows no .vue
- wrapper around tsc
- virtual TypeScript from SFCs
- same flags, extra files
basics
~20 sPlain tsc only understands TypeScript and JavaScript files, not .vue. vue-tsc wraps tsc with Vue's language plugin, which turns each SFC, template expressions included, into virtual TypeScript that tsc checks, and it accepts tsc's usual flags.
solid answer
~40 s`tsc` has no idea what a `.vue` file is: it cannot parse the `<template>` block or the `<script setup>` macros, so a project checked with `tsc` alone either ignores SFCs or needs a `declare module '*.vue'` shim that types every component the same generic way and never looks at templates. `vue-tsc` is Vue's wrapper around `tsc`: it reads `vueCompilerOptions` from `tsconfig.json`, creates a Vue language plugin, and transforms each `.vue` file into TypeScript **virtual code** before handing the program to `tsc`, so props, emits and every template expression are checked against real types. All `tsc` arguments work: `--noEmit` for checking, `--declaration --emitDeclarationOnly` to generate `.d.ts` files for a component library, and `--build` for project references. It runs the project's own `typescript` package and requires TypeScript 5.0 or newer.
code
vue · 9 lines<script setup lang="ts">
defineProps<{ total: number; currency: 'EUR' | 'USD' }>()
</script>
<template>
<!-- tsc never sees this file; vue-tsc reports that this comparison
has no overlap between '"EUR" | "USD"' and '"GBP"' -->
<strong v-if="currency === 'GBP'">{{ total.toFixed(2) }}</strong>
</template>go deeper
Remember that tsc cannot read .vue files and that vue-tsc is the drop-in wrapper that can, with the same flags.
Explain the virtual-code idea: vue-tsc turns each SFC, template included, into TypeScript that tsc checks, with errors mapped back to the .vue source.
Use vue-tsc for CI checks and library declarations, remove loose shims that hide errors, and keep its TypeScript version aligned with the editor's.
Decide how library typings are produced and verified, and treat the type-check toolchain as part of the release process rather than a developer convenience.
## The problem: tsc does not read SFCs A Vue **single-file component (SFC)** has a `<script>` or `<script setup>` block, a `<template>` block and usually `<style>`. The TypeScript compiler, `tsc`, only understands `.ts`, `.tsx`, `.js` and similar files. It cannot: - parse a `.vue` file at all; - understand compiler macros such as `defineProps<{ ... }>()`, which only exist inside `<script setup>`; - check the expressions in `<template>`, such as `{{ user.nmae }}` or `:count="'3'"`. Without help, `tsc` either ignores `.vue` files or fails to resolve imports of them. ## The old workaround: a wildcard shim Before the current tooling, projects added a declaration such as `declare module '*.vue'` exporting a generic component type. That makes imports of `.vue` files compile, but at a cost: - every SFC gets the same loose type, so passing a wrong prop from a `.ts` file is not caught; - the template is never type-checked; - the shim hides real problems, because the compiler thinks it knows what the module is. ## What vue-tsc does `vue-tsc` is published from the `vuejs/language-tools` repository and described as a `tsc` wrapper that enables the TypeScript compiler to understand `.vue` files. Its README lists three differences from `tsc`: 1. It reads **`vueCompilerOptions`** from `tsconfig.json`. 2. It creates a **Vue language plugin** to process `.vue` files. 3. It transforms `.vue` files into **TypeScript virtual code** before passing them to `tsc`. The virtual code is a TypeScript rendering of the whole component: the script block with macros resolved into typed declarations, and the template's bindings, props, events and slots written as TypeScript expressions against the component's real types. `tsc` checks that code, and errors are mapped back to the lines you wrote in the `.vue` file. ## What the checker sees in a template Once an SFC is virtual TypeScript, the template is checked like any other code: - **Interpolations** such as `{{ total.toFixed(2) }}` are checked against the types of `total`. - **Bindings** such as `:count="items.length"` are checked against the child component's declared prop types. - **Event handlers** such as `@select="onSelect"` are checked against the child's declared emits, so `onSelect`'s parameter must accept the emitted payload. - **Directives** such as `v-for` give their aliases real types, so `item` inside the loop has the array's element type. - **Refs** from `<script setup>` are unwrapped in the template exactly as at runtime, so `count` is a `number`, not a `Ref<number>`. None of this is visible to plain `tsc`, which is why a project that only runs `tsc` can pass while its templates are full of type errors. ## Using it | Goal | Command | |---|---| | Type-check only | `vue-tsc --noEmit` | | Generate declaration files for a library | `vue-tsc --declaration --emitDeclarationOnly` | | Check a solution with project references | `vue-tsc --build` | | Re-check on change | `vue-tsc --noEmit --watch` | Practical facts: - **Flags**: all `tsc` command-line arguments can be used directly. - **TypeScript version**: it runs the `typescript` package installed in your project, and requires 5.0 or newer. - **File types**: it processes the extensions in `vueCompilerOptions.extensions`, which defaults to `['.vue']`. - **Scope**: it checks what the selected `tsconfig` includes, so `.vue` files must be matched by its `include` patterns. ## Where it sits in the toolchain - The bundler compiles SFCs to JavaScript and strips types, without checking. - The editor extension uses the same Vue language core to show errors live. - `vue-tsc` is the command-line checker for scripts and CI, and the declaration generator for libraries. Because the editor tooling and `vue-tsc` share the same core, an error in one normally shows in the other; when they disagree, the cause is almost always a different TypeScript version, a different `tsconfig`, or different tool versions.
- How do you publish type declarations for a Vue component library written as SFCs?Run `vue-tsc --declaration --emitDeclarationOnly` (with an output directory configured in `tsconfig`). Because `vue-tsc` understands `.vue` files, it emits `.d.ts` files describing each component's props, emits and slots, which plain `tsc` cannot produce for SFCs. The JavaScript itself is still built by the bundler.
- Which TypeScript version does vue-tsc use, and why does that matter in CI?It resolves and runs the `typescript` package installed in the project, and needs 5.0 or newer. CI therefore checks with whatever version the lockfile pins, which may differ from the TypeScript an editor uses unless the editor is set to the workspace version. Different versions can report different errors.
saying these in an interview costs you the question
- tsc can check .vue files once lang="ts" is set on the script block.
- vue-tsc is a separate type checker that reimplements TypeScript.
- A declare module '*.vue' shim gives each component its real prop types.
- vue-tsc only checks script blocks, never template expressions.
- vue-tsc needs its own flags; tsc options like --noEmit do not apply.