0
# Custom Transformers
1
2
Experimental support for TypeScript transformers to modify the compilation process.
3
4
## Capabilities
5
6
### Transformer Interface
7
8
Define custom transformers that modify TypeScript compilation at different stages.
9
10
```typescript { .api }
11
/**
12
* Custom transformer configuration interface
13
*/
14
interface ICustomTransformer {
15
/** Transformers to run before TypeScript compilation */
16
before?: tsTypes.TransformerFactory<tsTypes.SourceFile>;
17
18
/** Transformers to run after TypeScript compilation */
19
after?: tsTypes.TransformerFactory<tsTypes.SourceFile>;
20
21
/** Transformers to run after declaration file generation */
22
afterDeclarations?: tsTypes.TransformerFactory<tsTypes.Bundle | tsTypes.SourceFile>;
23
}
24
25
/**
26
* Function that creates transformer configurations from language service
27
* @param ls - TypeScript language service instance
28
* @returns Transformer configuration or TypeScript's native CustomTransformers
29
*/
30
type TransformerFactoryCreator = (
31
ls: tsTypes.LanguageService
32
) => tsTypes.CustomTransformers | ICustomTransformer;
33
```
34
35
### Transformer Factory Creation
36
37
Create transformers that have access to the TypeScript program and type information.
38
39
```typescript { .api }
40
/**
41
* TypeScript's native transformer factory type
42
*/
43
type TransformerFactory<T extends tsTypes.Node> = (
44
context: tsTypes.TransformationContext
45
) => tsTypes.Transformer<T>;
46
47
/**
48
* TypeScript's transformation context
49
*/
50
interface TransformationContext {
51
/** Compiler options */
52
getCompilerOptions(): tsTypes.CompilerOptions;
53
54
/** Start lexical environment */
55
startLexicalEnvironment(): void;
56
57
/** End lexical environment and get hoisted declarations */
58
endLexicalEnvironment(): tsTypes.Statement[] | undefined;
59
60
/** Hoist function declaration */
61
hoistFunctionDeclaration(node: tsTypes.FunctionDeclaration): void;
62
63
/** Hoist variable declaration */
64
hoistVariableDeclaration(node: tsTypes.Identifier): void;
65
66
/** Request emit helper */
67
requestEmitHelper(helper: tsTypes.EmitHelper): void;
68
69
/** Read emit helper uniqueness */
70
readEmitHelpers(): tsTypes.EmitHelper[] | undefined;
71
72
/** Enable emit notification */
73
enableEmitNotification(kind: tsTypes.SyntaxKind): void;
74
75
/** Check if emit notification is enabled */
76
isEmitNotificationEnabled(node: tsTypes.Node): boolean;
77
78
/** Called when node is emitted */
79
onEmitNode(hint: tsTypes.EmitHint, node: tsTypes.Node, emitCallback: (hint: tsTypes.EmitHint, node: tsTypes.Node) => void): void;
80
81
/** Enable substitution */
82
enableSubstitution(kind: tsTypes.SyntaxKind): void;
83
84
/** Check if substitution is enabled */
85
isSubstitutionEnabled(node: tsTypes.Node): boolean;
86
87
/** Called when node is substituted */
88
onSubstituteNode(hint: tsTypes.EmitHint, node: tsTypes.Node): tsTypes.Node;
89
}
90
```
91
92
### Usage Examples
93
94
**Basic Transformer:**
95
96
```javascript
97
import typescript from 'rollup-plugin-typescript2';
98
99
// Simple transformer that adds console.log to functions
100
const addLoggingTransformer = (program) => {
101
return (context) => {
102
return (sourceFile) => {
103
function visit(node) {
104
if (typescript.isFunctionDeclaration(node) && node.name) {
105
// Add console.log at start of function
106
const logStatement = typescript.factory.createExpressionStatement(
107
typescript.factory.createCallExpression(
108
typescript.factory.createPropertyAccessExpression(
109
typescript.factory.createIdentifier('console'),
110
typescript.factory.createIdentifier('log')
111
),
112
undefined,
113
[typescript.factory.createStringLiteral(`Entering ${node.name.text}`)]
114
)
115
);
116
117
const updatedBody = typescript.factory.updateBlock(
118
node.body,
119
[logStatement, ...node.body.statements]
120
);
121
122
return typescript.factory.updateFunctionDeclaration(
123
node,
124
node.decorators,
125
node.modifiers,
126
node.asteriskToken,
127
node.name,
128
node.typeParameters,
129
node.parameters,
130
node.type,
131
updatedBody
132
);
133
}
134
return typescript.visitEachChild(node, visit, context);
135
}
136
return typescript.visitNode(sourceFile, visit);
137
};
138
};
139
};
140
141
// Use in plugin configuration
142
export default {
143
plugins: [
144
typescript({
145
transformers: [
146
(service) => ({
147
before: [addLoggingTransformer(service.getProgram())]
148
})
149
]
150
})
151
]
152
};
153
```
154
155
**Advanced Transformer with Type Information:**
156
157
```javascript
158
import keysTransformer from 'ts-transformer-keys/transformer';
159
160
// Using ts-transformer-keys for compile-time type key extraction
161
const keysTransformerFactory = (service) => {
162
const program = service.getProgram();
163
const typeChecker = program.getTypeChecker();
164
165
return {
166
before: [keysTransformer(program).before],
167
after: []
168
};
169
};
170
171
typescript({
172
transformers: [keysTransformerFactory]
173
})
174
```
175
176
### Transformer Categories
177
178
Transformers can be applied at different stages of the compilation process:
179
180
```typescript { .api }
181
/**
182
* Transformer execution stages
183
*/
184
interface TransformerStages {
185
/**
186
* Before TypeScript compilation
187
* - Operates on TypeScript AST before type checking
188
* - Can modify source code structure
189
* - Has access to full type information
190
*/
191
before: tsTypes.TransformerFactory<tsTypes.SourceFile>[];
192
193
/**
194
* After TypeScript compilation
195
* - Operates on JavaScript AST after compilation
196
* - Can modify generated JavaScript
197
* - Limited type information available
198
*/
199
after: tsTypes.TransformerFactory<tsTypes.SourceFile>[];
200
201
/**
202
* After declaration file generation
203
* - Operates on declaration files (.d.ts)
204
* - Can modify type definitions
205
* - Useful for documentation generation
206
*/
207
afterDeclarations: tsTypes.TransformerFactory<tsTypes.Bundle | tsTypes.SourceFile>[];
208
}
209
```
210
211
### Common Transformer Patterns
212
213
**Metadata Collection:**
214
215
```javascript
216
// Collect class metadata for dependency injection
217
const metadataTransformer = (program) => {
218
const typeChecker = program.getTypeChecker();
219
220
return (context) => {
221
return (sourceFile) => {
222
function visit(node) {
223
if (typescript.isClassDeclaration(node)) {
224
// Extract constructor parameter types
225
const constructor = node.members.find(
226
member => typescript.isConstructorDeclaration(member)
227
);
228
229
if (constructor) {
230
const paramTypes = constructor.parameters.map(param => {
231
const type = typeChecker.getTypeAtLocation(param);
232
return typeChecker.typeToString(type);
233
});
234
235
// Add metadata as static property
236
const metadataProperty = typescript.factory.createPropertyDeclaration(
237
undefined,
238
[typescript.factory.createModifier(typescript.SyntaxKind.StaticKeyword)],
239
'metadata',
240
undefined,
241
undefined,
242
typescript.factory.createArrayLiteralExpression(
243
paramTypes.map(type =>
244
typescript.factory.createStringLiteral(type)
245
)
246
)
247
);
248
249
return typescript.factory.updateClassDeclaration(
250
node,
251
node.decorators,
252
node.modifiers,
253
node.name,
254
node.typeParameters,
255
node.heritageClauses,
256
[...node.members, metadataProperty]
257
);
258
}
259
}
260
return typescript.visitEachChild(node, visit, context);
261
}
262
return typescript.visitNode(sourceFile, visit);
263
};
264
};
265
};
266
```
267
268
**Code Generation:**
269
270
```javascript
271
// Generate getter/setter methods for properties
272
const accessorTransformer = (program) => {
273
return (context) => {
274
return (sourceFile) => {
275
function visit(node) {
276
if (typescript.isClassDeclaration(node)) {
277
const newMembers = [];
278
279
for (const member of node.members) {
280
newMembers.push(member);
281
282
if (typescript.isPropertyDeclaration(member) && member.name) {
283
const propertyName = member.name.getText();
284
285
// Generate getter
286
const getter = typescript.factory.createGetAccessorDeclaration(
287
undefined,
288
undefined,
289
propertyName,
290
[],
291
undefined,
292
typescript.factory.createBlock([
293
typescript.factory.createReturnStatement(
294
typescript.factory.createPropertyAccessExpression(
295
typescript.factory.createThis(),
296
`_${propertyName}`
297
)
298
)
299
])
300
);
301
302
// Generate setter
303
const setter = typescript.factory.createSetAccessorDeclaration(
304
undefined,
305
undefined,
306
propertyName,
307
[typescript.factory.createParameterDeclaration(
308
undefined,
309
undefined,
310
undefined,
311
'value',
312
undefined,
313
member.type
314
)],
315
typescript.factory.createBlock([
316
typescript.factory.createExpressionStatement(
317
typescript.factory.createBinaryExpression(
318
typescript.factory.createPropertyAccessExpression(
319
typescript.factory.createThis(),
320
`_${propertyName}`
321
),
322
typescript.SyntaxKind.EqualsToken,
323
typescript.factory.createIdentifier('value')
324
)
325
)
326
])
327
);
328
329
newMembers.push(getter, setter);
330
}
331
}
332
333
return typescript.factory.updateClassDeclaration(
334
node,
335
node.decorators,
336
node.modifiers,
337
node.name,
338
node.typeParameters,
339
node.heritageClauses,
340
newMembers
341
);
342
}
343
return typescript.visitEachChild(node, visit, context);
344
}
345
return typescript.visitNode(sourceFile, visit);
346
};
347
};
348
};
349
```
350
351
### Integration with TypeScript Language Service
352
353
Transformers have full access to TypeScript's language service capabilities:
354
355
```typescript { .api }
356
/**
357
* Language service features available to transformers
358
*/
359
interface LanguageServiceFeatures {
360
/** Get the TypeScript program */
361
getProgram(): tsTypes.Program;
362
363
/** Get type checker for type analysis */
364
getTypeChecker(): tsTypes.TypeChecker;
365
366
/** Get semantic diagnostics */
367
getSemanticDiagnostics(fileName: string): tsTypes.Diagnostic[];
368
369
/** Get syntactic diagnostics */
370
getSyntacticDiagnostics(fileName: string): tsTypes.Diagnostic[];
371
372
/** Get quick info at position */
373
getQuickInfoAtPosition(fileName: string, position: number): tsTypes.QuickInfo | undefined;
374
375
/** Get definitions at position */
376
getDefinitionAtPosition(fileName: string, position: number): tsTypes.DefinitionInfo[] | undefined;
377
}
378
```
379
380
**Type-Aware Transformer:**
381
382
```javascript
383
const typeAwareTransformer = (service) => {
384
const program = service.getProgram();
385
const typeChecker = program.getTypeChecker();
386
387
return {
388
before: [(context) => {
389
return (sourceFile) => {
390
function visit(node) {
391
if (typescript.isCallExpression(node)) {
392
// Get type information for function calls
393
const signature = typeChecker.getResolvedSignature(node);
394
if (signature) {
395
const returnType = typeChecker.getReturnTypeOfSignature(signature);
396
const typeName = typeChecker.typeToString(returnType);
397
398
// Add type information as comment
399
typescript.addSyntheticLeadingComment(
400
node,
401
typescript.SyntaxKind.MultiLineCommentTrivia,
402
` Returns: ${typeName} `,
403
false
404
);
405
}
406
}
407
return typescript.visitEachChild(node, visit, context);
408
}
409
return typescript.visitNode(sourceFile, visit);
410
};
411
}]
412
};
413
};
414
```
415
416
### Limitations and Considerations
417
418
**Important Notes:**
419
420
- Transformers are experimental and the API may change
421
- TypeScript may eventually add native transformer support to tsconfig
422
- Not all transformer libraries are compatible with this plugin
423
- Transformers can significantly impact build performance
424
- Complex transformers may interfere with source maps
425
426
**Best Practices:**
427
428
- Keep transformers as simple as possible
429
- Test thoroughly with different TypeScript versions
430
- Document any custom transformers used in your project
431
- Consider performance impact on large codebases
432
- Use verbosity level 3 to debug transformer issues