|
| 1 | +import { WatchEmitter } from './emitter'; |
| 2 | +import { FileWatcher } from './fileWatcher'; |
| 3 | +import type { ChangeEvent, ChokidarOptions, WatchEvent } from './types'; |
| 4 | + |
| 5 | +export interface WatchOptions { |
| 6 | + include?: string | RegExp | Array<string | RegExp>; |
| 7 | + exclude?: string | RegExp | Array<string | RegExp>; |
| 8 | + chokidar?: ChokidarOptions; |
| 9 | +} |
| 10 | + |
| 11 | +export class Watcher { |
| 12 | + private fileWatcher: FileWatcher; |
| 13 | + private emitter: WatchEmitter<{ |
| 14 | + event: (event: WatchEvent) => Promise<void>; |
| 15 | + change: (path: string, details: { event: ChangeEvent }) => Promise<void>; |
| 16 | + close: () => Promise<void>; |
| 17 | + }>; |
| 18 | + private closed = false; |
| 19 | + |
| 20 | + constructor(options: WatchOptions = {}) { |
| 21 | + this.emitter = new WatchEmitter(); |
| 22 | + this.fileWatcher = new FileWatcher( |
| 23 | + (id, event) => this.handleChange(id, event), |
| 24 | + options.chokidar, |
| 25 | + ); |
| 26 | + } |
| 27 | + |
| 28 | + watch(paths: string | string[]): void { |
| 29 | + const pathArray = Array.isArray(paths) ? paths : [paths]; |
| 30 | + for (const path of pathArray) { |
| 31 | + this.fileWatcher.watch(path); |
| 32 | + } |
| 33 | + } |
| 34 | + |
| 35 | + on<E extends 'event' | 'change' | 'close'>( |
| 36 | + event: E, |
| 37 | + callback: E extends 'event' |
| 38 | + ? (event: WatchEvent) => Promise<void> |
| 39 | + : E extends 'change' |
| 40 | + ? (id: string, details: { event: ChangeEvent }) => Promise<void> |
| 41 | + : () => Promise<void>, |
| 42 | + ): void { |
| 43 | + this.emitter.on(event, callback as any); |
| 44 | + } |
| 45 | + |
| 46 | + async close(): Promise<void> { |
| 47 | + if (this.closed) return; |
| 48 | + this.closed = true; |
| 49 | + this.fileWatcher.close(); |
| 50 | + await this.emitter.emit('close'); |
| 51 | + } |
| 52 | + |
| 53 | + /** |
| 54 | + * 提供两种层次的监听 |
| 55 | + * |
| 56 | + * 1. event:统一处理所有类型的事件 |
| 57 | + * example: |
| 58 | + * watcher.on('event', (event) => { |
| 59 | + * switch (event.code) { |
| 60 | + * case 'START': |
| 61 | + * console.log('Watch started'); |
| 62 | + * break; |
| 63 | + * case 'CHANGE': |
| 64 | + * console.log(`File ${event.id} ${event.event}`); |
| 65 | + * break; |
| 66 | + * case 'ERROR': |
| 67 | + * console.error('Watch error:', event.error); |
| 68 | + * break; |
| 69 | + * case 'END': |
| 70 | + * console.log('Watch ended'); |
| 71 | + * break; |
| 72 | + * } |
| 73 | + * }); |
| 74 | + * |
| 75 | + * 2. change:只关心文件变化 |
| 76 | + * example: |
| 77 | + * watcher.on('change', (id, {event}) => { |
| 78 | + * console.log(`File ${id} ${event}`); |
| 79 | + * }); |
| 80 | + */ |
| 81 | + private async handleChange(id: string, event: ChangeEvent): Promise<void> { |
| 82 | + await this.emitter.emit('event', { code: 'CHANGE', id, event }); |
| 83 | + await this.emitter.emit('change', id, { event }); |
| 84 | + } |
| 85 | +} |
0 commit comments