@@ -84,6 +84,18 @@ import { createPluginHookUtils } from '../plugins'
84
84
import { buildErrorMessage } from './middlewares/error'
85
85
import type { ModuleGraph } from './moduleGraph'
86
86
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
+
87
99
export interface PluginContainerOptions {
88
100
cwd ?: string
89
101
output ?: OutputOptions
@@ -195,6 +207,7 @@ export async function createPluginContainer(
195
207
) : Promise < void > {
196
208
const parallelPromises : Promise < unknown > [ ] = [ ]
197
209
for ( const plugin of getSortedPlugins ( hookName ) ) {
210
+ // Don't throw here if closed, so buildEnd and closeBundle hooks can finish running
198
211
const hook = plugin [ hookName ]
199
212
if ( ! hook ) continue
200
213
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -571,12 +584,26 @@ export async function createPluginContainer(
571
584
}
572
585
573
586
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
+ }
574
597
575
598
const container : PluginContainer = {
576
599
options : await ( async ( ) => {
577
600
let options = rollupOptions
578
601
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
580
607
}
581
608
if ( options . acornInjectPlugins ) {
582
609
parser = acorn . Parser . extend (
@@ -593,10 +620,12 @@ export async function createPluginContainer(
593
620
getModuleInfo,
594
621
595
622
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
+ ) ,
600
629
)
601
630
} ,
602
631
@@ -609,10 +638,10 @@ export async function createPluginContainer(
609
638
ctx . _scan = scan
610
639
ctx . _resolveSkips = skip
611
640
const resolveStart = debugResolve ? performance . now ( ) : 0
612
-
613
641
let id : string | null = null
614
642
const partial : Partial < PartialResolvedId > = { }
615
643
for ( const plugin of getSortedPlugins ( 'resolveId' ) ) {
644
+ if ( closed ) throwClosedServerError ( )
616
645
if ( ! plugin . resolveId ) continue
617
646
if ( skip ?. has ( plugin ) ) continue
618
647
@@ -623,13 +652,15 @@ export async function createPluginContainer(
623
652
'handler' in plugin . resolveId
624
653
? plugin . resolveId . handler
625
654
: 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
+ )
633
664
if ( ! result ) continue
634
665
635
666
if ( typeof result === 'string' ) {
@@ -675,11 +706,14 @@ export async function createPluginContainer(
675
706
const ctx = new Context ( )
676
707
ctx . ssr = ! ! ssr
677
708
for ( const plugin of getSortedPlugins ( 'load' ) ) {
709
+ if ( closed ) throwClosedServerError ( )
678
710
if ( ! plugin . load ) continue
679
711
ctx . _activePlugin = plugin
680
712
const handler =
681
713
'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
+ )
683
717
if ( result != null ) {
684
718
if ( isObject ( result ) ) {
685
719
updateModuleInfo ( id , result )
@@ -696,6 +730,7 @@ export async function createPluginContainer(
696
730
const ctx = new TransformContext ( id , code , inMap as SourceMap )
697
731
ctx . ssr = ! ! ssr
698
732
for ( const plugin of getSortedPlugins ( 'transform' ) ) {
733
+ if ( closed ) throwClosedServerError ( )
699
734
if ( ! plugin . transform ) continue
700
735
ctx . _activePlugin = plugin
701
736
ctx . _activeId = id
@@ -707,7 +742,9 @@ export async function createPluginContainer(
707
742
? plugin . transform . handler
708
743
: plugin . transform
709
744
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
+ )
711
748
} catch ( e ) {
712
749
ctx . error ( e )
713
750
}
@@ -741,6 +778,8 @@ export async function createPluginContainer(
741
778
742
779
async close ( ) {
743
780
if ( closed ) return
781
+ closed = true
782
+ await Promise . allSettled ( Array . from ( processesing ) )
744
783
const ctx = new Context ( )
745
784
await hookParallel (
746
785
'buildEnd' ,
@@ -752,7 +791,6 @@ export async function createPluginContainer(
752
791
( ) => ctx ,
753
792
( ) => [ ] ,
754
793
)
755
- closed = true
756
794
} ,
757
795
}
758
796
0 commit comments