Skip to content

Commit 42b7ede

Browse files
authoredApr 30, 2024
feat: expose CommonJS API via tsx/cjs/api (#3)
1 parent b63019c commit 42b7ede

File tree

4 files changed

+127
-2
lines changed

4 files changed

+127
-2
lines changed
 

‎docs/node.md

+74-2
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,81 @@ node --import tsx/esm ./file.ts
4343
node --loader tsx/esm ./file.ts
4444
```
4545

46-
### CommonJS only
47-
If you only need to add TypeScript & ESM support in a CommonJS context, you can use the CJS loader:
46+
---
47+
48+
# CommonJS
49+
50+
If you only need to add TypeScript & ESM support in a CommonJS context.
51+
52+
## Command-line API
53+
54+
Pass tsx into the `--require` flag:
4855

4956
```sh
5057
node --require tsx/cjs ./file.ts
5158
```
59+
60+
## Node.js API
61+
62+
### Globally patching `require`
63+
64+
#### Enabling TSX Enhancement
65+
66+
To enhance the global `require()` function with TypeScript support, prepend your script with the following line:
67+
68+
```js
69+
require('tsx/cjs')
70+
```
71+
72+
#### Manual Registration & Unregistration
73+
74+
To manually register and unregister the TypeScript enhancement on the global `require()`:
75+
76+
```js
77+
const tsx = require('tsx/cjs/api')
78+
79+
// Register tsx enhancement for all global require() calls
80+
const unregister = tsx.register()
81+
82+
// Optionally, unregister the enhancement when needed
83+
unregister()
84+
```
85+
86+
### Isolated `require()`
87+
88+
In situations where TypeScript support is needed only for loading a specific file (e.g. within third-party packages) without affecting the global environment, you can utilize tsx's custom `require` function.
89+
90+
Note the current file path must be passed in as the second argument so it knows how to resolve relative paths.
91+
92+
#### CommonJS usage
93+
```js
94+
const tsx = require('tsx/cjs/api')
95+
96+
const loaded = tsx.require('./file.ts', __filename)
97+
const filepath = tsx.require.resolve('./file.ts', __filename)
98+
```
99+
100+
#### ESM usage
101+
```js
102+
import { require } from 'tsx/cjs/api'
103+
104+
const loaded = require('./file.ts', import.meta.url)
105+
const filepath = require.resolve('./file.ts', import.meta.url)
106+
```
107+
108+
#### Module graph
109+
110+
If you're interested in seeing what files were loaded, you can traverse the CommonJS module graph. This can be useful for watchers:
111+
112+
```js
113+
// To detect watch files, we can parse the CommonJS module graph
114+
const resolvedPath = require.resolve('./file', import.meta.url)
115+
116+
const collectDependencies = module => [
117+
module.filename,
118+
...module.children.flatMap(collectDependencies)
119+
]
120+
121+
console.log(collectDependencies(require.cache[resolvedPath]))
122+
// ['/file.ts', '/foo.ts', '/bar.ts']
123+
```

‎package.json

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
"./package.json": "./package.json",
2727
".": "./dist/loader.mjs",
2828
"./cjs": "./dist/cjs/index.cjs",
29+
"./cjs/api": {
30+
"types": "./dist/cjs/api/index.d.ts",
31+
"default": "./dist/cjs/api/index.cjs"
32+
},
2933
"./esm": "./dist/esm/index.mjs",
3034
"./cli": "./dist/cli.mjs",
3135
"./source-map": "./dist/source-map.cjs",

‎src/cjs/api/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { register } from './global-require-patch.js';
2+
export { require } from './require.js';

‎src/cjs/api/require.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import path from 'path';
2+
import { fileURLToPath } from 'node:url';
3+
import { register } from './global-require-patch.js';
4+
import { resolveFilename } from './module-resolve-filename.js';
5+
6+
const getRequestContext = (
7+
filepath: string | URL,
8+
) => {
9+
if (
10+
(typeof filepath === 'string' && filepath.startsWith('file://'))
11+
|| filepath instanceof URL
12+
) {
13+
filepath = fileURLToPath(filepath);
14+
}
15+
return path.dirname(filepath);
16+
};
17+
18+
const tsxRequire = (
19+
id: string,
20+
fromFile: string | URL,
21+
) => {
22+
const unregister = register();
23+
try {
24+
const contextId = path.resolve(getRequestContext(fromFile), id);
25+
26+
// eslint-disable-next-line import-x/no-dynamic-require, n/global-require
27+
return require(contextId);
28+
} finally {
29+
unregister();
30+
}
31+
};
32+
33+
const resolve = (
34+
id: string,
35+
fromFile: string | URL,
36+
options?: { paths?: string[] | undefined },
37+
) => {
38+
const contextId = path.resolve(getRequestContext(fromFile), id);
39+
return resolveFilename(contextId, module, false, options);
40+
};
41+
resolve.paths = require.resolve.paths;
42+
43+
tsxRequire.resolve = resolve;
44+
tsxRequire.main = require.main;
45+
tsxRequire.extensions = require.extensions;
46+
tsxRequire.cache = require.cache;
47+
48+
export { tsxRequire as require };

0 commit comments

Comments
 (0)
Please sign in to comment.