@@ -84,6 +84,18 @@ import { createPluginHookUtils } from '../plugins'
8484import { buildErrorMessage } from './middlewares/error'
8585import type { ModuleGraph } from './moduleGraph'
8686
87+ export const ERR_CLOSED_SERVER = 'ERR_CLOSED_SERVER'
88+
89+ export function throwClosedServerError ( ) : never {
90+ const err : any = new Error (
91+ 'The server is being restarted or closed. Request is outdated' ,
92+ )
93+ err . code = ERR_CLOSED_SERVER
94+ // This error will be caught by the transform middleware that will
95+ // send a 504 status code request timeout
96+ throw err
97+ }
98+
8799export interface PluginContainerOptions {
88100 cwd ?: string
89101 output ?: OutputOptions
@@ -195,6 +207,7 @@ export async function createPluginContainer(
195207 ) : Promise < void > {
196208 const parallelPromises : Promise < unknown > [ ] = [ ]
197209 for ( const plugin of getSortedPlugins ( hookName ) ) {
210+ // Don't throw here if closed, so buildEnd and closeBundle hooks can finish running
198211 const hook = plugin [ hookName ]
199212 if ( ! hook ) continue
200213 // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -571,12 +584,26 @@ export async function createPluginContainer(
571584 }
572585
573586 let closed = false
587+ const processesing = new Set < Promise < any > > ( )
588+ // keeps track of hook promises so that we can wait for them all to finish upon closing the server
589+ function handleHookPromise < T > ( maybePromise : undefined | T | Promise < T > ) {
590+ if ( ! ( maybePromise as any ) ?. then ) {
591+ return maybePromise
592+ }
593+ const promise = maybePromise as Promise < T >
594+ processesing . add ( promise )
595+ return promise . finally ( ( ) => processesing . delete ( promise ) )
596+ }
574597
575598 const container : PluginContainer = {
576599 options : await ( async ( ) => {
577600 let options = rollupOptions
578601 for ( const optionsHook of getSortedPluginHooks ( 'options' ) ) {
579- options = ( await optionsHook . call ( minimalContext , options ) ) || options
602+ if ( closed ) throwClosedServerError ( )
603+ options =
604+ ( await handleHookPromise (
605+ optionsHook . call ( minimalContext , options ) ,
606+ ) ) || options
580607 }
581608 if ( options . acornInjectPlugins ) {
582609 parser = acorn . Parser . extend (
@@ -593,10 +620,12 @@ export async function createPluginContainer(
593620 getModuleInfo,
594621
595622 async buildStart ( ) {
596- await hookParallel (
597- 'buildStart' ,
598- ( plugin ) => new Context ( plugin ) ,
599- ( ) => [ container . options as NormalizedInputOptions ] ,
623+ await handleHookPromise (
624+ hookParallel (
625+ 'buildStart' ,
626+ ( plugin ) => new Context ( plugin ) ,
627+ ( ) => [ container . options as NormalizedInputOptions ] ,
628+ ) ,
600629 )
601630 } ,
602631
@@ -609,10 +638,10 @@ export async function createPluginContainer(
609638 ctx . _scan = scan
610639 ctx . _resolveSkips = skip
611640 const resolveStart = debugResolve ? performance . now ( ) : 0
612-
613641 let id : string | null = null
614642 const partial : Partial < PartialResolvedId > = { }
615643 for ( const plugin of getSortedPlugins ( 'resolveId' ) ) {
644+ if ( closed ) throwClosedServerError ( )
616645 if ( ! plugin . resolveId ) continue
617646 if ( skip ?. has ( plugin ) ) continue
618647
@@ -623,13 +652,15 @@ export async function createPluginContainer(
623652 'handler' in plugin . resolveId
624653 ? plugin . resolveId . handler
625654 : plugin . resolveId
626- const result = await handler . call ( ctx as any , rawId , importer , {
627- assertions : options ?. assertions ?? { } ,
628- custom : options ?. custom ,
629- isEntry : ! ! options ?. isEntry ,
630- ssr,
631- scan,
632- } )
655+ const result = await handleHookPromise (
656+ handler . call ( ctx as any , rawId , importer , {
657+ assertions : options ?. assertions ?? { } ,
658+ custom : options ?. custom ,
659+ isEntry : ! ! options ?. isEntry ,
660+ ssr,
661+ scan,
662+ } ) ,
663+ )
633664 if ( ! result ) continue
634665
635666 if ( typeof result === 'string' ) {
@@ -675,11 +706,14 @@ export async function createPluginContainer(
675706 const ctx = new Context ( )
676707 ctx . ssr = ! ! ssr
677708 for ( const plugin of getSortedPlugins ( 'load' ) ) {
709+ if ( closed ) throwClosedServerError ( )
678710 if ( ! plugin . load ) continue
679711 ctx . _activePlugin = plugin
680712 const handler =
681713 'handler' in plugin . load ? plugin . load . handler : plugin . load
682- const result = await handler . call ( ctx as any , id , { ssr } )
714+ const result = await handleHookPromise (
715+ handler . call ( ctx as any , id , { ssr } ) ,
716+ )
683717 if ( result != null ) {
684718 if ( isObject ( result ) ) {
685719 updateModuleInfo ( id , result )
@@ -696,6 +730,7 @@ export async function createPluginContainer(
696730 const ctx = new TransformContext ( id , code , inMap as SourceMap )
697731 ctx . ssr = ! ! ssr
698732 for ( const plugin of getSortedPlugins ( 'transform' ) ) {
733+ if ( closed ) throwClosedServerError ( )
699734 if ( ! plugin . transform ) continue
700735 ctx . _activePlugin = plugin
701736 ctx . _activeId = id
@@ -707,7 +742,9 @@ export async function createPluginContainer(
707742 ? plugin . transform . handler
708743 : plugin . transform
709744 try {
710- result = await handler . call ( ctx as any , code , id , { ssr } )
745+ result = await handleHookPromise (
746+ handler . call ( ctx as any , code , id , { ssr } ) ,
747+ )
711748 } catch ( e ) {
712749 ctx . error ( e )
713750 }
@@ -741,6 +778,8 @@ export async function createPluginContainer(
741778
742779 async close ( ) {
743780 if ( closed ) return
781+ closed = true
782+ await Promise . allSettled ( Array . from ( processesing ) )
744783 const ctx = new Context ( )
745784 await hookParallel (
746785 'buildEnd' ,
@@ -752,7 +791,6 @@ export async function createPluginContainer(
752791 ( ) => ctx ,
753792 ( ) => [ ] ,
754793 )
755- closed = true
756794 } ,
757795 }
758796
0 commit comments