Skip to content

Commit

Permalink
add support for '--import ts-node/import'
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
isaacs committed Sep 6, 2023
1 parent fa45fb9 commit bb2e9f1
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 12 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ To test your version of `env` for compatibility with `-S`:

## node flags and other tools

You can register ts-node without using our CLI: `node -r ts-node/register` and `node --loader ts-node/esm`
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.

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.

Expand Down
2 changes: 1 addition & 1 deletion esm.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ const require = createRequire(fileURLToPath(import.meta.url));

/** @type {import('./dist/esm')} */
const esm = require('./dist/esm');
export const { resolve, load, getFormat, transformSource, globalPreload } = esm.registerAndCreateEsmHooks();
export const { initialize, resolve, load, getFormat, transformSource, globalPreload } = esm.registerAndCreateEsmHooks();
39 changes: 29 additions & 10 deletions src/esm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@ export namespace NodeLoaderHooksAPI2 {
export type GlobalPreloadHook = (context?: { port: MessagePort }) => string;
}

export interface NodeLoaderHooksAPI3 {
resolve: NodeLoaderHooksAPI2.ResolveHook;
load: NodeLoaderHooksAPI2.LoadHook;
initialize?: NodeLoaderHooksAPI3.InitializeHook;
}
export namespace NodeLoaderHooksAPI3 {
// technically this can be anything that can be passed through a postMessage channel,
// but defined here based on how ts-node uses it.
export type InitializeHook = (data: any) => void | Promise<void>;
}

export type NodeLoaderHooksFormat = 'builtin' | 'commonjs' | 'dynamic' | 'json' | 'module' | 'wasm';

export type NodeImportConditions = unknown;
Expand All @@ -87,17 +98,24 @@ export interface NodeImportAssertions {
}

// The hooks API changed in node version X so we need to check for backwards compatibility.
const newHooksAPI = versionGteLt(process.versions.node, '16.12.0');
const hooksAPIVersion = versionGteLt(process.versions.node, '21.0.0')
? 3
: versionGteLt(process.versions.node, '16.12.0')
? 2
: 1;

/** @internal */
export function filterHooksByAPIVersion(
hooks: NodeLoaderHooksAPI1 & NodeLoaderHooksAPI2
): NodeLoaderHooksAPI1 | NodeLoaderHooksAPI2 {
const { getFormat, load, resolve, transformSource, globalPreload } = hooks;
hooks: NodeLoaderHooksAPI1 & NodeLoaderHooksAPI2 & NodeLoaderHooksAPI3
): NodeLoaderHooksAPI1 | NodeLoaderHooksAPI2 | NodeLoaderHooksAPI3 {
const { getFormat, load, resolve, transformSource, globalPreload, initialize } = hooks;
// Explicit return type to avoid TS's non-ideal inferred type
const hooksAPI: NodeLoaderHooksAPI1 | NodeLoaderHooksAPI2 = newHooksAPI
? { resolve, load, globalPreload, getFormat: undefined, transformSource: undefined }
: { resolve, getFormat, transformSource, load: undefined };
const hooksAPI: NodeLoaderHooksAPI1 | NodeLoaderHooksAPI2 | NodeLoaderHooksAPI3 =
hooksAPIVersion === 3
? { resolve, load, initialize, globalPreload: undefined, transformSource: undefined, getFormat: undefined }
: hooksAPIVersion === 2
? { resolve, load, globalPreload, initialize: undefined, getFormat: undefined, transformSource: undefined }
: { resolve, getFormat, transformSource, initialize: undefined, globalPreload: undefined, load: undefined };
return hooksAPI;
}

Expand All @@ -122,16 +140,17 @@ export function createEsmHooks(tsNodeService: Service) {
getFormat,
transformSource,
globalPreload: useLoaderThread ? globalPreload : undefined,
initialize: undefined,
});

function globalPreload({ port }: { port?: MessagePort } = {}) {
// The loader thread doesn't get process.stderr.isTTY properly,
// so this signal lets us infer it based on the state of the main
// thread, but only relevant if options.pretty is unset.
let stderrTTYSignal: string;
if (tsNodeService.options.pretty === undefined) {
port?.on('message', (data) => {
if (data?.stderrIsTTY) {
if (port && tsNodeService.options.pretty === undefined) {
port.on('message', (data: { stderrIsTTY?: boolean }) => {

Check warning on line 152 in src/esm.ts

View check run for this annotation

Codecov / codecov/patch

src/esm.ts#L152

Added line #L152 was not covered by tests
if (data.stderrIsTTY) {
tsNodeService.setPrettyErrors(true);

Check warning on line 154 in src/esm.ts

View check run for this annotation

Codecov / codecov/patch

src/esm.ts#L154

Added line #L154 was not covered by tests
}
});
Expand Down

0 comments on commit bb2e9f1

Please sign in to comment.