An unplugin that generates declaration files (*.d.ts
) from .ts(x)
or .vue
source files when using library mode.
English | 中文
pnpm i -D unplugin-dts
Previous only for vite (do not recommend):
pnpm i -D vite-plugin-dts
In vite.config.ts
:
import { resolve } from 'path'
import { defineConfig } from 'vite'
import dts from 'unplugin-dts/vite'
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'MyLib',
formats: ['es'],
fileName: 'my-lib',
}
},
plugins: [dts()],
})
In rollup.config.mjs
:
import { defineConfig } from 'rollup'
import typescript from '@rollup/plugin-typescript'
import dts from 'unplugin-dts/rollup'
export default defineConfig({
input: {
index: './src/index.ts',
},
output: [
{
dir: 'dist',
exports: 'named',
format: 'esm',
},
],
plugins: [typescript(), dts()],
})
In rolldown.config.mjs
:
import { defineConfig } from 'rolldown'
import dts from 'unplugin-dts/rolldown'
export default defineConfig({
input: {
index: './src/index.ts',
},
output: [
{
dir: 'dist',
exports: 'named',
format: 'esm',
},
],
plugins: [dts()],
})
In webpack.config.js
:
import { resolve } from 'node:path'
import dts from 'unplugin-dts/webpack'
export default {
entry: {
index: './src/index.ts',
},
output: {
path: resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
plugins: [dts()],
}
In rspack.config.mjs
:
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { defineConfig } from '@rspack/cli'
import dts from 'unplugin-dts/rspack'
const rootDir = resolve(fileURLToPath(import.meta.url), '..')
export default defineConfig({
entry: {
index: './src/index.ts',
},
output: {
path: resolve(rootDir, 'dist'),
},
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: {
syntax: 'ecmascript',
},
},
},
},
],
},
{
test: /\.ts$/,
use: [
{
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: {
syntax: 'typescript',
decorators: true,
},
},
},
},
],
},
],
},
plugins: [dts()],
})
In your build script:
import { build } from 'esbuild'
import dts from 'unplugin-dts/esbuild'
await build({
entryPoints: ['src/index.ts'],
format: 'esm',
outdir: 'dist',
bundle: true,
plugins: [dts()],
})
Fortunately, with the help of API Extractor, the plugin can bundle all types into a single file. You just need to install @microsoft/api-extractor
and set bundleTypes: true
:
pnpm i -D @microsoft/api-extractor
{
plugins: [dts({ bundleTypes: true })]
}
If you start with official Vite template, you should specify the tsconfigPath
:
{
plugins: [dts({ tsconfigPath: './tsconfig.app.json' })]
}
Here are some FAQ's and solutions.
node_modules
This is an existing TypeScript issue where TypeScript infers types from packages located in node_modules
through soft links (pnpm). A workaround is to add baseUrl
to your tsconfig.json
and specify the paths
for these packages:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"third-lib": ["node_modules/third-lib"]
}
}
}
Internal Error
occurs when using bundleTypes: true
Refer to this issue, it's due to a limitation of @microsoft/api-extractor
or TypeScript resolver.
The main reason is that baseUrl
is specified in tsconfig.json
and non-standard paths are used directly when imported.
For example: baseUrl: 'src'
is specified and importing from <root>/src/components/index.ts
in <root>/src/index.ts
, and import 'components'
is used instead of import './components'
.
Currently, you need to avoid the above situation, or use aliases instead (with the paths
option).
This is likely due to incorrect configuration of the include
property in your default tsconfig.json
.
Due to some limitations, the plugin relies on the top-level tsconfig.json
to resolve the files to include. Therefore, you need to specify the correct include
property in the top-level tsconfig.json
, or you can specify a configuration file path with the correct include
property using the tsconfigPath
option of the plugin. For example, in the Vite initial template, it is tsconfig.app.json
.
You can refer to this comment.
import type ts from 'typescript'
import type { IExtractorConfigPrepareOptions, IExtractorInvokeOptions } from '@microsoft/api-extractor'
type MaybePromise<T> = T | Promise<T>
export type RollupConfig = Omit<
IExtractorConfigPrepareOptions['configObject'],
| 'projectFolder'
| 'mainEntryPointFilePath'
| 'compiler'
| 'dtsRollup'
| 'bundledPackages'
>
export interface ResolverTransformOutput {
path: string,
content: string
}
export interface Resolver {
/**
* The name of the resolver
*
* The later resolver with the same name will overwrite the earlier
*/
name: string,
/**
* Determine whether the resolver supports the file
*/
supports: (id: string) => void | boolean,
/**
* Transform source to declaration files
*
* Note that the path of the returns should base on `outDir`, or relative path to `root`
*/
transform: (payload: {
id: string,
code: string,
root: string,
outDir: string,
host: ts.CompilerHost,
program: ts.Program
}) => MaybePromise<ResolverTransformOutput[] | {
outputs: ResolverTransformOutput[],
emitSkipped?: boolean,
diagnostics?: readonly ts.Diagnostic[]
}>
}
export interface PluginOptions {
/**
* Specify root directory.
*
* Defaults to the 'root' of the Vite config, or `process.cwd()` if using Rollup.
*/
root?: string,
/**
* Output directory for declaration files.
*
* Can be an array to output to multiple directories.
*
* Defaults to 'build.outDir' of the Vite config, or `outDir` of tsconfig.json if using Rollup.
*/
outDir?: string | string[],
/**
* Override root path of entry files (useful in monorepos).
*
* The output path of each file will be calculated based on the value provided.
*
* The default is the smallest public path for all source files.
*/
entryRoot?: string,
/**
* Restrict declaration files output to `outDir`.
*
* If true, generated declaration files outside `outDir` will be ignored.
*
* @default true
*/
strictOutput?: boolean,
/**
* Override compilerOptions.
*
* @default null
*/
compilerOptions?: ts.CompilerOptions | null,
/**
* Specify tsconfig.json path.
*
* Plugin resolves `include` and `exclude` globs from tsconfig.json.
*
* If not specified, plugin will find config file from root.
*/
tsconfigPath?: string,
/**
* Specify custom resolvers.
*
* @default []
*/
resolvers?: Resolver[],
/**
* Parsing `paths` of tsconfig.json to aliases.
*
* Note that these aliases only use for declaration files.
*
* @default true
* @remarks Only use first replacement of each path.
*/
pathsToAliases?: boolean,
/**
* Set which paths should be excluded when transforming aliases.
*
* @default []
*/
aliasesExclude?: (string | RegExp)[],
/**
* Whether to transform file names ending in '.vue.d.ts' to '.d.ts'.
*
* If there is a duplicate name after transform, it will fall back to the original name.
*
* @default false
*/
cleanVueFileName?: boolean,
/**
* Whether to transform dynamic imports to static (eg `import('vue').DefineComponent` to `import { DefineComponent } from 'vue'`).
*
* Value is forced to `true` when `rollupTypes` is `true`.
*
* @default false
*/
staticImport?: boolean,
/**
* Override `include` glob (relative to root).
*
* Defaults to `include` property of tsconfig.json (relative to tsconfig.json located).
*/
include?: string | string[],
/**
* Override `exclude` glob.
*
* Defaults to `exclude` property of tsconfig.json or `'node_modules/**'` if not supplied.
*/
exclude?: string | string[],
/**
* Whether to remove `import 'xxx'`.
*
* @default true
*/
clearPureImport?: boolean,
/**
* Whether to generate types entry file(s).
*
* When `true`, uses package.json `types` property if it exists or `${outDir}/index.d.ts`.
*
* Value is forced to `true` when `rollupTypes` is `true`.
*
* @default false
*/
insertTypesEntry?: boolean,
/**
* Rollup type declaration files after emitting them.
*
* Powered by `@microsoft/api-extractor` - time-intensive operation.
*
* @default false
*/
bundleTypes?: boolean | {
/**
* Override the config of `@microsoft/api-extractor`.
*
* @default {}
* @see https://api-extractor.com/pages/setup/configure_api_report/
*/
extractorConfig?: BundleConfig,
/**
* Bundled packages for `@microsoft/api-extractor`.
*
* @default []
* @see https://api-extractor.com/pages/configs/api-extractor_json/#bundledpackages
*/
bundledPackages?: string[],
/**
* Override the invoke options of `@microsoft/api-extractor`.
*
* @default {}
* @see https://api-extractor.com/pages/setup/invoking/#invoking-from-a-build-script
*/
invokeOptions?: IExtractorInvokeOptions
},
/**
* Whether to copy .d.ts source files to `outDir`.
*
* @default false
* @remarks Before 2.0, the default was `true`.
*/
copyDtsFiles?: boolean,
/**
* Whether to emit declaration files only.
*
* When `true`, all the original outputs of vite (rollup) will be force removed.
*
* @default false
*/
declarationOnly?: boolean,
/**
* Hook called after diagnostic is emitted.
*
* According to the `diagnostics.length`, you can judge whether there is any type error.
*
* @default () => {}
*/
afterDiagnostic?: (diagnostics: readonly ts.Diagnostic[]) => MaybePromise<void>,
/**
* Hook called prior to writing each declaration file.
*
* This allows you to transform the path or content.
*
* The file will be skipped when the return value `false` or `Promise<false>`.
*
* @default () => {}
*/
beforeWriteFile?: (
filePath: string,
content: string
) => MaybePromise<
| void
| false
| {
filePath?: string,
content?: string
}
>,
/**
* Hook called after rolling up declaration files.
*
* @default () => {}
*/
afterRollup?: (result: ExtractorResult) => MaybePromise<void>,
/**
* Hook called after all declaration files are written.
*
* It will be received a map (path -> content) that records those emitted files.
*
* @default () => {}
*/
afterBuild?: (emittedFiles: Map<string, string>) => MaybePromise<void>
}
Thanks for all the contributions!
Clone and run the following script (see demo series scripts in root package.json
):
pnpm run demo:ts-rollup
pnpm run demo:react-vite
pnpm run demo:vue-rspack
Then check examples/*/types
.
There are also many cases under examples
.
A real project using this plugin: Vexip UI.
MIT License.