From 0e8b8279d6fa036fb0dba14a3eb680e0cfb9b1ae Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Thu, 14 Apr 2022 11:07:25 -0400 Subject: [PATCH] add short-circuit flag to all ESM hook return values (#1715) --- src/esm.ts | 137 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 78 insertions(+), 59 deletions(-) diff --git a/src/esm.ts b/src/esm.ts index 38f4f0d58..d61ee1584 100644 --- a/src/esm.ts +++ b/src/esm.ts @@ -69,7 +69,11 @@ export namespace NodeLoaderHooksAPI2 { parentURL: string; }, defaultResolve: ResolveHook - ) => Promise<{ url: string; format?: NodeLoaderHooksFormat }>; + ) => Promise<{ + url: string; + format?: NodeLoaderHooksFormat; + shortCircuit?: boolean; + }>; export type LoadHook = ( url: string, context: { @@ -80,6 +84,7 @@ export namespace NodeLoaderHooksAPI2 { ) => Promise<{ format: NodeLoaderHooksFormat; source: string | Buffer | undefined; + shortCircuit?: boolean; }>; export type NodeImportConditions = unknown; export interface NodeImportAssertions { @@ -205,32 +210,34 @@ export function createEsmHooks(tsNodeService: Service) { } } - const parsed = parseUrl(specifier); - const { pathname, protocol, hostname } = parsed; + return addShortCircuitFlag(async () => { + const parsed = parseUrl(specifier); + const { pathname, protocol, hostname } = parsed; - if (!isFileUrlOrNodeStyleSpecifier(parsed)) { - return entrypointFallback(defer); - } + if (!isFileUrlOrNodeStyleSpecifier(parsed)) { + return entrypointFallback(defer); + } - if (protocol !== null && protocol !== 'file:') { - return entrypointFallback(defer); - } + if (protocol !== null && protocol !== 'file:') { + return entrypointFallback(defer); + } - // Malformed file:// URL? We should always see `null` or `''` - if (hostname) { - // TODO file://./foo sets `hostname` to `'.'`. Perhaps we should special-case this. - return entrypointFallback(defer); - } + // Malformed file:// URL? We should always see `null` or `''` + if (hostname) { + // TODO file://./foo sets `hostname` to `'.'`. Perhaps we should special-case this. + return entrypointFallback(defer); + } - // pathname is the path to be resolved + // pathname is the path to be resolved - return entrypointFallback(() => - nodeResolveImplementation.defaultResolve( - specifier, - context, - defaultResolve - ) - ); + return entrypointFallback(() => + nodeResolveImplementation.defaultResolve( + specifier, + context, + defaultResolve + ) + ); + }); } // `load` from new loader hook API (See description at the top of this file) @@ -245,47 +252,49 @@ export function createEsmHooks(tsNodeService: Service) { format: NodeLoaderHooksFormat; source: string | Buffer | undefined; }> { - // If we get a format hint from resolve() on the context then use it - // otherwise call the old getFormat() hook using node's old built-in defaultGetFormat() that ships with ts-node - const format = - context.format ?? - (await getFormat(url, context, defaultGetFormat)).format; - - let source = undefined; - if (format !== 'builtin' && format !== 'commonjs') { - // Call the new defaultLoad() to get the source - const { source: rawSource } = await defaultLoad( - url, - { - ...context, - format, - }, - defaultLoad - ); + return addShortCircuitFlag(async () => { + // If we get a format hint from resolve() on the context then use it + // otherwise call the old getFormat() hook using node's old built-in defaultGetFormat() that ships with ts-node + const format = + context.format ?? + (await getFormat(url, context, defaultGetFormat)).format; + + let source = undefined; + if (format !== 'builtin' && format !== 'commonjs') { + // Call the new defaultLoad() to get the source + const { source: rawSource } = await defaultLoad( + url, + { + ...context, + format, + }, + defaultLoad + ); + + if (rawSource === undefined || rawSource === null) { + throw new Error( + `Failed to load raw source: Format was '${format}' and url was '${url}''.` + ); + } - if (rawSource === undefined || rawSource === null) { - throw new Error( - `Failed to load raw source: Format was '${format}' and url was '${url}''.` + // Emulate node's built-in old defaultTransformSource() so we can re-use the old transformSource() hook + const defaultTransformSource: typeof transformSource = async ( + source, + _context, + _defaultTransformSource + ) => ({ source }); + + // Call the old hook + const { source: transformedSource } = await transformSource( + rawSource, + { url, format }, + defaultTransformSource ); + source = transformedSource; } - // Emulate node's built-in old defaultTransformSource() so we can re-use the old transformSource() hook - const defaultTransformSource: typeof transformSource = async ( - source, - _context, - _defaultTransformSource - ) => ({ source }); - - // Call the old hook - const { source: transformedSource } = await transformSource( - rawSource, - { url, format }, - defaultTransformSource - ); - source = transformedSource; - } - - return { format, source }; + return { format, source }; + }); } async function getFormat( @@ -384,3 +393,13 @@ export function createEsmHooks(tsNodeService: Service) { return hooksAPI; } + +async function addShortCircuitFlag(fn: () => Promise) { + const ret = await fn(); + // Not sure if this is necessary; being lazy. Can revisit in the future. + if (ret == null) return ret; + return { + ...ret, + shortCircuit: true, + }; +}