| |

Vue.js 98 🟢 Automated Type Definition Generation (vue-tsc)

⏱️ Estimated read: 9 min | |

TypeScript declarations for a Vue component library are not optional. A consumer that imports MyButton from the library needs to know the shape of its props, the events it emits, and the slots it exposes. Without .d.ts files, the consumer gets any, and the editor’s autocomplete and type checking stop at the import boundary. vue-tsc is the tool that generates those declarations. It is a wrapper around tsc that teaches the TypeScript compiler to understand .vue files, and it is the official recommendation in the Vue documentation for command-line type checking and type declaration generation .

This chapter covers the vue-tsc command, the two flags that control declaration output, the tsconfig.json requirements that make the generation work, and the integration with the library build pipeline that produces a publishable package.

Key point: vue-tsc is not a bundler. It type-checks and emits declarations. The JavaScript bundle is produced by Vite in library mode; the .d.ts files are produced by vue-tsc running as a separate step. The two outputs must be combined in the package.json types field for the consumer to see both.


Why vue-tsc exists

The .vue file problem. TypeScript’s tsc does not understand single-file components. A .vue file contains a <script> block, a <template> block, and a <style> block, and the relationship between them is defined by the Vue compiler, not by TypeScript. vue-tsc wraps tsc and inserts a Vue language plugin that transforms each .vue file into TypeScript virtual code before passing it to the compiler . The compiler then sees a normal TypeScript module with the component’s props, emits, and slots typed.

The declaration generation problem. vite-plugin-dts was the earlier standard for generating declarations in Vite library mode. It worked by extracting types from the source files and rolling them up. The Wikimedia Codex project migrated from vite-plugin-dts to vue-tsc for declaration generation, and the commit notes that vue-tsc now offers the feature that previously required the plugin . The vue-tsc approach is to run the actual TypeScript compiler with --declaration --emitDeclarationOnly, which produces declarations that are derived from the compiler’s own understanding of the types.

The type-checking problem. A library that ships declarations without checking them can ship declarations that are wrong. vue-tsc --noEmit runs the full type check without writing files, which is the CI step that ensures the declarations are valid before they are published .


a. The vue-tsc Command and Flags

vue-tsc accepts the same command-line arguments as tsc. The two flags that matter for declaration generation are --declaration and --emitDeclarationOnly.

# Type-check only (no files written)
vue-tsc --noEmit

# Generate declarations only
vue-tsc --declaration --emitDeclarationOnly

The --noEmit flag is the type-checking command. It reads the tsconfig.json, processes the .vue and .ts files, and reports type errors without producing output. The --declaration --emitDeclarationOnly combination produces the .d.ts files and nothing else. The output directory is controlled by compilerOptions.outDir in tsconfig.json .

The package.json scripts should separate the two operations:

{
  "scripts": {
    "type-check": "vue-tsc --noEmit",
    "build:types": "vue-tsc --declaration --emitDeclarationOnly"
  }
}

The type-check script runs in CI on every pull request. The build:types script runs as part of the release build, after the JavaScript bundle has been produced by Vite.


b. The tsconfig.json Requirements

vue-tsc reads the tsconfig.json in the project root, and several options determine whether the declaration generation works correctly.

The compilerOptions.declaration option must be true for the --declaration flag to produce output, and emitDeclarationOnly must be true for the --emitDeclarationOnly flag to suppress the JavaScript output . The outDir must be set to the directory where the declarations should be written, typically dist.

The include array must include the src directory so that the compiler finds the source files. The exclude array should exclude node_modules and dist to avoid processing the output directory .

The vueCompilerOptions.extensions field controls which file extensions the Vue language plugin processes. The default is ['.vue'], which is sufficient for a standard library .

A minimal tsconfig.json for a Vue library:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "declaration": true,
    "emitDeclarationOnly": true,
    "outDir": "dist",
    "skipLibCheck": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

The skipLibCheck: true option is common in library builds because it skips type checking of the .d.ts files in node_modules, which can be slow and are not the library author’s responsibility .


c. Integration with the Library Build

The build pipeline has two independent steps: the Vite library build and the vue-tsc declaration build. The Vite build produces the JavaScript bundle; vue-tsc produces the declarations. The package.json must point to both.

