@@ -15,6 +15,7 @@ import {
15
15
parse ,
16
16
split ,
17
17
yn ,
18
+ isRelativeSpecifier ,
18
19
} from './util' ;
19
20
import { readConfig } from './configuration' ;
20
21
import type { TSCommon , TSInternal } from './ts-compiler-types' ;
@@ -117,6 +118,7 @@ export interface ProcessEnv {
117
118
TS_NODE_SKIP_PROJECT ?: string ;
118
119
TS_NODE_SKIP_IGNORE ?: string ;
119
120
TS_NODE_PREFER_TS_EXTS ?: string ;
121
+ TS_NODE_TRY_TS_EXT ?: string ;
120
122
TS_NODE_IGNORE_DIAGNOSTICS ?: string ;
121
123
TS_NODE_TRANSPILE_ONLY ?: string ;
122
124
TS_NODE_TYPE_CHECK ?: string ;
@@ -353,6 +355,14 @@ export interface RegisterOptions extends CreateOptions {
353
355
* @default false
354
356
*/
355
357
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
356
366
}
357
367
358
368
/**
@@ -400,6 +410,7 @@ export const DEFAULTS: RegisterOptions = {
400
410
skipProject : yn ( env . TS_NODE_SKIP_PROJECT ) ,
401
411
skipIgnore : yn ( env . TS_NODE_SKIP_IGNORE ) ,
402
412
preferTsExts : yn ( env . TS_NODE_PREFER_TS_EXTS ) ,
413
+ tryTsExt : yn ( env . TS_NODE_TRY_TS_EXT ) ,
403
414
ignoreDiagnostics : split ( env . TS_NODE_IGNORE_DIAGNOSTICS ) ,
404
415
transpileOnly : yn ( env . TS_NODE_TRANSPILE_ONLY ) ,
405
416
typeCheck : yn ( env . TS_NODE_TYPE_CHECK ) ,
@@ -477,6 +488,44 @@ export function getExtensions(config: _ts.ParsedCommandLine) {
477
488
return { tsExtensions, jsExtensions } ;
478
489
}
479
490
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
+
480
529
/**
481
530
* Register TypeScript compiler instance onto node.js
482
531
*/
@@ -497,6 +546,10 @@ export function register(opts: RegisterOptions = {}): Service {
497
546
originalJsHandler
498
547
) ;
499
548
549
+ if ( service . options . tryTsExt ) {
550
+ patchResolveFileName ( ) ;
551
+ }
552
+
500
553
// Require specified modules before start-up.
501
554
( Module as any ) . _preloadModules ( service . options . require ) ;
502
555
@@ -548,6 +601,7 @@ export function create(rawOptions: CreateOptions = {}): Service {
548
601
...( tsNodeOptionsFromTsconfig . require || [ ] ) ,
549
602
...( rawOptions . require || [ ] ) ,
550
603
] ;
604
+ options . preferTsExts = options . preferTsExts || options . tryTsExt
551
605
552
606
// Experimental REPL await is not compatible targets lower than ES2018
553
607
const targetSupportsTla = config . options . target ! >= ts . ScriptTarget . ES2018 ;
0 commit comments