A Vite plugin that generates TypeScript declaration files from source files in library mode
—
Extensible resolver system for handling non-standard file types during TypeScript declaration generation, enabling support for custom file formats and preprocessing pipelines.
Core interface defining how custom resolvers integrate with the plugin's TypeScript compilation process.
/**
* Custom resolver for transforming non-standard files to TypeScript declarations
*/
interface Resolver {
/** Unique name identifier (later resolvers with same name overwrite earlier ones) */
name: string;
/** Determine whether this resolver can handle the given file */
supports: (id: string) => void | boolean;
/** Transform source code to declaration file outputs */
transform: (payload: {
id: string;
code: string;
root: string;
outDir: string;
host: ts.CompilerHost;
program: ts.Program;
}) => MaybePromise<
| { outputs: { path: string; content: string }[]; emitSkipped?: boolean; diagnostics?: readonly ts.Diagnostic[] }
| { path: string; content: string }[]
>;
}Factory pattern for creating resolvers that handle specific file types or transformation requirements.
/**
* Create a custom resolver for specific file types
* @param name - Unique identifier for the resolver
* @param supportsPattern - File extension or pattern matching function
* @param transformFn - Function to transform source to declarations
* @returns Configured resolver instance
*/
function createCustomResolver(
name: string,
supportsPattern: string | RegExp | ((id: string) => boolean),
transformFn: (payload: ResolverPayload) => ResolverResult
): Resolver;
interface ResolverPayload {
id: string;
code: string;
root: string;
outDir: string;
host: ts.CompilerHost;
program: ts.Program;
}
type ResolverResult = MaybePromise<
| { outputs: { path: string; content: string }[]; emitSkipped?: boolean; diagnostics?: readonly ts.Diagnostic[] }
| { path: string; content: string }[]
>;Usage Examples:
import dts from "vite-plugin-dts";
// Custom resolver for .yaml configuration files
const yamlResolver: Resolver = {
name: "yaml-config",
supports: (id) => id.endsWith(".yaml") || id.endsWith(".yml"),
transform: async ({ id, code, outDir, root }) => {
// Parse YAML and generate TypeScript definitions
const configName = path.basename(id, path.extname(id));
const capitalizedName = configName.charAt(0).toUpperCase() + configName.slice(1);
const declaration = `
export interface ${capitalizedName}Config {
[key: string]: any;
}
declare const config: ${capitalizedName}Config;
export default config;
`;
return [{
path: path.resolve(outDir, `${configName}.d.ts`),
content: declaration
}];
}
};
// Custom resolver for GraphQL schema files
const graphqlResolver: Resolver = {
name: "graphql-schema",
supports: (id) => id.endsWith(".graphql") || id.endsWith(".gql"),
transform: async ({ id, code, outDir }) => {
// Generate TypeScript types from GraphQL schema
const schemaName = path.basename(id, path.extname(id));
// Simple example - in practice you'd parse the GraphQL schema
const typeDefinitions = `
export interface ${schemaName}Schema {
query: Query;
mutation?: Mutation;
subscription?: Subscription;
}
interface Query {
[field: string]: any;
}
interface Mutation {
[field: string]: any;
}
interface Subscription {
[field: string]: any;
}
`;
return {
outputs: [{
path: path.resolve(outDir, `${schemaName}.schema.d.ts`),
content: typeDefinitions
}],
emitSkipped: false
};
}
};
// Use custom resolvers in plugin configuration
export default defineConfig({
plugins: [
dts({
resolvers: [yamlResolver, graphqlResolver]
})
]
});System for managing multiple resolvers and handling conflicts between resolver names.
/**
* Parse and deduplicate resolvers by name
* Later resolvers with the same name overwrite earlier ones
* @param resolvers - Array of resolver instances
* @returns Deduplicated array of resolvers
*/
function parseResolvers(resolvers: Resolver[]): Resolver[];Usage Examples:
import { parseResolvers } from "vite-plugin-dts/resolvers";
// Multiple resolvers with potential name conflicts
const resolvers = [
{ name: "json", supports: () => false, transform: () => [] }, // Will be overwritten
{ name: "custom", supports: () => true, transform: () => [] },
{ name: "json", supports: (id) => id.endsWith(".json"), transform: () => [] } // Overwrites first
];
const finalResolvers = parseResolvers(resolvers);
// Result: [custom resolver, second json resolver]
// Use in plugin configuration
dts({
resolvers: finalResolvers
});Complex resolver implementations for sophisticated file transformation scenarios.
// Multi-output resolver (generates multiple declaration files from one source)
const multiOutputResolver: Resolver = {
name: "multi-output",
supports: (id) => id.endsWith(".multi.ts"),
transform: async ({ id, code, outDir, program }) => {
// Generate multiple declaration files from analysis
return {
outputs: [
{
path: path.resolve(outDir, "types.d.ts"),
content: "export interface GeneratedType {}"
},
{
path: path.resolve(outDir, "constants.d.ts"),
content: "export declare const GENERATED_CONSTANT: string;"
}
],
emitSkipped: false
};
}
};
// Conditional resolver with diagnostics
const conditionalResolver: Resolver = {
name: "conditional",
supports: (id) => id.includes(".conditional."),
transform: async ({ id, code, host, program }) => {
const diagnostics: ts.Diagnostic[] = [];
try {
// Validate file content
if (!code.includes("export")) {
diagnostics.push({
file: program.getSourceFile(id),
start: 0,
length: code.length,
messageText: "File must contain exports",
category: ts.DiagnosticCategory.Warning,
code: 9999
} as ts.Diagnostic);
}
const declaration = `// Generated from ${path.basename(id)}\nexport {};`;
return {
outputs: [{
path: id.replace(/\.(conditional\.\w+)$/, ".d.ts"),
content: declaration
}],
emitSkipped: false,
diagnostics
};
} catch (error) {
return {
outputs: [],
emitSkipped: true,
diagnostics: [{
file: undefined,
start: 0,
length: 0,
messageText: `Resolver error: ${error.message}`,
category: ts.DiagnosticCategory.Error,
code: 1
} as ts.Diagnostic]
};
}
}
};Advanced resolvers that leverage the TypeScript compiler's program and host for sophisticated analysis.
// Program-aware resolver
const programAwareResolver: Resolver = {
name: "program-aware",
supports: (id) => id.endsWith(".analyzed.ts"),
transform: async ({ id, code, host, program, root, outDir }) => {
// Access TypeScript compiler services
const sourceFile = program.getSourceFile(id);
if (!sourceFile) {
return { outputs: [], emitSkipped: true };
}
// Analyze the source file using TypeScript APIs
const typeChecker = program.getTypeChecker();
const exports: string[] = [];
// Walk the AST to find exports
ts.forEachChild(sourceFile, (node) => {
if (ts.isExportDeclaration(node) || ts.isFunctionDeclaration(node) && node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
// Extract export information
if (ts.isFunctionDeclaration(node) && node.name) {
const symbol = typeChecker.getSymbolAtLocation(node.name);
if (symbol) {
exports.push(`export declare function ${symbol.getName()}(): any;`);
}
}
}
});
const content = exports.length > 0
? exports.join('\n')
: '// No exports found\nexport {};';
return [{
path: path.resolve(outDir, path.relative(root, id).replace(/\.analyzed\.ts$/, '.d.ts')),
content
}];
}
};Best practices for handling errors and providing useful diagnostics in custom resolvers.
const robustResolver: Resolver = {
name: "robust",
supports: (id) => id.endsWith(".custom"),
transform: async ({ id, code }) => {
try {
// Attempt transformation
const result = await processCustomFile(code);
return {
outputs: [{
path: id.replace(/\.custom$/, '.d.ts'),
content: result
}],
emitSkipped: false
};
} catch (error) {
// Provide helpful diagnostics on failure
console.warn(`Custom resolver failed for ${id}: ${error.message}`);
return {
outputs: [{
path: id.replace(/\.custom$/, '.d.ts'),
content: `// Error processing ${path.basename(id)}\n// ${error.message}\nexport {};`
}],
emitSkipped: false,
diagnostics: [{
file: undefined,
start: 0,
length: 0,
messageText: `Custom resolver error: ${error.message}`,
category: ts.DiagnosticCategory.Warning,
code: 9999
} as ts.Diagnostic]
};
}
}
};Install with Tessl CLI
npx tessl i tessl/npm-vite-plugin-dts