0
# Build-time Transformation
1
2
Build-time transformation plugins automatically transform async functions to preserve context across `await` statements, enabling universal async context support without runtime dependencies.
3
4
## API Reference
5
6
```typescript { .api }
7
/**
8
* Universal bundler plugin for async context transformation
9
* Created with createUnplugin, provides bundler-specific methods
10
*/
11
export const unctxPlugin: {
12
rollup(options?: UnctxPluginOptions): Plugin;
13
vite(options?: UnctxPluginOptions): Plugin;
14
webpack(options?: UnctxPluginOptions): Plugin;
15
};
16
17
/**
18
* Create AST transformer for async context preservation
19
* @param options - Transformation configuration
20
* @returns Transformer with transform and shouldTransform methods
21
*/
22
function createTransformer(options?: TransformerOptions): {
23
transform(code: string, options?: { force?: false }): TransformResult | undefined;
24
shouldTransform(code: string): boolean;
25
};
26
27
interface UnctxPluginOptions extends TransformerOptions {
28
/**
29
* Filter function to determine which files to transform
30
* @param id - File path/identifier
31
* @returns true if file should be transformed
32
*/
33
transformInclude?: (id: string) => boolean;
34
}
35
36
interface TransformerOptions {
37
/**
38
* Function names to be transformed for async context preservation
39
* @default ['withAsyncContext']
40
*/
41
asyncFunctions?: string[];
42
43
/**
44
* Module name to import helper functions from
45
* @default 'unctx'
46
*/
47
helperModule?: string;
48
49
/**
50
* Name of the helper function for async execution
51
* @default 'executeAsync'
52
*/
53
helperName?: string;
54
55
/**
56
* Object property transformation rules
57
* Map function names to property names to transform
58
* @default {}
59
*/
60
objectDefinitions?: Record<string, string[]>;
61
}
62
63
interface TransformResult {
64
code: string;
65
magicString: MagicString;
66
}
67
```
68
69
## Plugin Installation
70
71
### Vite
72
73
```javascript
74
// vite.config.js
75
import { unctxPlugin } from "unctx/plugin";
76
77
export default {
78
plugins: [
79
unctxPlugin.vite({
80
// Transform functions named 'withAsyncContext' and 'callAsync'
81
asyncFunctions: ['withAsyncContext', 'callAsync'],
82
83
// Only transform specific files
84
transformInclude: (id) => {
85
return id.includes('src/') && !id.includes('node_modules/');
86
}
87
})
88
]
89
};
90
```
91
92
### Rollup
93
94
```javascript
95
// rollup.config.js
96
import { unctxPlugin } from "unctx/plugin";
97
98
export default {
99
plugins: [
100
unctxPlugin.rollup({
101
asyncFunctions: ['withAsyncContext', 'callAsync'],
102
transformInclude: (id) => id.endsWith('.ts') || id.endsWith('.js')
103
})
104
]
105
};
106
```
107
108
### Webpack
109
110
```javascript
111
// webpack.config.js
112
const { unctxPlugin } = require("unctx/plugin");
113
114
module.exports = {
115
plugins: [
116
unctxPlugin.webpack({
117
asyncFunctions: ['withAsyncContext'],
118
helperModule: 'unctx',
119
helperName: 'executeAsync'
120
})
121
]
122
};
123
```
124
125
## Transformation Process
126
127
### Before Transformation
128
129
```typescript
130
import { createContext, withAsyncContext } from "unctx";
131
132
const userContext = createContext<User>();
133
134
const processUser = withAsyncContext(async () => {
135
console.log(userContext.use()); // Available
136
137
await fetch("/api/data");
138
139
console.log(userContext.use()); // Would be undefined without transformation
140
141
await processMoreData();
142
143
return userContext.use();
144
});
145
```
146
147
### After Transformation
148
149
The plugin automatically transforms the code to:
150
151
```typescript
152
import { executeAsync as __executeAsync } from "unctx";
153
import { createContext, withAsyncContext } from "unctx";
154
155
const userContext = createContext<User>();
156
157
const processUser = withAsyncContext(async () => {
158
let __temp, __restore;
159
160
console.log(userContext.use()); // Available
161
162
(([__temp, __restore] = __executeAsync(() => fetch("/api/data"))),
163
__temp = await __temp, __restore(), __temp);
164
165
console.log(userContext.use()); // ✅ Context restored automatically
166
167
(([__temp, __restore] = __executeAsync(() => processMoreData())),
168
__temp = await __temp, __restore(), __temp);
169
170
return userContext.use();
171
}, 1);
172
```
173
174
## Using callAsync with Transformation
175
176
Enable `callAsync` transformation by adding it to `asyncFunctions`:
177
178
```javascript
179
// Build configuration
180
unctxPlugin.vite({
181
asyncFunctions: ['withAsyncContext', 'callAsync']
182
})
183
```
184
185
```typescript
186
import { createContext } from "unctx";
187
188
const ctx = createContext<Data>();
189
190
// Now callAsync is automatically transformed
191
await ctx.callAsync(data, async () => {
192
console.log(ctx.use()); // Available
193
194
await operation1();
195
console.log(ctx.use()); // ✅ Restored automatically
196
197
await operation2();
198
console.log(ctx.use()); // ✅ Restored automatically
199
});
200
```
201
202
## Custom Function Transformation
203
204
Transform your own async functions:
205
206
```javascript
207
// Build configuration
208
unctxPlugin.vite({
209
asyncFunctions: ['withAsyncContext', 'myAsyncWrapper', 'customAsync']
210
})
211
```
212
213
```typescript
214
// Define custom async wrapper
215
function myAsyncWrapper<T>(fn: () => Promise<T>): () => Promise<T> {
216
return fn; // Actual implementation
217
}
218
219
// Usage - will be transformed
220
const handler = myAsyncWrapper(async () => {
221
const ctx = myContext.use();
222
await asyncOperation();
223
return myContext.use(); // Context preserved
224
});
225
```
226
227
## Object Property Transformation
228
229
Transform specific properties within object definitions:
230
231
```javascript
232
// Build configuration
233
unctxPlugin.vite({
234
objectDefinitions: {
235
'defineMiddleware': ['handler'],
236
'defineRoute': ['middleware', 'handler']
237
}
238
})
239
```
240
241
```typescript
242
// These object properties will be transformed
243
const middleware = defineMiddleware({
244
handler: async (req, res) => {
245
const ctx = requestContext.use();
246
await processRequest();
247
// Context automatically restored
248
return requestContext.use();
249
}
250
});
251
252
const route = defineRoute({
253
path: '/api/users',
254
middleware: async (req, res, next) => {
255
await authenticate();
256
// Context preserved
257
next();
258
},
259
handler: async (req, res) => {
260
await handleRequest();
261
// Context preserved
262
}
263
});
264
```
265
266
## Manual Transformer Usage
267
268
Create custom transformers for advanced use cases:
269
270
```typescript
271
import { createTransformer } from "unctx/transform";
272
273
const transformer = createTransformer({
274
asyncFunctions: ['myAsyncFn'],
275
helperModule: 'my-context-lib',
276
helperName: 'executeWithContext'
277
});
278
279
// Check if code needs transformation
280
const code = `
281
const handler = myAsyncFn(async () => {
282
await fetch('/api');
283
});
284
`;
285
286
if (transformer.shouldTransform(code)) {
287
const result = transformer.transform(code);
288
console.log(result.code); // Transformed code
289
}
290
```
291
292
## TypeScript Integration
293
294
The plugin works seamlessly with TypeScript:
295
296
```typescript
297
// tsconfig.json - no special configuration needed
298
{
299
"compilerOptions": {
300
"target": "ES2020",
301
"module": "ESNext"
302
}
303
}
304
```
305
306
```typescript
307
// TypeScript code is transformed correctly
308
interface UserData {
309
id: string;
310
name: string;
311
}
312
313
const userContext = createContext<UserData>();
314
315
const processUser = withAsyncContext(async (): Promise<UserData> => {
316
const user = userContext.use(); // Typed correctly
317
318
await validateUser(user);
319
320
return userContext.use(); // TypeScript knows this is UserData
321
});
322
```
323
324
## File Filtering
325
326
Control which files are transformed:
327
328
```javascript
329
unctxPlugin.vite({
330
transformInclude: (id) => {
331
// Only transform application code, skip node_modules
332
if (id.includes('node_modules/')) return false;
333
334
// Only transform specific file types
335
if (!id.match(/\.(ts|js|vue)$/)) return false;
336
337
// Only transform files in src directory
338
return id.includes('/src/');
339
}
340
})
341
```
342
343
## Performance Considerations
344
345
### Build Performance
346
347
```javascript
348
// Optimize build performance
349
unctxPlugin.vite({
350
// Limit transformation to necessary functions
351
asyncFunctions: ['withAsyncContext'], // Don't include unnecessary functions
352
353
// Use precise file filtering
354
transformInclude: (id) => {
355
// Skip large third-party files
356
if (id.includes('node_modules/')) return false;
357
// Only transform files that actually use unctx
358
return id.includes('src/') && !id.includes('.spec.');
359
}
360
})
361
```
362
363
### Runtime Performance
364
365
The transformation adds minimal runtime overhead:
366
367
```typescript
368
// Generated code is efficient
369
const result = (([__temp, __restore] = __executeAsync(() => operation())),
370
__temp = await __temp, __restore(), __temp);
371
372
// Equivalent to manual context restoration but automated
373
```
374
375
## Debugging Transformed Code
376
377
### Enable Source Maps
378
379
```javascript
380
unctxPlugin.vite({
381
// Source maps are generated automatically
382
// Use browser dev tools to debug original code
383
})
384
```
385
386
### Inspect Transformed Output
387
388
```typescript
389
import { createTransformer } from "unctx/transform";
390
391
const transformer = createTransformer();
392
const result = transformer.transform(sourceCode);
393
394
console.log('Transformed code:');
395
console.log(result.code);
396
```
397
398
## Common Issues and Solutions
399
400
### Issue: Functions Not Being Transformed
401
402
```javascript
403
// ❌ Function name not in asyncFunctions list
404
const handler = myCustomAsync(async () => {
405
// Context lost after await
406
});
407
408
// ✅ Add function name to config
409
unctxPlugin.vite({
410
asyncFunctions: ['withAsyncContext', 'myCustomAsync']
411
})
412
```
413
414
### Issue: TypeScript Compilation Errors
415
416
```typescript
417
// ❌ Missing import for generated helper
418
// The plugin automatically adds: import { executeAsync as __executeAsync } from "unctx";
419
420
// ✅ Ensure unctx is installed as dependency
421
// npm install unctx
422
```
423
424
### Issue: Context Still Lost
425
426
```typescript
427
// ❌ Variable assignment prevents transformation
428
const handler = withAsyncContext;
429
const myHandler = handler(async () => {
430
// Not transformed - indirect call
431
});
432
433
// ✅ Direct function call enables transformation
434
const myHandler = withAsyncContext(async () => {
435
// Transformed correctly
436
});
437
```