@@ -32,6 +32,9 @@ import { callInChildWithEsm } from './child/spawn-child-with-esm';
32
32
import { findAndReadConfig } from './configuration' ;
33
33
import { getChildProcessArguments } from './child/child-exec-args' ;
34
34
35
+ type MarkPropAsRequired < T , K extends keyof T > = Omit < T , K > &
36
+ Required < Pick < T , K >> ;
37
+
35
38
/**
36
39
* Main `bin` functionality.
37
40
*
@@ -73,66 +76,74 @@ export interface BootstrapState {
73
76
* only capture information that should be persisted to e.g. forked child processes.
74
77
*/
75
78
export interface BootstrapStateForForkedProcesses {
76
- // For the final bootstrap we are only interested in the user arguments
77
- // that should be passed to the entry-point script (or eval script).
78
- // We don't want to encode any options that would break child forking. e.g .
79
- // persisting the `--eval` option would break `child_process.fork` in scripts.
80
- parseArgvResult : Pick < ReturnType < typeof parseArgv > , 'restArgs' > ;
79
+ // For the final bootstrap we are only interested in the user arguments that should
80
+ // be passed to the entry-point script (or eval script). We don't want to encode any
81
+ // other options from `parseArgvResult` that would break child forking.
82
+ // e.g. persisting the `--eval` option would break `child_process.fork` in scripts.
83
+ restArgs : string [ ] ;
81
84
phase3Result : Pick <
82
85
ReturnType < typeof phase3 > ,
83
86
'enableEsmLoader' | 'preloadedConfig'
84
87
> ;
85
88
}
86
89
87
- export interface BootstrapStateInitialProcessChild
88
- extends Omit < BootstrapStateForForkedProcesses , 'initialProcessOptions' > {
89
- initialProcessOptions : { resolutionCwd : string } & Pick <
90
- ReturnType < typeof parseArgv > ,
91
- // These are options which should not persist into forked child processes,
92
- // but can be passed-through in the initial child process creation -- but should
93
- // not be encoded in the Brotli state for child process forks (through `execArgv`.)
94
- 'version' | 'showConfig' | 'code' | 'print' | 'interactive'
95
- > ;
90
+ export interface BootstrapStateInitialProcess
91
+ extends Omit < BootstrapStateForForkedProcesses , 'phase3Result' > {
92
+ initialChildArgv : ReturnType < typeof parseArgv > ;
93
+ initialChildResolutionCwd : string ;
94
+ phase3Result ?: ReturnType < typeof phase3 > ;
96
95
}
97
96
98
- export type BootstrapStateForChild = Omit <
99
- BootstrapStateForForkedProcesses ,
100
- 'initialProcessOptions'
101
- > &
102
- Partial < BootstrapStateInitialProcessChild > ;
97
+ export type BootstrapStateForChild = BootstrapStateForForkedProcesses &
98
+ Partial < BootstrapStateInitialProcess > ;
103
99
104
100
/** @internal */
105
101
export function bootstrap ( state : BootstrapState ) {
106
102
state . phase2Result = phase2 ( state ) ;
107
- state . phase3Result = phase3 ( state ) ;
108
-
109
- const initialChildState : BootstrapStateInitialProcessChild = {
110
- ...createBootstrapStateForChildProcess ( state as Required < BootstrapState > ) ,
111
- // Aside with the default child process state, we attach the initial process
112
- // options since this `callInChild` invocation is from the initial process.
113
- // Later when forking, the initial process options are omitted / not persisted.
114
- initialProcessOptions : {
115
- code : state . parseArgvResult . code ,
116
- interactive : state . parseArgvResult . interactive ,
117
- print : state . parseArgvResult . print ,
118
- showConfig : state . parseArgvResult . showConfig ,
119
- version : state . parseArgvResult . version ,
120
- resolutionCwd : state . phase2Result . resolutionCwd ,
121
- } ,
103
+
104
+ const initialProcessState : BootstrapStateInitialProcess = {
105
+ restArgs : state . parseArgvResult . restArgs ,
106
+ initialChildArgv : state . parseArgvResult ,
107
+ initialChildResolutionCwd : state . phase2Result . resolutionCwd ,
122
108
} ;
123
109
110
+ // Perf optimization for ESM until ESM hooks can be registered without needing
111
+ // a child process. We skip phase3 and defer it to the child process where we
112
+ // would load the TS compiler anyway, avoiding loading it twice in different processes.
113
+ if ( initialProcessState . initialChildArgv . esm ) {
114
+ callInChildWithEsm ( initialProcessState , process . cwd ( ) ) ;
115
+ return ;
116
+ }
117
+
118
+ const phase3Result = phase3 ( initialProcessState ) ;
119
+
124
120
// For ESM, we need to spawn a new Node process to be able to register our hooks.
125
- if ( state . phase3Result . enableEsmLoader ) {
121
+ if ( phase3Result . enableEsmLoader ) {
126
122
// Note: When transitioning into the child process for the final phase,
127
123
// we want to preserve the initial user working directory.
128
- callInChildWithEsm ( initialChildState , process . cwd ( ) ) ;
124
+ callInChildWithEsm ( initialProcessState , process . cwd ( ) ) ;
129
125
} else {
130
- completeBootstrap ( initialChildState ) ;
126
+ completeBootstrap ( { ... initialProcessState , phase3Result } ) ;
131
127
}
132
128
}
133
129
/** Final phase of the bootstrap. */
134
- export function completeBootstrap ( state : BootstrapStateForChild ) {
135
- return phase4 ( state ) ;
130
+ export function completeBootstrap (
131
+ state : BootstrapStateForForkedProcesses | BootstrapStateInitialProcess
132
+ ) {
133
+ // IMPORTANT: This is an optimization when we detected `--esm` early in the CLI.
134
+ // In such cases we skip phase3 and let phase3 to be processed in the child process here.
135
+ // This avoids loading the TS compiler twice as loading TS is rather slow.
136
+ // TODO: Remove this when we don't need to spawn a child process for ESM. See:
137
+ if ( state . phase3Result === undefined ) {
138
+ state . phase3Result = phase3 ( state as BootstrapStateInitialProcess ) ;
139
+ }
140
+
141
+ return phase4 (
142
+ state as MarkPropAsRequired <
143
+ BootstrapStateForForkedProcesses | BootstrapStateInitialProcess ,
144
+ 'phase3Result'
145
+ >
146
+ ) ;
136
147
}
137
148
138
149
function parseArgv ( argv : string [ ] , entrypointArgs : Record < string , any > ) {
@@ -394,8 +405,8 @@ function phase3(payload: BootstrapState) {
394
405
scopeDir,
395
406
esm,
396
407
experimentalSpecifierResolution,
397
- } = payload . parseArgvResult ;
398
- const { resolutionCwd } = payload . phase2Result ! ;
408
+ } = payload . initialChildArgv ;
409
+ const resolutionCwd = payload . initialChildResolutionCwd ;
399
410
400
411
// NOTE: When we transition to a child process for ESM, the entry-point script determined
401
412
// here might not be the one used later in `phase4`. This can happen when we execute the
@@ -405,7 +416,7 @@ function phase3(payload: BootstrapState) {
405
416
// See: https://github.com/TypeStrong/ts-node/issues/1812.
406
417
const { entryPointPath } = getEntryPointInfo (
407
418
resolutionCwd ,
408
- payload . parseArgvResult !
419
+ payload . initialChildArgv
409
420
) ;
410
421
411
422
const preloadedConfig = findAndReadConfig ( {
@@ -498,10 +509,9 @@ function getEntryPointInfo(
498
509
}
499
510
500
511
function phase4 ( payload : BootstrapStateForChild ) {
501
- const { restArgs } = payload . parseArgvResult ;
512
+ const restArgs = payload . restArgs ;
502
513
const { preloadedConfig } = payload . phase3Result ;
503
- const resolutionCwd =
504
- payload . initialProcessOptions ?. resolutionCwd ?? process . cwd ( ) ;
514
+ const resolutionCwd = payload . initialChildResolutionCwd ?? process . cwd ( ) ;
505
515
506
516
const {
507
517
entryPointPath,
@@ -510,9 +520,9 @@ function phase4(payload: BootstrapStateForChild) {
510
520
executeRepl,
511
521
executeStdin,
512
522
} = getEntryPointInfo ( resolutionCwd , {
513
- code : payload . initialProcessOptions ?. code ,
514
- interactive : payload . initialProcessOptions ?. interactive ,
515
- restArgs : payload . parseArgvResult . restArgs ,
523
+ code : payload . initialChildArgv ?. code ,
524
+ interactive : payload . initialChildArgv ?. interactive ,
525
+ restArgs : payload . restArgs ,
516
526
} ) ;
517
527
518
528
/**
@@ -597,13 +607,13 @@ function phase4(payload: BootstrapStateForChild) {
597
607
stdinStuff ?. repl . setService ( service ) ;
598
608
599
609
// Output project information.
600
- if ( payload . initialProcessOptions ?. version === 2 ) {
610
+ if ( payload . initialChildArgv ?. version === 2 ) {
601
611
console . log ( `ts-node v${ VERSION } ` ) ;
602
612
console . log ( `node ${ process . version } ` ) ;
603
613
console . log ( `compiler v${ service . ts . version } ` ) ;
604
614
process . exit ( 0 ) ;
605
615
}
606
- if ( ( payload . initialProcessOptions ?. version ?? 0 ) >= 3 ) {
616
+ if ( ( payload . initialChildArgv ?. version ?? 0 ) >= 3 ) {
607
617
console . log ( `ts-node v${ VERSION } ${ dirname ( __dirname ) } ` ) ;
608
618
console . log ( `node ${ process . version } ` ) ;
609
619
console . log (
@@ -612,7 +622,7 @@ function phase4(payload: BootstrapStateForChild) {
612
622
process . exit ( 0 ) ;
613
623
}
614
624
615
- if ( payload . initialProcessOptions ?. showConfig ) {
625
+ if ( payload . initialChildArgv ?. showConfig ) {
616
626
const ts = service . ts as any as TSInternal ;
617
627
if ( typeof ts . convertToTSConfig !== 'function' ) {
618
628
console . error (
@@ -700,8 +710,8 @@ function phase4(payload: BootstrapStateForChild) {
700
710
evalAndExitOnTsError (
701
711
evalStuff ! . repl ,
702
712
evalStuff ! . module ! ,
703
- payload . initialProcessOptions ! . code ! ,
704
- payload . initialProcessOptions ! . print ,
713
+ payload . initialChildArgv ! . code ! ,
714
+ payload . initialChildArgv ! . print ,
705
715
'eval'
706
716
) ;
707
717
}
@@ -711,15 +721,15 @@ function phase4(payload: BootstrapStateForChild) {
711
721
}
712
722
713
723
if ( executeStdin ) {
714
- let buffer = payload . initialProcessOptions ?. code ?? '' ;
724
+ let buffer = payload . initialChildArgv ?. code ?? '' ;
715
725
process . stdin . on ( 'data' , ( chunk : Buffer ) => ( buffer += chunk ) ) ;
716
726
process . stdin . on ( 'end' , ( ) => {
717
727
evalAndExitOnTsError (
718
728
stdinStuff ! . repl ,
719
729
stdinStuff ! . module ! ,
720
730
buffer ,
721
731
// `echo 123 | node -p` still prints 123
722
- payload . initialProcessOptions ?. print ?? false ,
732
+ payload . initialChildArgv ?. print ?? false ,
723
733
'stdin'
724
734
) ;
725
735
} ) ;
@@ -728,14 +738,12 @@ function phase4(payload: BootstrapStateForChild) {
728
738
}
729
739
730
740
function createBootstrapStateForChildProcess (
731
- state : BootstrapStateInitialProcessChild | BootstrapStateForForkedProcesses
741
+ state : BootstrapStateInitialProcess | BootstrapStateForForkedProcesses
732
742
) : BootstrapStateForForkedProcesses {
733
743
// NOTE: Build up the child process fork bootstrap state manually so that we do
734
744
// not encode unnecessary properties into the bootstrap state that is persisted
735
745
return {
736
- parseArgvResult : {
737
- restArgs : state . parseArgvResult . restArgs ,
738
- } ,
746
+ restArgs : state . restArgs ,
739
747
phase3Result : {
740
748
enableEsmLoader : state . phase3Result ! . enableEsmLoader ,
741
749
preloadedConfig : state . phase3Result ! . preloadedConfig ,
0 commit comments