{
  "name": "my-ui",
  "type": "module",
  "main": "./dist/my-ui.cjs",
  "module": "./dist/my-ui.es.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/my-ui.es.js",
      "require": "./dist/my-ui.cjs"
    }
  },
  "files": ["dist"],
  "scripts": {
    "build:js": "vite build",
    "build:types": "vue-tsc --declaration --emitDeclarationOnly",
    "build": "pnpm build:js && pnpm build:types"
  }
}

The build script runs Vite first and vue-tsc second. The order matters only if the Vite build cleans the dist directory; if it does, the declarations must be generated after the clean. If Vite’s emptyOutDir is false, the order is not critical, but running the JavaScript build first is the conventional sequence.

The types field points to the entry declaration file. If the source has a single src/index.ts that re-exports every component, the generated dist/index.d.ts is the entry. If the source is structured as multiple files, the declarations follow the source structure, and the types field must point to the declaration that corresponds to the main and module entries.

For a component library that uses vite-plugin-dts alongside vue-tsc, the two tools would produce the same declarations twice. The current recommendation is to use vue-tsc directly and remove vite-plugin-dts, as the Wikimedia Codex migration did .


Complete Example Session

// ============================================
// PART 1: TSCONFIG.JSON
// ============================================
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "declaration": true,
    "emitDeclarationOnly": true,
    "outDir": "dist",
    "skipLibCheck": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}


// ============================================
// PART 2: SOURCE ENTRY POINT
// ============================================
// src/index.ts
export { default as MyButton } from './components/MyButton.vue'
export { default as MyCard } from './components/MyCard.vue'


// ============================================
// PART 3: PACKAGE.JSON SCRIPTS
// ============================================
{
  "scripts": {
    "type-check": "vue-tsc --noEmit",
    "build:js": "vite build",
    "build:types": "vue-tsc --declaration --emitDeclarationOnly",
    "build": "pnpm build:js && pnpm build:types"
  }
}


// ============================================
// PART 4: TYPE CHECK IN CI
// ============================================
// pnpm type-check
// Fails if any component has a type error


// ============================================
// PART 5: BUILD
// ============================================
// pnpm build
// 1. vite build → dist/my-ui.es.js, dist/my-ui.cjs
// 2. vue-tsc → dist/index.d.ts, dist/components/*.d.ts


// ============================================
// PART 6: PACKAGE.JSON TYPES FIELD
// ============================================
{
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/my-ui.es.js"
    }
  }
}


// ============================================
// PART 7: VERIFY THE DECLARATIONS
// ============================================
// ls dist/
// index.d.ts
// components/MyButton.vue.d.ts
// components/MyCard.vue.d.ts

The seven parts trace the configuration, the scripts, the CI check, the build order, and the verification.


Quick Reference

vue-tsc Commands

CommandPurpose
vue-tsc --noEmitType check only
vue-tsc --declaration --emitDeclarationOnlyGenerate declarations
vue-tsc --watchWatch mode

tsconfig.json Requirements

OptionValue
declarationtrue
emitDeclarationOnlytrue
outDirdist
include["src"]
exclude["node_modules", "dist"]
skipLibChecktrue (recommended)

Build Pipeline

StepToolOutput
1vite builddist/*.js
2vue-tsc --declarationdist/*.d.ts
3package.json typesPoints to dist/index.d.ts

Best Practices

✅ Do This:

// Separate type-check and build:types scripts
"type-check": "vue-tsc --noEmit",
"build:types": "vue-tsc --declaration --emitDeclarationOnly"          // ✅
// Set declaration and emitDeclarationOnly in tsconfig
"declaration": true,
"emitDeclarationOnly": true                                             // ✅
# Run type-check in CI before the build
pnpm type-check && pnpm build                                          // ✅

❌ Don’t Do This:

// Omit the types field in package.json
// Consumers get no type information                                        // ❌
// Use tsc directly on .vue files
tsc --declaration  // fails, tsc does not understand .vue               // ❌
// Set outDir to the same directory as the Vite output without checking
// Vite may clean dist and remove the declarations                         // ❌

Common Pitfalls

PitfallWhy It HappensFix
types not foundoutDir mismatchSet outDir: "dist" and types to dist/index.d.ts
Declarations missingdeclaration not trueSet in tsconfig.json
.vue files not processedvue-tsc not usedUse vue-tsc, not tsc
Type check is slowskipLibCheck not setSet skipLibCheck: true
Declarations deleted by ViteVite emptyOutDir cleans distRun vue-tsc after vite build

Real-World Examples

1. Type Check

vue-tsc --noEmit

2. Generate Declarations

vue-tsc --declaration --emitDeclarationOnly

3. Package Script

"build:types": "vue-tsc --declaration --emitDeclarationOnly"

4. tsconfig Declaration

"declaration": true, "emitDeclarationOnly": true

5. outDir

"outDir": "dist"

6. skipLibCheck

"skipLibCheck": true

7. types Field

"types": "./dist/index.d.ts"

8. Watch Mode

vue-tsc --watch

9. CI Check

pnpm type-check && pnpm build

10. Verify Output

ls dist/**/*.d.ts

