@@ -176,7 +176,29 @@ export class ModuleCacheMap extends Map<string, ModuleCache> {
176176 }
177177}
178178
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+ } >
180202
181203export class ViteNodeRunner {
182204 root : string
@@ -189,6 +211,27 @@ export class ViteNodeRunner {
189211 */
190212 moduleCache : ModuleCacheMap
191213
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+
192235 constructor ( public options : ViteNodeRunnerOptions ) {
193236 this . root = options . root ?? process . cwd ( )
194237 this . moduleCache = options . moduleCache ?? new ModuleCacheMap ( )
@@ -542,10 +585,51 @@ export class ViteNodeRunner {
542585 columnOffset : - codeDefinition . length ,
543586 }
544587
545- this . options . moduleExecutionInfo ?. set ( options . filename , { startOffset : codeDefinition . length } )
588+ const finishModuleExecutionInfo = this . startCalculateModuleExecutionInfo ( options . filename , codeDefinition . length )
546589
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+ }
549633 }
550634
551635 prepareContext ( context : Record < string , any > ) : Record < string , any > {
0 commit comments