0
# ESM API
1
2
The ESM API (`tsx/esm/api`) provides programmatic access to tsx's TypeScript transformation capabilities within ES modules. It uses Node.js's module.register() system to provide hook-based loading of TypeScript files with advanced configuration options.
3
4
## Capabilities
5
6
### Module Registration
7
8
Register tsx transformation hooks for ES modules using Node.js module.register().
9
10
```typescript { .api }
11
/**
12
* Register tsx loader for ES modules
13
* @param options - Optional configuration
14
* @returns Unregister function
15
*/
16
function register(options?: RegisterOptions): Unregister;
17
18
/**
19
* Register tsx loader with required namespace
20
* @param options - Configuration with required namespace
21
* @returns Namespaced unregister function with additional methods
22
*/
23
function register(options: RequiredProperty<RegisterOptions, 'namespace'>): NamespacedUnregister;
24
25
interface RegisterOptions {
26
namespace?: string;
27
onImport?: (url: string) => void;
28
tsconfig?: TsconfigOptions;
29
}
30
31
interface InitializationOptions {
32
namespace?: string;
33
port?: MessagePort;
34
tsconfig?: TsconfigOptions;
35
}
36
37
type TsconfigOptions = false | string;
38
type Unregister = () => Promise<void>;
39
40
interface NamespacedUnregister extends Unregister {
41
import: ScopedImport;
42
unregister: Unregister;
43
}
44
45
type Register = {
46
(options: RequiredProperty<RegisterOptions, 'namespace'>): NamespacedUnregister;
47
(options?: RegisterOptions): Unregister;
48
};
49
```
50
51
**Usage Examples:**
52
53
```typescript
54
import { register } from "tsx/esm/api";
55
56
// Basic registration
57
const unregister = register();
58
59
// Now TypeScript files can be imported
60
const myModule = await import("./my-module.ts");
61
62
// Unregister when done
63
await unregister();
64
65
// Registration with import callback
66
const unregister2 = register({
67
onImport: (url) => console.log(`Loading: ${url}`)
68
});
69
70
// Namespaced registration for isolation
71
const api = register({ namespace: "my-app" });
72
const tsModule = await api.import("./module.ts", import.meta.url);
73
await api.unregister();
74
```
75
76
### TypeScript Import
77
78
Dynamic import function with TypeScript transformation support.
79
80
```typescript { .api }
81
/**
82
* Import TypeScript modules with transformation
83
* @param specifier - Module specifier or path
84
* @param options - Parent URL or options object
85
* @returns Promise resolving to module exports
86
*/
87
function tsImport(
88
specifier: string,
89
options: string | TsImportOptions
90
): Promise<any>;
91
92
interface TsImportOptions {
93
parentURL: string;
94
onImport?: (url: string) => void;
95
tsconfig?: TsconfigOptions;
96
}
97
```
98
99
**Usage Examples:**
100
101
```typescript
102
import { tsImport } from "tsx/esm/api";
103
104
// Import TypeScript files
105
const utils = await tsImport("./utils.ts", import.meta.url);
106
const config = await tsImport("./config.ts", import.meta.url);
107
108
// Import relative modules
109
const helper = await tsImport("../helpers/format.ts", import.meta.url);
110
111
// Import with options
112
const module = await tsImport("./module.ts", {
113
parentURL: import.meta.url,
114
onImport: (url) => console.log(`Importing: ${url}`),
115
tsconfig: "./custom-tsconfig.json"
116
});
117
```
118
119
### Scoped Import
120
121
When using namespace registration, access to scoped import functionality.
122
123
```typescript { .api }
124
type ScopedImport = (
125
specifier: string,
126
parent: string,
127
) => Promise<any>;
128
```
129
130
**Usage Examples:**
131
132
```typescript
133
import { register } from "tsx/esm/api";
134
135
// Create namespaced registration
136
const api = register({ namespace: "isolated-context" });
137
138
// Use scoped import - isolated from other tsx instances
139
const module1 = await api.import("./module1.ts", import.meta.url);
140
const module2 = await api.import("./module2.ts", import.meta.url);
141
142
// Cleanup
143
await api.unregister();
144
```
145
146
### TypeScript Configuration
147
148
Control tsx behavior through tsconfig options.
149
150
```typescript { .api }
151
// Disable tsconfig loading
152
const unregister = register({ tsconfig: false });
153
154
// Use custom tsconfig path
155
const unregister2 = register({
156
tsconfig: "./custom-tsconfig.json"
157
});
158
159
// Environment variable control
160
process.env.TSX_TSCONFIG_PATH = "./my-config.json";
161
const unregister3 = register();
162
```
163
164
**Usage Examples:**
165
166
```typescript
167
import { register } from "tsx/esm/api";
168
169
// Use specific tsconfig for registration
170
const unregister = register({
171
tsconfig: "./build-tsconfig.json",
172
onImport: (url) => {
173
if (url.includes('.test.')) {
174
console.log(`Loading test file: ${url}`);
175
}
176
}
177
});
178
179
// Import modules with custom configuration
180
const testModule = await import("./test.ts");
181
const srcModule = await import("./src/index.ts");
182
183
await unregister();
184
```
185
186
### Advanced Import Hooks
187
188
Monitor and control the import process with callback hooks.
189
190
```typescript { .api }
191
interface ImportHookOptions {
192
namespace?: string;
193
onImport?: (url: string) => void;
194
tsconfig?: TsconfigOptions;
195
}
196
```
197
198
**Usage Examples:**
199
200
```typescript
201
import { register } from "tsx/esm/api";
202
203
// Track all imports
204
const importedFiles = new Set<string>();
205
const unregister = register({
206
onImport: (url) => {
207
importedFiles.add(url);
208
console.log(`Imported: ${url}`);
209
}
210
});
211
212
// Use the imports
213
await import("./app.ts");
214
await import("./config.ts");
215
216
console.log(`Total imports: ${importedFiles.size}`);
217
await unregister();
218
```
219
220
### Integration Patterns
221
222
Common patterns for integrating tsx in ESM applications.
223
224
**Application Bootstrap:**
225
226
```typescript
227
import { register } from "tsx/esm/api";
228
229
// Register at application start
230
const unregister = register({
231
onImport: (url) => console.log(`Loading: ${url}`)
232
});
233
234
// Import TypeScript application modules
235
const { startServer } = await import("./server.ts");
236
const { loadConfig } = await import("./config.ts");
237
238
// Start application
239
const config = await loadConfig();
240
await startServer(config);
241
242
// Cleanup on shutdown
243
process.on('SIGTERM', async () => {
244
await unregister();
245
process.exit(0);
246
});
247
```
248
249
**Conditional TypeScript Loading:**
250
251
```typescript
252
import { register, tsImport } from "tsx/esm/api";
253
254
async function loadPlugin(pluginPath: string) {
255
if (pluginPath.endsWith('.ts')) {
256
// Use tsx for TypeScript files
257
return await tsImport(pluginPath, import.meta.url);
258
} else {
259
// Use standard import for JavaScript
260
return await import(pluginPath);
261
}
262
}
263
264
// Load mixed plugin types
265
const jsPlugin = await loadPlugin("./plugins/js-plugin.js");
266
const tsPlugin = await loadPlugin("./plugins/ts-plugin.ts");
267
```
268
269
**Multiple Isolated Contexts:**
270
271
```typescript
272
import { register } from "tsx/esm/api";
273
274
// Create separate contexts
275
const devContext = register({
276
namespace: "dev",
277
tsconfig: "./tsconfig.dev.json"
278
});
279
280
const prodContext = register({
281
namespace: "prod",
282
tsconfig: "./tsconfig.prod.json"
283
});
284
285
// Load modules in different contexts
286
const devModule = await devContext.import("./dev.ts", import.meta.url);
287
const prodModule = await prodContext.import("./prod.ts", import.meta.url);
288
289
// Cleanup
290
await devContext.unregister();
291
await prodContext.unregister();
292
```
293
294
## Types
295
296
### Core Types
297
298
```typescript { .api }
299
interface TsxRequest {
300
namespace: string;
301
parentURL: string;
302
specifier: string;
303
}
304
305
type Message = {
306
type: 'deactivated';
307
} | {
308
type: 'load';
309
url: string;
310
};
311
```
312
313
### Node.js Compatibility
314
315
tsx ESM API requires Node.js v18.19+ or v20.6+ for module.register() support.
316
317
```typescript
318
import { register } from "tsx/esm/api";
319
320
try {
321
const unregister = register();
322
// Registration successful
323
} catch (error) {
324
if (error.message.includes('does not support module.register')) {
325
console.error('Please upgrade Node.js to v18.19+ or v20.6+');
326
}
327
}
328
```
329
330
### Error Handling
331
332
tsx preserves TypeScript error information and source maps for debugging.
333
334
#### Node.js Version Compatibility
335
336
```typescript
337
import { register } from "tsx/esm/api";
338
339
try {
340
const unregister = register();
341
// Registration successful
342
} catch (error) {
343
if (error.message.includes('does not support module.register')) {
344
console.error('Please upgrade Node.js to v18.19+ or v20.6+');
345
process.exit(1);
346
}
347
throw error;
348
}
349
```
350
351
#### TypeScript Transformation Errors
352
353
```typescript
354
import { tsImport } from "tsx/esm/api";
355
356
try {
357
const module = await tsImport("./broken.ts", import.meta.url);
358
} catch (error) {
359
// Error includes TypeScript source locations with source maps
360
console.error('TypeScript compilation error:', error.stack);
361
362
// Handle specific error types
363
if (error.code === 'MODULE_NOT_FOUND') {
364
console.error('Module could not be resolved:', error.path);
365
}
366
}
367
```
368
369
#### Import Parameter Validation
370
371
```typescript
372
import { tsImport } from "tsx/esm/api";
373
374
try {
375
// This will throw an error
376
const module = await tsImport("./module.ts"); // Missing parentURL
377
} catch (error) {
378
console.error(error.message);
379
// "The current file path (import.meta.url) must be provided in the second argument of tsImport()"
380
}
381
382
try {
383
// This will also throw an error
384
const module = await tsImport("./module.ts", { /* missing parentURL */ });
385
} catch (error) {
386
console.error(error.message);
387
// "The current file path (import.meta.url) must be provided in the second argument of tsImport()"
388
}
389
```