Skip to content

Commit

Permalink
Partially replace chokidar with @parcel/watcher (#2379)
Browse files Browse the repository at this point in the history
Co-authored-by: Natalie Weizenbaum <nweiz@google.com>
  • Loading branch information
ntkme and nex3 authored Oct 11, 2024
1 parent 85b467b commit 7290399
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 26 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@
* **Potentially breaking bug fix:** `math.unit()` now wraps multiple denominator
units in parentheses. For example, `px/(em*em)` instead of `px/em*em`.

### Command-Line Interface

* Use `@parcel/watcher` to watch the filesystem when running from JavaScript and
not using `--poll`. This should mitigate more frequent failures users have
been seeing since version 4.0.0 of Chokidar, our previous watching tool, was
released.

### JS API

* Fix `SassColor.interpolate()` to allow an undefined `options` parameter, as
Expand Down
79 changes: 53 additions & 26 deletions lib/src/io/js.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'package:watcher/watcher.dart';

import '../exception.dart';
import '../js/chokidar.dart';
import '../js/parcel_watcher.dart';

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

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

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

// Don't assign the controller until after the ready event fires. Otherwise,
// Chokidar will give us a bunch of add events for files that already exist.
StreamController<WatchEvent>? controller;
watcher
..on(
'add',
allowInterop((String path, [void _]) =>
controller?.add(WatchEvent(ChangeType.ADD, path))))
..on(
'change',
allowInterop((String path, [void _]) =>
controller?.add(WatchEvent(ChangeType.MODIFY, path))))
..on(
'unlink',
allowInterop((String path) =>
controller?.add(WatchEvent(ChangeType.REMOVE, path))))
..on('error', allowInterop((Object error) => controller?.addError(error)));

var completer = Completer<Stream<WatchEvent>>();
watcher.on('ready', allowInterop(() {
// dart-lang/sdk#45348
var stream = (controller = StreamController<WatchEvent>(onCancel: () {
watcher.close();
if (poll) {
var watcher = chokidar.watch(path, ChokidarOptions(usePolling: true));
watcher
..on(
'add',
allowInterop((String path, [void _]) =>
controller?.add(WatchEvent(ChangeType.ADD, path))))
..on(
'change',
allowInterop((String path, [void _]) =>
controller?.add(WatchEvent(ChangeType.MODIFY, path))))
..on(
'unlink',
allowInterop((String path) =>
controller?.add(WatchEvent(ChangeType.REMOVE, path))))
..on(
'error', allowInterop((Object error) => controller?.addError(error)));

var completer = Completer<Stream<WatchEvent>>();
watcher.on('ready', allowInterop(() {
// dart-lang/sdk#45348
var stream = (controller = StreamController<WatchEvent>(onCancel: () {
watcher.close();
}))
.stream;
completer.complete(stream);
}));

return completer.future;
} else {
var subscription = await ParcelWatcher.subscribeFuture(path,
(Object? error, List<ParcelWatcherEvent> events) {
if (error != null) {
controller?.addError(error);
} else {
for (var event in events) {
switch (event.type) {
case 'create':
controller?.add(WatchEvent(ChangeType.ADD, event.path));
case 'update':
controller?.add(WatchEvent(ChangeType.MODIFY, event.path));
case 'delete':
controller?.add(WatchEvent(ChangeType.REMOVE, event.path));
}
}
}
});

return (controller = StreamController<WatchEvent>(onCancel: () {
subscription.unsubscribe();
}))
.stream;
completer.complete(stream);
}));

return completer.future;
}
}
44 changes: 44 additions & 0 deletions lib/src/js/parcel_watcher.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2024 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:js/js.dart';
import 'package:node_interop/js.dart';
import 'package:node_interop/util.dart';

@JS()
class ParcelWatcherSubscription {
external void unsubscribe();
}

@JS()
class ParcelWatcherEvent {
external String get type;
external String get path;
}

/// The @parcel/watcher module.
///
/// See [the docs on npm](https://www.npmjs.com/package/@parcel/watcher).
@JS('parcel_watcher')
class ParcelWatcher {
external static Promise subscribe(String path, Function callback);
static Future<ParcelWatcherSubscription> subscribeFuture(String path,
void Function(Object? error, List<ParcelWatcherEvent>) callback) =>
promiseToFuture(
subscribe(path, allowInterop((Object? error, List<dynamic> events) {
callback(error, events.cast<ParcelWatcherEvent>());
})));

external static Promise getEventsSince(String path, String snapshotPath);
static Future<List<ParcelWatcherEvent>> getEventsSinceFuture(
String path, String snapshotPath) async {
List<dynamic> events =
await promiseToFuture(getEventsSince(path, snapshotPath));
return events.cast<ParcelWatcherEvent>();
}

external static Promise writeSnapshot(String path, String snapshotPath);
static Future<void> writeSnapshotFuture(String path, String snapshotPath) =>
promiseToFuture(writeSnapshot(path, snapshotPath));
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
],
"name": "sass",
"devDependencies": {
"@parcel/watcher": "^2.4.1",
"chokidar": "^4.0.0",
"immutable": "^4.0.0",
"intercept-stdout": "^0.1.2"
Expand Down
1 change: 1 addition & 0 deletions package/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"node": ">=14.0.0"
},
"dependencies": {
"@parcel/watcher": "^2.4.1",
"chokidar": "^4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
Expand Down
1 change: 1 addition & 0 deletions tool/grind.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ void main(List<String> args) {
pkg.homebrewFormula.value = "Formula/sass.rb";
pkg.homebrewEditFormula.value = _updateHomebrewLanguageRevision;
pkg.jsRequires.value = [
pkg.JSRequire("@parcel/watcher", target: pkg.JSRequireTarget.cli),
pkg.JSRequire("immutable", target: pkg.JSRequireTarget.all),
pkg.JSRequire("chokidar", target: pkg.JSRequireTarget.cli),
pkg.JSRequire("readline", target: pkg.JSRequireTarget.cli),
Expand Down

0 comments on commit 7290399

Please sign in to comment.