diff --git a/src/esm.ts b/src/esm.ts index 35bca748a..77a7f87bd 100644 --- a/src/esm.ts +++ b/src/esm.ts @@ -36,6 +36,39 @@ const { defaultGetFormat } = require('../dist-raw/node-esm-default-get-format'); // from node, build our implementation of the *new* API on top of it, and implement the *old* // hooks API as a shim to the *new* API. +export interface NodeHooksAPI1 { + resolve( + specifier: string, + context: { parentURL: string }, + defaultResolve: NodeHooksAPI1['resolve'] + ): Promise<{ url: string }>; + getFormat( + url: string, + context: {}, + defaultGetFormat: NodeHooksAPI1['getFormat'] + ): Promise<{ format: Format }>; + transformSource( + source: string | Buffer, + context: { url: string; format: Format }, + defaultTransformSource: NodeHooksAPI1['transformSource'] + ): Promise<{ source: string | Buffer }>; +} + +export interface NodeHooksAPI2 { + resolve( + specifier: string, + context: { parentURL: string }, + defaultResolve: NodeHooksAPI2['resolve'] + ): Promise<{ url: string }>; + load( + url: string, + context: { format: Format | null | undefined }, + defaultLoad: NodeHooksAPI2['load'] + ): Promise<{ format: Format; source: string | Buffer | undefined }>; +} + +type Format = 'builtin' | 'commonjs' | 'dynamic' | 'json' | 'module' | 'wasm'; + /** @internal */ export function registerAndCreateEsmHooks(opts?: RegisterOptions) { // Automatically performs registration just like `-r ts-node/register` @@ -62,12 +95,7 @@ export function createEsmHooks(tsNodeService: Service) { versionGteLt(process.versions.node, '12.999.999', '13.0.0'); // Explicit return type to avoid TS's non-ideal inferred type - const hooksAPI: { - resolve: typeof resolve; - getFormat: typeof getFormat | undefined; - transformSource: typeof transformSource | undefined; - load: typeof load | undefined; - } = newHooksAPI + const hooksAPI: NodeHooksAPI1 | NodeHooksAPI2 = newHooksAPI ? { resolve, load, getFormat: undefined, transformSource: undefined } : { resolve, getFormat, transformSource, load: undefined }; return hooksAPI; @@ -160,7 +188,6 @@ export function createEsmHooks(tsNodeService: Service) { return { format, source }; } - type Format = 'builtin' | 'commonjs' | 'dynamic' | 'json' | 'module' | 'wasm'; async function getFormat( url: string, context: {}, diff --git a/src/test/esm-loader.spec.ts b/src/test/esm-loader.spec.ts index e117e1ef8..e53b3e70b 100644 --- a/src/test/esm-loader.spec.ts +++ b/src/test/esm-loader.spec.ts @@ -7,11 +7,15 @@ import semver = require('semver'); import { contextTsNodeUnderTest, EXPERIMENTAL_MODULES_FLAG, + resetNodeEnvironment, TEST_DIR, } from './helpers'; import { createExec } from './exec-helpers'; import { join } from 'path'; import * as expect from 'expect'; +import type { NodeHooksAPI2 } from '../esm'; + +const nodeUsesNewHooksApi = semver.gte(process.version, '16.12.0'); const test = context(contextTsNodeUnderTest); @@ -37,3 +41,31 @@ test.suite('createEsmHooks', (test) => { }); } }); + +test.suite('hooks', (_test) => { + const test = _test.context(async (t) => { + const service = t.context.tsNodeUnderTest.create(); + t.teardown(() => { + resetNodeEnvironment(); + }); + return { + service, + hooks: t.context.tsNodeUnderTest.createEsmHooks(service), + }; + }); + + if (nodeUsesNewHooksApi) { + test('Correctly determines format of data URIs', async (t) => { + const { hooks } = t.context; + const url = 'data:text/javascript,console.log("hello world");'; + const result = await (hooks as NodeHooksAPI2).load( + url, + { format: undefined }, + async (url, context, _ignored) => { + return { format: context.format!, source: '' }; + } + ); + expect(result.format).toBe('module'); + }); + } +});