10.2 · difficulty 2/4 · 10 min read
Generating Type Declaration Files
Publishing an npm package requires .d.ts type declarations — vite-plugin-dts generates them automatically, when to hand-write .d.ts files, and how tsconfig settings affect the output.
Vite 8.1Stable
Why you need .d.ts files
After a user runs pnpm add my-lib, their TypeScript project needs to know the library's types:
ts
import { Button } from "my-lib"
// TypeScript looks in node_modules/my-lib/ for type declarations
// Without .d.ts files, it errors: "Could not find a declaration file for module 'my-lib'"Type declaration files can come from:
- The package itself (declared in
package.jsonundertypes/exports["types"]) - A separate
@types/xxxpackage (e.g.,@types/react) - The package ships TypeScript source and the consumer configures path resolution in tsconfig
vite-plugin-dts
The easiest approach: use vite-plugin-dts to automatically generate .d.ts files at build time:
bash
pnpm add -D vite-plugin-dtsts
import { defineConfig } from "vite"
import dts from "vite-plugin-dts"
export default defineConfig({
plugins: [
dts({
// Reads configuration from tsconfig.json
// Places generated .d.ts files inside dist/
include: ["src"],
outDir: "dist",
// rollupTypes: merge all declarations into a single file (optional)
rollupTypes: true,
}),
],
build: {
lib: {
entry: "src/index.ts",
formats: ["es", "cjs"],
},
},
})Build output:
text
dist/
index.js ← ESM
index.cjs ← CJS
index.d.ts ← Type declarationsHow tsconfig settings affect type generation
json
{
"compilerOptions": {
"declaration": true, // Generate .d.ts files
"declarationDir": "dist", // Output directory for .d.ts files
"declarationMap": true, // Generate .d.ts.map (optional, enables Go to Definition)
"emitDeclarationOnly": true // Only emit .d.ts, no .js (use alongside Vite)
}
}Using tsc directly (without a plugin)
For simple libraries, you can call tsc directly in the build script:
json
{
"scripts": {
"build": "vite build && tsc --emitDeclarationOnly --outDir dist"
}
}When to write .d.ts files by hand
Sometimes you need to write type declarations manually:
- Virtual modules: your plugin generates virtual modules that need types
ts
declare module "virtual:my-data" {
export const items: string[]
export default items
}- Global type augmentation
ts
declare global {
interface Window {
__MY_LIB__: { version: string }
}
}
export {}- .vue / .svelte file types
ts
declare module "*.vue" {
import type { DefineComponent } from "vue"
const Component: DefineComponent
export default Component
}Verifying your type declarations
Before publishing, check with arethetypeswrong:
bash
npx arethetypeswrong@latest --packThis tool verifies that your package's types resolve correctly under different module systems.
Self-check
- What problem will TypeScript users encounter if a published npm package has no
.d.tsfiles? - What does
rollupTypes: truedo invite-plugin-dts? - What is a
.d.ts.mapfile used for? Do you need to include it when publishing an npm package? - In what situations should you write
.d.tsfiles by hand instead of generating them automatically?
ts
// Set up complete type generation for a React component library:
// - The library has three components: Button, Input, and Modal
// - Each component has a Props type
// - Consumers should get full type hints in their IDE
// 1. Write the export structure for src/index.ts
// 2. Write the dts configuration in vite.config.ts
// 3. Write the types/exports configuration in package.json
// src/index.ts
export { Button } from "./components/Button"
export type { ButtonProps } from "./components/Button"
export { Input } from "./components/Input"
export type { InputProps } from "./components/Input"
// TODO: ...