@@ -22,6 +22,7 @@ import {
2222 relative ,
2323 wordWrap ,
2424} from '../../utils/renderer'
25+ import { serializeBatches , type SerialBatches } from '../../utils/serial-batches'
2526import { drainStdin , outputFile } from './utils'
2627
2728const css = String . raw
@@ -326,15 +327,25 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
326327
327328 let [ compiler , scanner ] = await handleError ( ( ) => createCompiler ( input , I ) )
328329 let cleanupWatchers : ( ( ) => Promise < void > ) [ ] = [ ]
330+ let finishInitialBuild ! : ( ) => void
331+ let initialBuildFinished = new Promise < void > ( ( resolve ) => ( finishInitialBuild = resolve ) )
332+ let eventBatches : SerialBatches < string > | null = null
333+ let eventHandler : ( ( files : string [ ] ) => Promise < void > ) | null = null
329334
330335 // Watch for changes
331336 if ( args [ '--watch' ] && pollInterval === false ) {
332337 // Ensure the file watcher can be loaded before setting up any watchers,
333338 // such that we can present a helpful error message if needed.
334339 await handleError ( ( ) => loadWatcher ( ) )
335340
336- cleanupWatchers . push (
337- await createWatchers ( await watchDirectories ( scanner ) , async function handle ( files ) {
341+ eventBatches = serializeBatches (
342+ ( files ) => eventHandler ! ( files ) ,
343+ initialBuildFinished ,
344+ ( error ) => eprintln ( formatError ( error ) ) ,
345+ )
346+ let initialWatchers = await createWatchers (
347+ await watchDirectories ( scanner ) ,
348+ async function handle ( files ) {
338349 try {
339350 // If the only change happened to the output file, then we don't want to
340351 // trigger a rebuild because that will result in an infinite loop.
@@ -388,15 +399,19 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
388399
389400 // Setup new watchers
390401 DEBUG && I . start ( 'Setup new watchers' )
391- let newCleanupFunction = await createWatchers ( await watchDirectories ( scanner ) , handle )
402+ let newWatchers = await createWatchers (
403+ await watchDirectories ( scanner ) ,
404+ handle ,
405+ eventBatches ! ,
406+ )
392407 DEBUG && I . end ( 'Setup new watchers' )
393408
394409 // Clear old watchers
395410 DEBUG && I . start ( 'Cleanup old watchers' )
396411 await Promise . all ( cleanupWatchers . splice ( 0 ) . map ( ( cleanup ) => cleanup ( ) ) )
397412 DEBUG && I . end ( 'Cleanup old watchers' )
398413
399- cleanupWatchers . push ( newCleanupFunction )
414+ cleanupWatchers . push ( newWatchers . cleanup )
400415
401416 // Re-compile the CSS
402417 DEBUG && I . start ( 'Build CSS' )
@@ -481,17 +496,22 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
481496 let end = process . hrtime . bigint ( )
482497 if ( ! args [ '--silent' ] ) eprintln ( `Done in ${ formatDuration ( end - start ) } ` )
483498 }
484- } ) ,
499+ } ,
500+ eventBatches ,
485501 )
502+ eventHandler = initialWatchers . callback
503+ cleanupWatchers . push ( initialWatchers . cleanup )
486504
487505 // Abort the watcher if `stdin` is closed to avoid zombie processes. You can
488506 // disable this behavior with `--watch=always`.
489507 if ( args [ '--watch' ] !== 'always' ) {
490508 process . stdin . on ( 'end' , ( ) => {
491- Promise . all ( cleanupWatchers . map ( ( fn ) => fn ( ) ) ) . then (
492- ( ) => process . exit ( 0 ) ,
493- ( ) => process . exit ( 1 ) ,
494- )
509+ Promise . all ( cleanupWatchers . map ( ( fn ) => fn ( ) ) )
510+ . then ( ( ) => eventBatches ?. close ( ) )
511+ . then (
512+ ( ) => process . exit ( 0 ) ,
513+ ( ) => process . exit ( 1 ) ,
514+ )
495515 } )
496516 }
497517
@@ -515,6 +535,7 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
515535 }
516536
517537 await write ( output , map , args , I )
538+ finishInitialBuild ( )
518539
519540 let end = process . hrtime . bigint ( )
520541 if ( ! args [ '--silent' ] ) eprintln ( `Done in ${ formatDuration ( end - start ) } ` )
@@ -693,9 +714,13 @@ async function loadWatcher(): Promise<typeof import('@parcel/watcher')> {
693714 }
694715}
695716
696- async function createWatchers ( dirs : string [ ] , cb : ( files : string [ ] ) => void ) {
697- let watcher = await loadWatcher ( )
698-
717+ export async function createWatchers (
718+ dirs : string [ ] ,
719+ cb : ( files : string [ ] ) => Promise < void > ,
720+ batches : SerialBatches < string > ,
721+ watcher ?: Awaited < ReturnType < typeof loadWatcher > > ,
722+ ) {
723+ watcher ??= await loadWatcher ( )
699724 // Remove any directories that are children of an already watched directory.
700725 // If we don't we may not get notified of certain filesystem events regardless
701726 // of whether or not they are for the directory that is duplicated.
@@ -740,8 +765,9 @@ async function createWatchers(dirs: string[], cb: (files: string[]) => void) {
740765
741766 // Setup a new macrotask to handle the files in batch.
742767 debounceQueue . queueMacrotask ( ( ) => {
743- cb ( Array . from ( files ) )
768+ let batch = Array . from ( files )
744769 files . clear ( )
770+ void batches . push ( batch )
745771 } )
746772 }
747773
@@ -791,9 +817,17 @@ async function createWatchers(dirs: string[], cb: (files: string[]) => void) {
791817 }
792818
793819 // Cleanup
794- return async ( ) => {
795- await watchers . dispose ( )
796- await debounceQueue . dispose ( )
820+ return {
821+ callback : cb ,
822+ cleanup : async ( ) => {
823+ await watchers . dispose ( )
824+ await debounceQueue . dispose ( )
825+ if ( files . size > 0 ) {
826+ let batch = Array . from ( files )
827+ files . clear ( )
828+ void batches . push ( batch )
829+ }
830+ } ,
797831 }
798832}
799833
0 commit comments