Skip to content

Commit e0c6268

Browse files
authored
Debounce watch events on all platforms (#354)
This doesn't flake as often on Dart as it does on Node, but it does flake.
1 parent c0a3f9d commit e0c6268

File tree

2 files changed

+28
-24
lines changed

2 files changed

+28
-24
lines changed

lib/src/executable/watch.dart

+27-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:async';
66
import 'dart:collection';
77

88
import 'package:stack_trace/stack_trace.dart';
9+
import 'package:stream_transform/stream_transform.dart';
910
import 'package:watcher/watcher.dart';
1011

1112
import '../exception.dart';
@@ -104,7 +105,7 @@ class _Watcher {
104105
/// Returns a future that will only complete if an unexpected error occurs.
105106
Future watch(MultiDirWatcher watcher) async {
106107
loop:
107-
await for (var event in watcher.events) {
108+
await for (var event in _debounceEvents(watcher.events)) {
108109
var extension = p.extension(event.path);
109110
if (extension != '.sass' && extension != '.scss') continue;
110111
var url = p.toUri(p.canonicalize(event.path));
@@ -148,6 +149,31 @@ class _Watcher {
148149
}
149150
}
150151

152+
/// Combine [WatchEvent]s that happen in quick succession.
153+
///
154+
/// Otherwise, if a file is erased and then rewritten, we can end up reading
155+
/// the intermediate erased version.
156+
Stream<WatchEvent> _debounceEvents(Stream<WatchEvent> events) {
157+
return events
158+
.transform(debounceBuffer(new Duration(milliseconds: 25)))
159+
.expand((buffer) {
160+
var typeForPath = new PathMap<ChangeType>();
161+
for (var event in buffer) {
162+
var oldType = typeForPath[event.path];
163+
if (oldType == null) {
164+
typeForPath[event.path] = event.type;
165+
} else if (event.type == ChangeType.REMOVE) {
166+
typeForPath[event.path] = ChangeType.REMOVE;
167+
} else if (oldType != ChangeType.ADD) {
168+
typeForPath[event.path] = ChangeType.MODIFY;
169+
}
170+
}
171+
172+
return typeForPath.keys
173+
.map((path) => new WatchEvent(typeForPath[path], path));
174+
});
175+
}
176+
151177
/// Recompiles [nodes] and everything that transitively imports them, if
152178
/// necessary.
153179
Future _recompileDownstream(Iterable<StylesheetNode> nodes) async {

lib/src/io/node.dart

+1-23
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import 'dart:convert';
88
import 'package:dart2_constant/convert.dart' as convert;
99
import 'package:js/js.dart';
1010
import 'package:source_span/source_span.dart';
11-
import 'package:stream_transform/stream_transform.dart';
1211
import 'package:watcher/watcher.dart';
1312

1413
import '../exception.dart';
@@ -266,28 +265,7 @@ Future<Stream<WatchEvent>> watchDir(String path) {
266265
var completer = new Completer<Stream<WatchEvent>>();
267266
watcher.on('ready', allowInterop(() {
268267
controller = new StreamController<WatchEvent>();
269-
270-
// Buffer events that happen in quick succession because otherwise, if a
271-
// file is erased and then rewritten, we can end up reading the intermediate
272-
// erased version.
273-
completer.complete(controller.stream
274-
.transform(debounceBuffer(new Duration(milliseconds: 25)))
275-
.expand((buffer) {
276-
var typeForPath = new PathMap<ChangeType>();
277-
for (var event in buffer) {
278-
var oldType = typeForPath[event.path];
279-
if (oldType == null) {
280-
typeForPath[event.path] = event.type;
281-
} else if (event.type == ChangeType.REMOVE) {
282-
typeForPath[event.path] = ChangeType.REMOVE;
283-
} else if (oldType != ChangeType.ADD) {
284-
typeForPath[event.path] = ChangeType.MODIFY;
285-
}
286-
}
287-
288-
return typeForPath.keys
289-
.map((path) => new WatchEvent(typeForPath[path], path));
290-
}));
268+
completer.complete(controller.stream);
291269
}));
292270

293271
return completer.future;

0 commit comments

Comments
 (0)