Skip to content

Commit 7290399

Browse files
ntkmenex3
andauthored
Partially replace chokidar with @parcel/watcher (#2379)
Co-authored-by: Natalie Weizenbaum <[email protected]>
1 parent 85b467b commit 7290399

File tree

6 files changed

+107
-26
lines changed

6 files changed

+107
-26
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919
* **Potentially breaking bug fix:** `math.unit()` now wraps multiple denominator
2020
units in parentheses. For example, `px/(em*em)` instead of `px/em*em`.
2121

22+
### Command-Line Interface
23+
24+
* Use `@parcel/watcher` to watch the filesystem when running from JavaScript and
25+
not using `--poll`. This should mitigate more frequent failures users have
26+
been seeing since version 4.0.0 of Chokidar, our previous watching tool, was
27+
released.
28+
2229
### JS API
2330

2431
* Fix `SassColor.interpolate()` to allow an undefined `options` parameter, as

lib/src/io/js.dart

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import 'package:watcher/watcher.dart';
1616

1717
import '../exception.dart';
1818
import '../js/chokidar.dart';
19+
import '../js/parcel_watcher.dart';
1920

2021
@JS('process')
2122
external final Process? _nodeJsProcess; // process is null in the browser
@@ -248,39 +249,65 @@ int get exitCode => _process?.exitCode ?? 0;
248249

249250
set exitCode(int code) => _process?.exitCode = code;
250251

251-
Future<Stream<WatchEvent>> watchDir(String path, {bool poll = false}) {
252+
Future<Stream<WatchEvent>> watchDir(String path, {bool poll = false}) async {
252253
if (!isNodeJs) {
253254
throw UnsupportedError("watchDir() is only supported on Node.js");
254255
}
255-
var watcher = chokidar.watch(path, ChokidarOptions(usePolling: poll));
256256

257257
// Don't assign the controller until after the ready event fires. Otherwise,
258258
// Chokidar will give us a bunch of add events for files that already exist.
259259
StreamController<WatchEvent>? controller;
260-
watcher
261-
..on(
262-
'add',
263-
allowInterop((String path, [void _]) =>
264-
controller?.add(WatchEvent(ChangeType.ADD, path))))
265-
..on(
266-
'change',
267-
allowInterop((String path, [void _]) =>
268-
controller?.add(WatchEvent(ChangeType.MODIFY, path))))
269-
..on(
270-
'unlink',
271-
allowInterop((String path) =>
272-
controller?.add(WatchEvent(ChangeType.REMOVE, path))))
273-
..on('error', allowInterop((Object error) => controller?.addError(error)));
274-
275-
var completer = Completer<Stream<WatchEvent>>();
276-
watcher.on('ready', allowInterop(() {
277-
// dart-lang/sdk#45348
278-
var stream = (controller = StreamController<WatchEvent>(onCancel: () {
279-
watcher.close();
260+
if (poll) {
261+
var watcher = chokidar.watch(path, ChokidarOptions(usePolling: true));
262+
watcher
263+
..on(
264+
'add',
265+
allowInterop((String path, [void _]) =>
266+
controller?.add(WatchEvent(ChangeType.ADD, path))))
267+
..on(
268+
'change',
269+
allowInterop((String path, [void _]) =>
270+
controller?.add(WatchEvent(ChangeType.MODIFY, path))))
271+
..on(
272+
'unlink',
273+
allowInterop((String path) =>
274+
controller?.add(WatchEvent(ChangeType.REMOVE, path))))
275+
..on(
276+
'error', allowInterop((Object error) => controller?.addError(error)));
277+
278+
var completer = Completer<Stream<WatchEvent>>();
279+
watcher.on('ready', allowInterop(() {
280+
// dart-lang/sdk#45348
281+
var stream = (controller = StreamController<WatchEvent>(onCancel: () {
282+
watcher.close();
283+
}))
284+
.stream;
285+
completer.complete(stream);
286+
}));
287+
288+
return completer.future;
289+
} else {
290+
var subscription = await ParcelWatcher.subscribeFuture(path,
291+
(Object? error, List<ParcelWatcherEvent> events) {
292+
if (error != null) {
293+
controller?.addError(error);
294+
} else {
295+
for (var event in events) {
296+
switch (event.type) {
297+
case 'create':
298+
controller?.add(WatchEvent(ChangeType.ADD, event.path));
299+
case 'update':
300+
controller?.add(WatchEvent(ChangeType.MODIFY, event.path));
301+
case 'delete':
302+
controller?.add(WatchEvent(ChangeType.REMOVE, event.path));
303+
}
304+
}
305+
}
306+
});
307+
308+
return (controller = StreamController<WatchEvent>(onCancel: () {
309+
subscription.unsubscribe();
280310
}))
281311
.stream;
282-
completer.complete(stream);
283-
}));
284-
285-
return completer.future;
312+
}
286313
}

lib/src/js/parcel_watcher.dart

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2024 Google Inc. Use of this source code is governed by an
2+
// MIT-style license that can be found in the LICENSE file or at
3+
// https://opensource.org/licenses/MIT.
4+
5+
import 'package:js/js.dart';
6+
import 'package:node_interop/js.dart';
7+
import 'package:node_interop/util.dart';
8+
9+
@JS()
10+
class ParcelWatcherSubscription {
11+
external void unsubscribe();
12+
}
13+
14+
@JS()
15+
class ParcelWatcherEvent {
16+
external String get type;
17+
external String get path;
18+
}
19+
20+
/// The @parcel/watcher module.
21+
///
22+
/// See [the docs on npm](https://www.npmjs.com/package/@parcel/watcher).
23+
@JS('parcel_watcher')
24+
class ParcelWatcher {
25+
external static Promise subscribe(String path, Function callback);
26+
static Future<ParcelWatcherSubscription> subscribeFuture(String path,
27+
void Function(Object? error, List<ParcelWatcherEvent>) callback) =>
28+
promiseToFuture(
29+
subscribe(path, allowInterop((Object? error, List<dynamic> events) {
30+
callback(error, events.cast<ParcelWatcherEvent>());
31+
})));
32+
33+
external static Promise getEventsSince(String path, String snapshotPath);
34+
static Future<List<ParcelWatcherEvent>> getEventsSinceFuture(
35+
String path, String snapshotPath) async {
36+
List<dynamic> events =
37+
await promiseToFuture(getEventsSince(path, snapshotPath));
38+
return events.cast<ParcelWatcherEvent>();
39+
}
40+
41+
external static Promise writeSnapshot(String path, String snapshotPath);
42+
static Future<void> writeSnapshotFuture(String path, String snapshotPath) =>
43+
promiseToFuture(writeSnapshot(path, snapshotPath));
44+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
],
77
"name": "sass",
88
"devDependencies": {
9+
"@parcel/watcher": "^2.4.1",
910
"chokidar": "^4.0.0",
1011
"immutable": "^4.0.0",
1112
"intercept-stdout": "^0.1.2"

package/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"node": ">=14.0.0"
1818
},
1919
"dependencies": {
20+
"@parcel/watcher": "^2.4.1",
2021
"chokidar": "^4.0.0",
2122
"immutable": "^4.0.0",
2223
"source-map-js": ">=0.6.2 <2.0.0"

tool/grind.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ void main(List<String> args) {
3434
pkg.homebrewFormula.value = "Formula/sass.rb";
3535
pkg.homebrewEditFormula.value = _updateHomebrewLanguageRevision;
3636
pkg.jsRequires.value = [
37+
pkg.JSRequire("@parcel/watcher", target: pkg.JSRequireTarget.cli),
3738
pkg.JSRequire("immutable", target: pkg.JSRequireTarget.all),
3839
pkg.JSRequire("chokidar", target: pkg.JSRequireTarget.cli),
3940
pkg.JSRequire("readline", target: pkg.JSRequireTarget.cli),

0 commit comments

Comments
 (0)