Skip to content

Commit

Permalink
feat: expose CommonJS API via tsx/cjs/api (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
privatenumber authored Apr 30, 2024
1 parent b63019c commit 42b7ede
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 2 deletions.
76 changes: 74 additions & 2 deletions docs/node.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,81 @@ node --import tsx/esm ./file.ts
node --loader tsx/esm ./file.ts
```

### CommonJS only
If you only need to add TypeScript & ESM support in a CommonJS context, you can use the CJS loader:
---

# CommonJS

If you only need to add TypeScript & ESM support in a CommonJS context.

## Command-line API

Pass tsx into the `--require` flag:

```sh
node --require tsx/cjs ./file.ts
```

## Node.js API

### Globally patching `require`

#### Enabling TSX Enhancement

To enhance the global `require()` function with TypeScript support, prepend your script with the following line:

```js
require('tsx/cjs')
```

#### Manual Registration & Unregistration

To manually register and unregister the TypeScript enhancement on the global `require()`:

```js
const tsx = require('tsx/cjs/api')

// Register tsx enhancement for all global require() calls
const unregister = tsx.register()

// Optionally, unregister the enhancement when needed
unregister()
```

### Isolated `require()`

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.

Note the current file path must be passed in as the second argument so it knows how to resolve relative paths.

#### CommonJS usage
```js
const tsx = require('tsx/cjs/api')

const loaded = tsx.require('./file.ts', __filename)
const filepath = tsx.require.resolve('./file.ts', __filename)
```

#### ESM usage
```js
import { require } from 'tsx/cjs/api'

const loaded = require('./file.ts', import.meta.url)
const filepath = require.resolve('./file.ts', import.meta.url)
```
#### Module graph
If you're interested in seeing what files were loaded, you can traverse the CommonJS module graph. This can be useful for watchers:
```js
// To detect watch files, we can parse the CommonJS module graph
const resolvedPath = require.resolve('./file', import.meta.url)

const collectDependencies = module => [
module.filename,
...module.children.flatMap(collectDependencies)
]

console.log(collectDependencies(require.cache[resolvedPath]))
// ['/file.ts', '/foo.ts', '/bar.ts']
```
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
"./package.json": "./package.json",
".": "./dist/loader.mjs",
"./cjs": "./dist/cjs/index.cjs",
"./cjs/api": {
"types": "./dist/cjs/api/index.d.ts",
"default": "./dist/cjs/api/index.cjs"
},
"./esm": "./dist/esm/index.mjs",
"./cli": "./dist/cli.mjs",
"./source-map": "./dist/source-map.cjs",
Expand Down
1 change: 1 addition & 0 deletions src/cjs/api/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { register } from './global-require-patch.js';
export { require } from './require.js';
48 changes: 48 additions & 0 deletions src/cjs/api/require.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import path from 'path';
import { fileURLToPath } from 'node:url';
import { register } from './global-require-patch.js';
import { resolveFilename } from './module-resolve-filename.js';

const getRequestContext = (
filepath: string | URL,
) => {
if (
(typeof filepath === 'string' && filepath.startsWith('file://'))
|| filepath instanceof URL
) {
filepath = fileURLToPath(filepath);
}
return path.dirname(filepath);
};

const tsxRequire = (
id: string,
fromFile: string | URL,
) => {
const unregister = register();
try {
const contextId = path.resolve(getRequestContext(fromFile), id);

// eslint-disable-next-line import-x/no-dynamic-require, n/global-require
return require(contextId);
} finally {
unregister();
}
};

const resolve = (
id: string,
fromFile: string | URL,
options?: { paths?: string[] | undefined },
) => {
const contextId = path.resolve(getRequestContext(fromFile), id);
return resolveFilename(contextId, module, false, options);
};
resolve.paths = require.resolve.paths;

tsxRequire.resolve = resolve;
tsxRequire.main = require.main;
tsxRequire.extensions = require.extensions;
tsxRequire.cache = require.cache;

export { tsxRequire as require };

0 comments on commit 42b7ede

Please sign in to comment.