Visual

The Build Pipeline

┌──────────────────────────────────────────────────────────────┐
│  SOURCE                                                      │
│  ├── src/index.ts                                           │
│  └── src/components/*.vue                                   │
│           │                                                  │
│     ┌─────┴─────┐                                           │
│     ▼           ▼                                           │
│  ┌──────┐   ┌──────┐                                       │
│  │ Vite │   │vue-tsc│                                       │
│  │ build│   │--decl│                                       │
│  └──┬───┘   └──┬───┘                                       │
│     ▼          ▼                                            │
│  ┌──────┐   ┌──────┐                                       │
│  │ .js  │   │ .d.ts│                                       │
│  │ .cjs │   │      │                                       │
│  └──────┘   └──────┘                                       │
│                                                              │
│  Both outputs go into dist/                                  │
│  package.json points to both                                 │
│                                                              │
└──────────────────────────────────────────────────────────────┘

vue-tsc vs tsc

┌──────────────────────────────────────────────────────────────┐
│  tsc                           vue-tsc                       │
│                                                              │
│  ┌─────────────────┐            ┌─────────────────┐         │
│  │ Understands .ts │            │ Understands .ts │         │
│  │ Does NOT        │            │ Understands     │         │
│  │ understand .vue │            │ .vue            │         │
│  └─────────────────┘            └─────────────────┘         │
│                                                              │
│  vue-tsc wraps tsc and inserts the Vue language plugin       │
│  .vue → TypeScript virtual code → tsc                        │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
Toolvue-tsc
Type checkvue-tsc --noEmit
Generate declarationsvue-tsc --declaration --emitDeclarationOnly
Output directorycompilerOptions.outDir
Required tsconfigdeclaration: true, emitDeclarationOnly: true
Build orderVite first, then vue-tsc
package.json fieldtypes points to dist/index.d.ts
CI checkpnpm type-check

Key takeaways:

  • vue-tsc is the official tool for type checking and declaration generation. It wraps tsc and inserts a Vue language plugin that transforms .vue files into TypeScript virtual code, allowing the compiler to understand the component’s props, emits, and slots .
  • The --noEmit flag is the type check; --declaration --emitDeclarationOnly is the declaration generation. The two operations are separate scripts. The type check runs in CI; the declaration generation runs as part of the release build.
  • The tsconfig.json must have declaration: true and emitDeclarationOnly: true. Without these, the flags do not produce the expected output. The outDir must match the directory that the package.json types field points to .
  • The build order is Vite first, vue-tsc second. Vite produces the JavaScript bundle; vue-tsc produces the declarations. If Vite’s emptyOutDir cleans the dist directory, the declarations must be generated after the clean.
  • vue-tsc replaces vite-plugin-dts for declaration generation. The Wikimedia Codex project migrated from vite-plugin-dts to vue-tsc because the latter now provides the same feature. The current recommendation is to use vue-tsc directly .

Remember: A Vue component library without type declarations is a library that TypeScript consumers cannot fully use. The vue-tsc tool is the bridge between the .vue source files and the .d.ts files that the consumer’s editor and compiler need. The build pipeline has two independent outputs—the JavaScript bundle from Vite and the declarations from vue-tsc—and both must be present in the published package for the library to be complete. The types field in package.json is the contract that connects them, and the vue-tsc --noEmit check in CI is the guarantee that the declarations are valid before they are published.

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!