@@ -176,7 +176,29 @@ export class ModuleCacheMap extends Map<string, ModuleCache> {
176
176
}
177
177
}
178
178
179
- export type ModuleExecutionInfo = Map < string , { startOffset : number } >
179
+ export type ModuleExecutionInfo = Map < string , ModuleExecutionInfoEntry >
180
+
181
+ export interface ModuleExecutionInfoEntry {
182
+ startOffset : number
183
+
184
+ /** The duration that was spent executing the module. */
185
+ duration : number
186
+
187
+ /** The time that was spent executing the module itself and externalized imports. */
188
+ selfTime : number
189
+ }
190
+
191
+ /** Stack to track nested module execution for self-time calculation. */
192
+ type ExecutionStack = Array < {
193
+ /** The file that is being executed. */
194
+ filename : string
195
+
196
+ /** The start time of this module's execution. */
197
+ startTime : number
198
+
199
+ /** Accumulated time spent importing all sub-imports. */
200
+ subImportTime : number
201
+ } >
180
202
181
203
export class ViteNodeRunner {
182
204
root : string
@@ -189,6 +211,27 @@ export class ViteNodeRunner {
189
211
*/
190
212
moduleCache : ModuleCacheMap
191
213
214
+ /**
215
+ * Tracks the stack of modules being executed for the purpose of calculating import self-time.
216
+ *
217
+ * Note that while in most cases, imports are a linear stack of modules,
218
+ * this is occasionally not the case, for example when you have parallel top-level dynamic imports like so:
219
+ *
220
+ * ```ts
221
+ * await Promise.all([
222
+ * import('./module1'),
223
+ * import('./module2'),
224
+ * ]);
225
+ * ```
226
+ *
227
+ * In this case, the self time will be reported incorrectly for one of the modules (could go negative).
228
+ * As top-level awaits with dynamic imports like this are uncommon, we don't handle this case specifically.
229
+ */
230
+ private executionStack : ExecutionStack = [ ]
231
+
232
+ // `performance` can be mocked, so make sure we're using the original function
233
+ private performanceNow = performance . now . bind ( performance )
234
+
192
235
constructor ( public options : ViteNodeRunnerOptions ) {
193
236
this . root = options . root ?? process . cwd ( )
194
237
this . moduleCache = options . moduleCache ?? new ModuleCacheMap ( )
@@ -542,10 +585,51 @@ export class ViteNodeRunner {
542
585
columnOffset : - codeDefinition . length ,
543
586
}
544
587
545
- this . options . moduleExecutionInfo ?. set ( options . filename , { startOffset : codeDefinition . length } )
588
+ const finishModuleExecutionInfo = this . startCalculateModuleExecutionInfo ( options . filename , codeDefinition . length )
546
589
547
- const fn = vm . runInThisContext ( code , options )
548
- await fn ( ...Object . values ( context ) )
590
+ try {
591
+ const fn = vm . runInThisContext ( code , options )
592
+ await fn ( ...Object . values ( context ) )
593
+ }
594
+ finally {
595
+ this . options . moduleExecutionInfo ?. set ( options . filename , finishModuleExecutionInfo ( ) )
596
+ }
597
+ }
598
+
599
+ /**
600
+ * Starts calculating the module execution info such as the total duration and self time spent on executing the module.
601
+ * Returns a function to call once the module has finished executing.
602
+ */
603
+ protected startCalculateModuleExecutionInfo ( filename : string , startOffset : number ) : ( ) => ModuleExecutionInfoEntry {
604
+ const startTime = this . performanceNow ( )
605
+
606
+ this . executionStack . push ( {
607
+ filename,
608
+ startTime,
609
+ subImportTime : 0 ,
610
+ } )
611
+
612
+ return ( ) => {
613
+ const duration = this . performanceNow ( ) - startTime
614
+
615
+ const currentExecution = this . executionStack . pop ( )
616
+
617
+ if ( currentExecution == null ) {
618
+ throw new Error ( 'Execution stack is empty, this should never happen' )
619
+ }
620
+
621
+ const selfTime = duration - currentExecution . subImportTime
622
+
623
+ if ( this . executionStack . length > 0 ) {
624
+ this . executionStack . at ( - 1 ) ! . subImportTime += duration
625
+ }
626
+
627
+ return {
628
+ startOffset,
629
+ duration,
630
+ selfTime,
631
+ }
632
+ }
549
633
}
550
634
551
635
prepareContext ( context : Record < string , any > ) : Record < string , any > {
0 commit comments