Skip to content

Commit 0e8b827

Browse files
authored
add short-circuit flag to all ESM hook return values (#1715)
1 parent 511e0e7 commit 0e8b827

File tree

1 file changed

+78
-59
lines changed

1 file changed

+78
-59
lines changed

src/esm.ts

Lines changed: 78 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,11 @@ export namespace NodeLoaderHooksAPI2 {
6969
parentURL: string;
7070
},
7171
defaultResolve: ResolveHook
72-
) => Promise<{ url: string; format?: NodeLoaderHooksFormat }>;
72+
) => Promise<{
73+
url: string;
74+
format?: NodeLoaderHooksFormat;
75+
shortCircuit?: boolean;
76+
}>;
7377
export type LoadHook = (
7478
url: string,
7579
context: {
@@ -80,6 +84,7 @@ export namespace NodeLoaderHooksAPI2 {
8084
) => Promise<{
8185
format: NodeLoaderHooksFormat;
8286
source: string | Buffer | undefined;
87+
shortCircuit?: boolean;
8388
}>;
8489
export type NodeImportConditions = unknown;
8590
export interface NodeImportAssertions {
@@ -205,32 +210,34 @@ export function createEsmHooks(tsNodeService: Service) {
205210
}
206211
}
207212

208-
const parsed = parseUrl(specifier);
209-
const { pathname, protocol, hostname } = parsed;
213+
return addShortCircuitFlag(async () => {
214+
const parsed = parseUrl(specifier);
215+
const { pathname, protocol, hostname } = parsed;
210216

211-
if (!isFileUrlOrNodeStyleSpecifier(parsed)) {
212-
return entrypointFallback(defer);
213-
}
217+
if (!isFileUrlOrNodeStyleSpecifier(parsed)) {
218+
return entrypointFallback(defer);
219+
}
214220

215-
if (protocol !== null && protocol !== 'file:') {
216-
return entrypointFallback(defer);
217-
}
221+
if (protocol !== null && protocol !== 'file:') {
222+
return entrypointFallback(defer);
223+
}
218224

219-
// Malformed file:// URL? We should always see `null` or `''`
220-
if (hostname) {
221-
// TODO file://./foo sets `hostname` to `'.'`. Perhaps we should special-case this.
222-
return entrypointFallback(defer);
223-
}
225+
// Malformed file:// URL? We should always see `null` or `''`
226+
if (hostname) {
227+
// TODO file://./foo sets `hostname` to `'.'`. Perhaps we should special-case this.
228+
return entrypointFallback(defer);
229+
}
224230

225-
// pathname is the path to be resolved
231+
// pathname is the path to be resolved
226232

227-
return entrypointFallback(() =>
228-
nodeResolveImplementation.defaultResolve(
229-
specifier,
230-
context,
231-
defaultResolve
232-
)
233-
);
233+
return entrypointFallback(() =>
234+
nodeResolveImplementation.defaultResolve(
235+
specifier,
236+
context,
237+
defaultResolve
238+
)
239+
);
240+
});
234241
}
235242

236243
// `load` from new loader hook API (See description at the top of this file)
@@ -245,47 +252,49 @@ export function createEsmHooks(tsNodeService: Service) {
245252
format: NodeLoaderHooksFormat;
246253
source: string | Buffer | undefined;
247254
}> {
248-
// If we get a format hint from resolve() on the context then use it
249-
// otherwise call the old getFormat() hook using node's old built-in defaultGetFormat() that ships with ts-node
250-
const format =
251-
context.format ??
252-
(await getFormat(url, context, defaultGetFormat)).format;
253-
254-
let source = undefined;
255-
if (format !== 'builtin' && format !== 'commonjs') {
256-
// Call the new defaultLoad() to get the source
257-
const { source: rawSource } = await defaultLoad(
258-
url,
259-
{
260-
...context,
261-
format,
262-
},
263-
defaultLoad
264-
);
255+
return addShortCircuitFlag(async () => {
256+
// If we get a format hint from resolve() on the context then use it
257+
// otherwise call the old getFormat() hook using node's old built-in defaultGetFormat() that ships with ts-node
258+
const format =
259+
context.format ??
260+
(await getFormat(url, context, defaultGetFormat)).format;
261+
262+
let source = undefined;
263+
if (format !== 'builtin' && format !== 'commonjs') {
264+
// Call the new defaultLoad() to get the source
265+
const { source: rawSource } = await defaultLoad(
266+
url,
267+
{
268+
...context,
269+
format,
270+
},
271+
defaultLoad
272+
);
273+
274+
if (rawSource === undefined || rawSource === null) {
275+
throw new Error(
276+
`Failed to load raw source: Format was '${format}' and url was '${url}''.`
277+
);
278+
}
265279

266-
if (rawSource === undefined || rawSource === null) {
267-
throw new Error(
268-
`Failed to load raw source: Format was '${format}' and url was '${url}''.`
280+
// Emulate node's built-in old defaultTransformSource() so we can re-use the old transformSource() hook
281+
const defaultTransformSource: typeof transformSource = async (
282+
source,
283+
_context,
284+
_defaultTransformSource
285+
) => ({ source });
286+
287+
// Call the old hook
288+
const { source: transformedSource } = await transformSource(
289+
rawSource,
290+
{ url, format },
291+
defaultTransformSource
269292
);
293+
source = transformedSource;
270294
}
271295

272-
// Emulate node's built-in old defaultTransformSource() so we can re-use the old transformSource() hook
273-
const defaultTransformSource: typeof transformSource = async (
274-
source,
275-
_context,
276-
_defaultTransformSource
277-
) => ({ source });
278-
279-
// Call the old hook
280-
const { source: transformedSource } = await transformSource(
281-
rawSource,
282-
{ url, format },
283-
defaultTransformSource
284-
);
285-
source = transformedSource;
286-
}
287-
288-
return { format, source };
296+
return { format, source };
297+
});
289298
}
290299

291300
async function getFormat(
@@ -384,3 +393,13 @@ export function createEsmHooks(tsNodeService: Service) {
384393

385394
return hooksAPI;
386395
}
396+
397+
async function addShortCircuitFlag<T>(fn: () => Promise<T>) {
398+
const ret = await fn();
399+
// Not sure if this is necessary; being lazy. Can revisit in the future.
400+
if (ret == null) return ret;
401+
return {
402+
...ret,
403+
shortCircuit: true,
404+
};
405+
}

0 commit comments

Comments
 (0)