import type watcher from '@parcel/watcher' import { debounceAsync } from './concurrency-utils' import { EventEmitter } from './event-emitter' export type FileChangeHandler = (events: watcher.Event[]) => Promise type EmptyObject = Record type FileWatcherEvent = { error: unknown close: EmptyObject } type FileWatcherOptions = watcher.Options & { debounceMs?: number } /** * Wraps the Parcel file watcher to ensure errors can be catched in an async/await fashion */ export class FileWatcher { public static async watch(dir: string, fn: FileChangeHandler, opt?: FileWatcherOptions) { const eventEmitter = new EventEmitter() let subscriptionHandler = async (err: Error | null, events: watcher.Event[]) => { if (err) { eventEmitter.emit('error', err) return } try { await fn(events) } catch (thrown) { eventEmitter.emit('error', thrown) } } if (opt?.debounceMs) { subscriptionHandler = debounceAsync(subscriptionHandler, opt.debounceMs) } const watcher = await import('@parcel/watcher') const subscription = await watcher.subscribe(dir, subscriptionHandler, opt) return new FileWatcher(subscription, eventEmitter) } private constructor( private _subscription: watcher.AsyncSubscription, private _errorEmitter: EventEmitter ) {} public async close() { await this._subscription.unsubscribe() this._errorEmitter.emit('close', {}) } public wait = () => new Promise((resolve, reject) => { this._errorEmitter.once('error', reject) this._errorEmitter.once('close', resolve) }) }