Skip to content

Commit bb2e9f1

Browse files
committed
add support for '--import ts-node/import'
This also adds a type for the loader hooks API v3, as globalPreload is scheduled for removal in node v21, at which point '--loader ts-node/esm' will no longer work, and '--import ts-node/import' will be the way forward.
1 parent fa45fb9 commit bb2e9f1

File tree

3 files changed

+31
-12
lines changed

3 files changed

+31
-12
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ To test your version of `env` for compatibility with `-S`:
230230

231231
## node flags and other tools
232232

233-
You can register ts-node without using our CLI: `node -r ts-node/register` and `node --loader ts-node/esm`
233+
You can register ts-node without using our CLI: `node -r ts-node/register`, `node --loader ts-node/esm`, or `node --import ts-node/import` in node 20.6 and above.
234234

235235
In many cases, setting [`NODE_OPTIONS`](https://nodejs.org/api/cli.html#cli_node_options_options) will enable `ts-node` within other node tools, child processes, and worker threads. This can be combined with other node flags.
236236

esm.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ const require = createRequire(fileURLToPath(import.meta.url));
44

55
/** @type {import('./dist/esm')} */
66
const esm = require('./dist/esm');
7-
export const { resolve, load, getFormat, transformSource, globalPreload } = esm.registerAndCreateEsmHooks();
7+
export const { initialize, resolve, load, getFormat, transformSource, globalPreload } = esm.registerAndCreateEsmHooks();

src/esm.ts

+29-10
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,17 @@ export namespace NodeLoaderHooksAPI2 {
7979
export type GlobalPreloadHook = (context?: { port: MessagePort }) => string;
8080
}
8181

82+
export interface NodeLoaderHooksAPI3 {
83+
resolve: NodeLoaderHooksAPI2.ResolveHook;
84+
load: NodeLoaderHooksAPI2.LoadHook;
85+
initialize?: NodeLoaderHooksAPI3.InitializeHook;
86+
}
87+
export namespace NodeLoaderHooksAPI3 {
88+
// technically this can be anything that can be passed through a postMessage channel,
89+
// but defined here based on how ts-node uses it.
90+
export type InitializeHook = (data: any) => void | Promise<void>;
91+
}
92+
8293
export type NodeLoaderHooksFormat = 'builtin' | 'commonjs' | 'dynamic' | 'json' | 'module' | 'wasm';
8394

8495
export type NodeImportConditions = unknown;
@@ -87,17 +98,24 @@ export interface NodeImportAssertions {
8798
}
8899

89100
// The hooks API changed in node version X so we need to check for backwards compatibility.
90-
const newHooksAPI = versionGteLt(process.versions.node, '16.12.0');
101+
const hooksAPIVersion = versionGteLt(process.versions.node, '21.0.0')
102+
? 3
103+
: versionGteLt(process.versions.node, '16.12.0')
104+
? 2
105+
: 1;
91106

92107
/** @internal */
93108
export function filterHooksByAPIVersion(
94-
hooks: NodeLoaderHooksAPI1 & NodeLoaderHooksAPI2
95-
): NodeLoaderHooksAPI1 | NodeLoaderHooksAPI2 {
96-
const { getFormat, load, resolve, transformSource, globalPreload } = hooks;
109+
hooks: NodeLoaderHooksAPI1 & NodeLoaderHooksAPI2 & NodeLoaderHooksAPI3
110+
): NodeLoaderHooksAPI1 | NodeLoaderHooksAPI2 | NodeLoaderHooksAPI3 {
111+
const { getFormat, load, resolve, transformSource, globalPreload, initialize } = hooks;
97112
// Explicit return type to avoid TS's non-ideal inferred type
98-
const hooksAPI: NodeLoaderHooksAPI1 | NodeLoaderHooksAPI2 = newHooksAPI
99-
? { resolve, load, globalPreload, getFormat: undefined, transformSource: undefined }
100-
: { resolve, getFormat, transformSource, load: undefined };
113+
const hooksAPI: NodeLoaderHooksAPI1 | NodeLoaderHooksAPI2 | NodeLoaderHooksAPI3 =
114+
hooksAPIVersion === 3
115+
? { resolve, load, initialize, globalPreload: undefined, transformSource: undefined, getFormat: undefined }
116+
: hooksAPIVersion === 2
117+
? { resolve, load, globalPreload, initialize: undefined, getFormat: undefined, transformSource: undefined }
118+
: { resolve, getFormat, transformSource, initialize: undefined, globalPreload: undefined, load: undefined };
101119
return hooksAPI;
102120
}
103121

@@ -122,16 +140,17 @@ export function createEsmHooks(tsNodeService: Service) {
122140
getFormat,
123141
transformSource,
124142
globalPreload: useLoaderThread ? globalPreload : undefined,
143+
initialize: undefined,
125144
});
126145

127146
function globalPreload({ port }: { port?: MessagePort } = {}) {
128147
// The loader thread doesn't get process.stderr.isTTY properly,
129148
// so this signal lets us infer it based on the state of the main
130149
// thread, but only relevant if options.pretty is unset.
131150
let stderrTTYSignal: string;
132-
if (tsNodeService.options.pretty === undefined) {
133-
port?.on('message', (data) => {
134-
if (data?.stderrIsTTY) {
151+
if (port && tsNodeService.options.pretty === undefined) {
152+
port.on('message', (data: { stderrIsTTY?: boolean }) => {
153+
if (data.stderrIsTTY) {
135154
tsNodeService.setPrettyErrors(true);
136155
}
137156
});

0 commit comments

Comments
 (0)