0
# Custom Resolvers
1
2
Extensible resolver system for handling non-standard file types during TypeScript declaration generation, enabling support for custom file formats and preprocessing pipelines.
3
4
## Capabilities
5
6
### Resolver Interface
7
8
Core interface defining how custom resolvers integrate with the plugin's TypeScript compilation process.
9
10
```typescript { .api }
11
/**
12
* Custom resolver for transforming non-standard files to TypeScript declarations
13
*/
14
interface Resolver {
15
/** Unique name identifier (later resolvers with same name overwrite earlier ones) */
16
name: string;
17
18
/** Determine whether this resolver can handle the given file */
19
supports: (id: string) => void | boolean;
20
21
/** Transform source code to declaration file outputs */
22
transform: (payload: {
23
id: string;
24
code: string;
25
root: string;
26
outDir: string;
27
host: ts.CompilerHost;
28
program: ts.Program;
29
}) => MaybePromise<
30
| { outputs: { path: string; content: string }[]; emitSkipped?: boolean; diagnostics?: readonly ts.Diagnostic[] }
31
| { path: string; content: string }[]
32
>;
33
}
34
```
35
36
### Creating Custom Resolvers
37
38
Factory pattern for creating resolvers that handle specific file types or transformation requirements.
39
40
```typescript { .api }
41
/**
42
* Create a custom resolver for specific file types
43
* @param name - Unique identifier for the resolver
44
* @param supportsPattern - File extension or pattern matching function
45
* @param transformFn - Function to transform source to declarations
46
* @returns Configured resolver instance
47
*/
48
function createCustomResolver(
49
name: string,
50
supportsPattern: string | RegExp | ((id: string) => boolean),
51
transformFn: (payload: ResolverPayload) => ResolverResult
52
): Resolver;
53
54
interface ResolverPayload {
55
id: string;
56
code: string;
57
root: string;
58
outDir: string;
59
host: ts.CompilerHost;
60
program: ts.Program;
61
}
62
63
type ResolverResult = MaybePromise<
64
| { outputs: { path: string; content: string }[]; emitSkipped?: boolean; diagnostics?: readonly ts.Diagnostic[] }
65
| { path: string; content: string }[]
66
>;
67
```
68
69
**Usage Examples:**
70
71
```typescript
72
import dts from "vite-plugin-dts";
73
74
// Custom resolver for .yaml configuration files
75
const yamlResolver: Resolver = {
76
name: "yaml-config",
77
supports: (id) => id.endsWith(".yaml") || id.endsWith(".yml"),
78
transform: async ({ id, code, outDir, root }) => {
79
// Parse YAML and generate TypeScript definitions
80
const configName = path.basename(id, path.extname(id));
81
const capitalizedName = configName.charAt(0).toUpperCase() + configName.slice(1);
82
83
const declaration = `
84
export interface ${capitalizedName}Config {
85
[key: string]: any;
86
}
87
88
declare const config: ${capitalizedName}Config;
89
export default config;
90
`;
91
92
return [{
93
path: path.resolve(outDir, `${configName}.d.ts`),
94
content: declaration
95
}];
96
}
97
};
98
99
// Custom resolver for GraphQL schema files
100
const graphqlResolver: Resolver = {
101
name: "graphql-schema",
102
supports: (id) => id.endsWith(".graphql") || id.endsWith(".gql"),
103
transform: async ({ id, code, outDir }) => {
104
// Generate TypeScript types from GraphQL schema
105
const schemaName = path.basename(id, path.extname(id));
106
107
// Simple example - in practice you'd parse the GraphQL schema
108
const typeDefinitions = `
109
export interface ${schemaName}Schema {
110
query: Query;
111
mutation?: Mutation;
112
subscription?: Subscription;
113
}
114
115
interface Query {
116
[field: string]: any;
117
}
118
119
interface Mutation {
120
[field: string]: any;
121
}
122
123
interface Subscription {
124
[field: string]: any;
125
}
126
`;
127
128
return {
129
outputs: [{
130
path: path.resolve(outDir, `${schemaName}.schema.d.ts`),
131
content: typeDefinitions
132
}],
133
emitSkipped: false
134
};
135
}
136
};
137
138
// Use custom resolvers in plugin configuration
139
export default defineConfig({
140
plugins: [
141
dts({
142
resolvers: [yamlResolver, graphqlResolver]
143
})
144
]
145
});
146
```
147
148
### Resolver Registration and Management
149
150
System for managing multiple resolvers and handling conflicts between resolver names.
151
152
```typescript { .api }
153
/**
154
* Parse and deduplicate resolvers by name
155
* Later resolvers with the same name overwrite earlier ones
156
* @param resolvers - Array of resolver instances
157
* @returns Deduplicated array of resolvers
158
*/
159
function parseResolvers(resolvers: Resolver[]): Resolver[];
160
```
161
162
**Usage Examples:**
163
164
```typescript
165
import { parseResolvers } from "vite-plugin-dts/resolvers";
166
167
// Multiple resolvers with potential name conflicts
168
const resolvers = [
169
{ name: "json", supports: () => false, transform: () => [] }, // Will be overwritten
170
{ name: "custom", supports: () => true, transform: () => [] },
171
{ name: "json", supports: (id) => id.endsWith(".json"), transform: () => [] } // Overwrites first
172
];
173
174
const finalResolvers = parseResolvers(resolvers);
175
// Result: [custom resolver, second json resolver]
176
177
// Use in plugin configuration
178
dts({
179
resolvers: finalResolvers
180
});
181
```
182
183
### Advanced Resolver Patterns
184
185
Complex resolver implementations for sophisticated file transformation scenarios.
186
187
```typescript { .api }
188
// Multi-output resolver (generates multiple declaration files from one source)
189
const multiOutputResolver: Resolver = {
190
name: "multi-output",
191
supports: (id) => id.endsWith(".multi.ts"),
192
transform: async ({ id, code, outDir, program }) => {
193
// Generate multiple declaration files from analysis
194
return {
195
outputs: [
196
{
197
path: path.resolve(outDir, "types.d.ts"),
198
content: "export interface GeneratedType {}"
199
},
200
{
201
path: path.resolve(outDir, "constants.d.ts"),
202
content: "export declare const GENERATED_CONSTANT: string;"
203
}
204
],
205
emitSkipped: false
206
};
207
}
208
};
209
210
// Conditional resolver with diagnostics
211
const conditionalResolver: Resolver = {
212
name: "conditional",
213
supports: (id) => id.includes(".conditional."),
214
transform: async ({ id, code, host, program }) => {
215
const diagnostics: ts.Diagnostic[] = [];
216
217
try {
218
// Validate file content
219
if (!code.includes("export")) {
220
diagnostics.push({
221
file: program.getSourceFile(id),
222
start: 0,
223
length: code.length,
224
messageText: "File must contain exports",
225
category: ts.DiagnosticCategory.Warning,
226
code: 9999
227
} as ts.Diagnostic);
228
}
229
230
const declaration = `// Generated from ${path.basename(id)}\nexport {};`;
231
232
return {
233
outputs: [{
234
path: id.replace(/\.(conditional\.\w+)$/, ".d.ts"),
235
content: declaration
236
}],
237
emitSkipped: false,
238
diagnostics
239
};
240
} catch (error) {
241
return {
242
outputs: [],
243
emitSkipped: true,
244
diagnostics: [{
245
file: undefined,
246
start: 0,
247
length: 0,
248
messageText: `Resolver error: ${error.message}`,
249
category: ts.DiagnosticCategory.Error,
250
code: 1
251
} as ts.Diagnostic]
252
};
253
}
254
}
255
};
256
```
257
258
### TypeScript Program Integration
259
260
Advanced resolvers that leverage the TypeScript compiler's program and host for sophisticated analysis.
261
262
```typescript { .api }
263
// Program-aware resolver
264
const programAwareResolver: Resolver = {
265
name: "program-aware",
266
supports: (id) => id.endsWith(".analyzed.ts"),
267
transform: async ({ id, code, host, program, root, outDir }) => {
268
// Access TypeScript compiler services
269
const sourceFile = program.getSourceFile(id);
270
if (!sourceFile) {
271
return { outputs: [], emitSkipped: true };
272
}
273
274
// Analyze the source file using TypeScript APIs
275
const typeChecker = program.getTypeChecker();
276
const exports: string[] = [];
277
278
// Walk the AST to find exports
279
ts.forEachChild(sourceFile, (node) => {
280
if (ts.isExportDeclaration(node) || ts.isFunctionDeclaration(node) && node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
281
// Extract export information
282
if (ts.isFunctionDeclaration(node) && node.name) {
283
const symbol = typeChecker.getSymbolAtLocation(node.name);
284
if (symbol) {
285
exports.push(`export declare function ${symbol.getName()}(): any;`);
286
}
287
}
288
}
289
});
290
291
const content = exports.length > 0
292
? exports.join('\n')
293
: '// No exports found\nexport {};';
294
295
return [{
296
path: path.resolve(outDir, path.relative(root, id).replace(/\.analyzed\.ts$/, '.d.ts')),
297
content
298
}];
299
}
300
};
301
```
302
303
### Error Handling and Diagnostics
304
305
Best practices for handling errors and providing useful diagnostics in custom resolvers.
306
307
```typescript { .api }
308
const robustResolver: Resolver = {
309
name: "robust",
310
supports: (id) => id.endsWith(".custom"),
311
transform: async ({ id, code }) => {
312
try {
313
// Attempt transformation
314
const result = await processCustomFile(code);
315
316
return {
317
outputs: [{
318
path: id.replace(/\.custom$/, '.d.ts'),
319
content: result
320
}],
321
emitSkipped: false
322
};
323
} catch (error) {
324
// Provide helpful diagnostics on failure
325
console.warn(`Custom resolver failed for ${id}: ${error.message}`);
326
327
return {
328
outputs: [{
329
path: id.replace(/\.custom$/, '.d.ts'),
330
content: `// Error processing ${path.basename(id)}\n// ${error.message}\nexport {};`
331
}],
332
emitSkipped: false,
333
diagnostics: [{
334
file: undefined,
335
start: 0,
336
length: 0,
337
messageText: `Custom resolver error: ${error.message}`,
338
category: ts.DiagnosticCategory.Warning,
339
code: 9999
340
} as ts.Diagnostic]
341
};
342
}
343
}
344
};
345
```