Skip to content

Commit dd9044b

Browse files
committed
feat: drop js from request string
1 parent d936236 commit dd9044b

File tree

5 files changed

+75
-0
lines changed

5 files changed

+75
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"./register/files": "./register/files.js",
2020
"./register/transpile-only": "./register/transpile-only.js",
2121
"./register/type-check": "./register/type-check.js",
22+
"./register/try-ts-ext": "./register/try-ts-ext.js",
2223
"./esm": "./esm.mjs",
2324
"./esm.mjs": "./esm.mjs",
2425
"./esm/transpile-only": "./esm/transpile-only.mjs",

register/try-ts-ext.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
require('../dist').register({
2+
tryTsExt: true,
3+
});

src/configuration.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ function filterRecognizedTsConfigTsNodeOptions(
263263
ignoreDiagnostics,
264264
logError,
265265
preferTsExts,
266+
tryTsExt,
266267
pretty,
267268
require,
268269
skipIgnore,
@@ -286,6 +287,7 @@ function filterRecognizedTsConfigTsNodeOptions(
286287
ignoreDiagnostics,
287288
logError,
288289
preferTsExts,
290+
tryTsExt,
289291
pretty,
290292
require,
291293
skipIgnore,

src/index.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
parse,
1616
split,
1717
yn,
18+
isRelativeSpecifier,
1819
} from './util';
1920
import { readConfig } from './configuration';
2021
import type { TSCommon, TSInternal } from './ts-compiler-types';
@@ -117,6 +118,7 @@ export interface ProcessEnv {
117118
TS_NODE_SKIP_PROJECT?: string;
118119
TS_NODE_SKIP_IGNORE?: string;
119120
TS_NODE_PREFER_TS_EXTS?: string;
121+
TS_NODE_TRY_TS_EXT?: string;
120122
TS_NODE_IGNORE_DIAGNOSTICS?: string;
121123
TS_NODE_TRANSPILE_ONLY?: string;
122124
TS_NODE_TYPE_CHECK?: string;
@@ -353,6 +355,14 @@ export interface RegisterOptions extends CreateOptions {
353355
* @default false
354356
*/
355357
preferTsExts?: boolean;
358+
359+
/**
360+
* Attempt to resolve the typescript file when a request with a js extension is provided.
361+
* Implies preferTsExts.
362+
*
363+
* @default false
364+
*/
365+
tryTsExt?: boolean
356366
}
357367

358368
/**
@@ -400,6 +410,7 @@ export const DEFAULTS: RegisterOptions = {
400410
skipProject: yn(env.TS_NODE_SKIP_PROJECT),
401411
skipIgnore: yn(env.TS_NODE_SKIP_IGNORE),
402412
preferTsExts: yn(env.TS_NODE_PREFER_TS_EXTS),
413+
tryTsExt: yn(env.TS_NODE_TRY_TS_EXT),
403414
ignoreDiagnostics: split(env.TS_NODE_IGNORE_DIAGNOSTICS),
404415
transpileOnly: yn(env.TS_NODE_TRANSPILE_ONLY),
405416
typeCheck: yn(env.TS_NODE_TYPE_CHECK),
@@ -477,6 +488,44 @@ export function getExtensions(config: _ts.ParsedCommandLine) {
477488
return { tsExtensions, jsExtensions };
478489
}
479490

491+
function canDropJsExt(request: string, parentPath?: string) {
492+
if (isRelativeSpecifier(request) && request.slice(-3) === '.js') {
493+
if (!parentPath) return true
494+
const paths = require.main?.paths || []
495+
for(let i = 0; i < paths.length; i++) {
496+
if (parentPath.startsWith(paths[i])) {
497+
return false
498+
}
499+
}
500+
return true
501+
}
502+
}
503+
504+
505+
function patchResolveFileName() {
506+
const originalResolveFilename = (Module as any)._resolveFilename;
507+
508+
(Module as any)._resolveFilename = function (...args: any[]) {
509+
const [request, parent, isMain ] = args
510+
if (isMain) {
511+
return originalResolveFilename.apply(this, args);
512+
}
513+
if(canDropJsExt(request, parent?.path)) {
514+
try {
515+
return originalResolveFilename.call(this, request.slice(0, -3), ...args.slice(1))
516+
} catch(e) {
517+
const mainFile = originalResolveFilename.apply(this, args);
518+
if (mainFile.endsWith('.js')) {
519+
//re-resolve with configured extension preference
520+
return originalResolveFilename.call(this, mainFile.slice(0, -3), ...args.slice(1))
521+
}
522+
return mainFile
523+
}
524+
}
525+
return originalResolveFilename.apply(this, args);
526+
};
527+
}
528+
480529
/**
481530
* Register TypeScript compiler instance onto node.js
482531
*/
@@ -497,6 +546,10 @@ export function register(opts: RegisterOptions = {}): Service {
497546
originalJsHandler
498547
);
499548

549+
if (service.options.tryTsExt) {
550+
patchResolveFileName();
551+
}
552+
500553
// Require specified modules before start-up.
501554
(Module as any)._preloadModules(service.options.require);
502555

@@ -548,6 +601,7 @@ export function create(rawOptions: CreateOptions = {}): Service {
548601
...(tsNodeOptionsFromTsconfig.require || []),
549602
...(rawOptions.require || []),
550603
];
604+
options.preferTsExts = options.preferTsExts || options.tryTsExt
551605

552606
// Experimental REPL await is not compatible targets lower than ES2018
553607
const targetSupportsTla = config.options.target! >= ts.ScriptTarget.ES2018;

src/util.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,18 @@ export function cachedLookup<T, R>(fn: (arg: T) => R): (arg: T) => R {
9393
* `trace` options in hosts, I am using this placeholder.
9494
*/
9595
export function trace(s: string): void {}
96+
97+
/**
98+
*
99+
* Determine if a specifier is relative (from node core)
100+
* @internal
101+
*/
102+
export function isRelativeSpecifier(specifier: string) {
103+
if (specifier[0] === '.') {
104+
if (specifier.length === 1 || specifier[1] === '/') return true;
105+
if (specifier[1] === '.') {
106+
if (specifier.length === 2 || specifier[2] === '/') return true;
107+
}
108+
}
109+
return false;
110+
}

0 commit comments

Comments
 (0)