-
-
Notifications
You must be signed in to change notification settings - Fork 75
/
watcher.ts
115 lines (91 loc) 路 2.63 KB
/
watcher.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import { join, relative } from "../deps/path.ts";
import Events from "./events.ts";
import type { Event, EventListener, EventOptions } from "../core.ts";
/** The options to configure the local server */
export interface Options {
/** The folder root to watch */
root: string;
/** Paths ignored by the watcher */
ignore?: string[];
/** The debounce waiting time */
debounce?: number;
}
/** Custom events for server */
export interface WatchEvent extends Event {
/** The event type */
type: WatchEventType;
/** The list of changed files (only for "change" events) */
files?: Set<string>;
/** The error object (only for "error" events) */
error?: Error;
}
/** The available event types */
export type WatchEventType =
| "start"
| "change"
| "error";
export default class Watcher {
events: Events<WatchEvent> = new Events<WatchEvent>();
options: Options;
constructor(options: Options) {
this.options = options;
}
/** Add a listener to an event */
addEventListener(
type: WatchEventType,
listener: EventListener<WatchEvent>,
options?: EventOptions,
) {
this.events.addEventListener(type, listener, options);
return this;
}
/** Dispatch an event */
dispatchEvent(event: WatchEvent) {
return this.events.dispatchEvent(event);
}
/** Start the file watcher */
async start() {
const { root, ignore, debounce } = this.options;
const watcher = Deno.watchFs(root);
const changes = new Set<string>();
let timer = 0;
let runningCallback = false;
await this.dispatchEvent({ type: "start" });
const callback = async () => {
if (!changes.size || runningCallback) {
return;
}
const files = new Set(changes);
changes.clear();
runningCallback = true;
try {
const result = await this.dispatchEvent({ type: "change", files });
if (false === result) {
return watcher.close();
}
} catch (error) {
await this.dispatchEvent({ type: "error", error });
}
runningCallback = false;
};
for await (const event of watcher) {
let { paths } = event;
// Filter ignored paths
paths = paths.filter((path) => {
if (path.endsWith(".DS_Store")) { // macOS file
return false;
}
return ignore
? !ignore.some((ignore) => path.startsWith(join(root, ignore, "/")))
: true;
});
if (!paths.length) {
continue;
}
paths.forEach((path) => changes.add(join("/", relative(root, path))));
// Debounce
clearTimeout(timer);
timer = setTimeout(callback, debounce ?? 100);
}
}
}