Skip to content

Commit

Permalink
Lazy load @parcel/watcher and fallback to chokidar
Browse files Browse the repository at this point in the history
  • Loading branch information
ntkme committed Oct 27, 2024
1 parent 7129352 commit ec49bf8
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 81 deletions.
108 changes: 58 additions & 50 deletions lib/src/io/js.dart
Original file line number Diff line number Diff line change
Expand Up @@ -254,60 +254,68 @@ Future<Stream<WatchEvent>> watchDir(String path, {bool poll = false}) async {
throw UnsupportedError("watchDir() is only supported on Node.js");
}

if (poll || parcelWatcher == null) {
return _chokidarWatchDir(path, poll: poll);
} else {
return _parcelWatcherWatchDir(path);
}
}

Future<Stream<WatchEvent>> _chokidarWatchDir(String path, {bool poll = false}) {
// 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;
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));
}
var watcher = chokidar.watch(path, ChokidarOptions(usePolling: poll));
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;
}

Future<Stream<WatchEvent>> _parcelWatcherWatchDir(String path) async {
StreamController<WatchEvent>? controller;
var subscription = await parcelWatcher!.subscribe(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;
}
return (controller = StreamController<WatchEvent>(onCancel: () {
subscription.unsubscribe();
}))
.stream;
}
51 changes: 28 additions & 23 deletions lib/src/js/parcel_watcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,48 @@
// 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';
import 'dart:js_interop';

@JS()
class ParcelWatcherSubscription {
extension type ParcelWatcherSubscription(JSObject _) implements JSObject {
external void unsubscribe();
}

@JS()
class ParcelWatcherEvent {
extension type ParcelWatcherEvent(JSObject _) implements JSObject {
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,
@JS()
extension type ParcelWatcher(JSObject _) implements JSObject {
@JS('subscribe')
external JSPromise<ParcelWatcherSubscription> _subscribe(
String path, JSFunction callback);
Future<ParcelWatcherSubscription> subscribe(String path,
void Function(Object? error, List<ParcelWatcherEvent>) callback) =>
promiseToFuture(
subscribe(path, allowInterop((Object? error, List<dynamic> events) {
callback(error, events.cast<ParcelWatcherEvent>());
})));
_subscribe(
path,
(JSObject? error, JSArray<ParcelWatcherEvent> events) {
callback(error, events.toDart);
}.toJS)
.toDart;

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>();
}
@JS('getEventsSince')
external JSPromise<JSArray<ParcelWatcherEvent>> _getEventsSince(
String path, String snapshotPath);
Future<List<ParcelWatcherEvent>> getEventsSince(
String path, String snapshotPath) async =>
(await _getEventsSince(path, snapshotPath).toDart).toDart;

external static Promise writeSnapshot(String path, String snapshotPath);
static Future<void> writeSnapshotFuture(String path, String snapshotPath) =>
promiseToFuture(writeSnapshot(path, snapshotPath));
@JS('writeSnapshot')
external JSPromise<JSAny> _writeSnapshot(String path, String snapshotPath);
Future<void> writeSnapshot(String path, String snapshotPath) =>
_writeSnapshot(path, snapshotPath).toDart;
}

@JS('parcel_watcher')
external ParcelWatcher? get parcelWatcher;
2 changes: 1 addition & 1 deletion lib/src/parse/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ class Parser {
var span = scanner.spanFrom(state);
return _interpolationMap == null
? span
: LazyFileSpan(() => _interpolationMap!.mapSpan(span));
: LazyFileSpan(() => _interpolationMap.mapSpan(span));
}

/// Throws an error associated with [span].
Expand Down
2 changes: 1 addition & 1 deletion lib/src/visitor/async_evaluate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1812,7 +1812,7 @@ final class _EvaluateVisitor
if (result != null) {
isDependency = _inDependency;
} else {
result = await _nodeImporter!.loadAsync(originalUrl, previous, forImport);
result = await _nodeImporter.loadAsync(originalUrl, previous, forImport);
if (result == null) return null;
isDependency = true;
}
Expand Down
4 changes: 2 additions & 2 deletions lib/src/visitor/evaluate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// DO NOT EDIT. This file was generated from async_evaluate.dart.
// See tool/grind/synchronize.dart for details.
//
// Checksum: 396c8f169d95c601598b8c3be1f4b948ca22effa
// Checksum: 3986f5db33dd220dcd971a39e8587ca4e52d9a3f
//
// ignore_for_file: unused_import

Expand Down Expand Up @@ -1808,7 +1808,7 @@ final class _EvaluateVisitor
if (result != null) {
isDependency = _inDependency;
} else {
result = _nodeImporter!.load(originalUrl, previous, forImport);
result = _nodeImporter.load(originalUrl, previous, forImport);
if (result == null) return null;
isDependency = true;
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/sass_api/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: Additional APIs for Dart Sass.
homepage: https://github.com/sass/dart-sass

environment:
sdk: ">=3.0.0 <4.0.0"
sdk: ">=3.3.0 <4.0.0"

dependencies:
sass: 1.80.5
Expand Down
7 changes: 5 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ executables:
sass: sass

environment:
sdk: ">=3.0.0 <4.0.0"
sdk: ">=3.3.0 <4.0.0"

dependencies:
args: ^2.0.0
async: ^2.5.0
charcode: ^1.2.0
cli_pkg: ^2.8.0
cli_pkg:
git:
url: https://github.com/google/dart_cli_pkg.git
ref: refs/pull/169/head
cli_repl: ^0.2.1
collection: ^1.16.0
http: ^1.1.0
Expand Down
3 changes: 2 additions & 1 deletion tool/grind.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ 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("@parcel/watcher",
target: pkg.JSRequireTarget.cli, lazy: true, optional: true),
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 ec49bf8

Please sign in to comment.