diff --git a/README.md b/README.md index 5e90fc71..b6b0c587 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # hot -[![version 1.0.0-rc.2](https://img.shields.io/badge/pub-1.0.0--rc.2-brightgreen.svg)](https://pub.dartlang.org/packages/angel_rethink) +[![Pub](https://img.shields.io/pub/v/angel_hot.svg)](https://pub.dartlang.org/packages/angel_hot) Supports *hot reloading* of Angel servers on file changes. This is faster and more reliable than merely reactively restarting a `Process`. diff --git a/example/basic/server.dart b/example/basic/server.dart index 3c550213..2bf80b3c 100644 --- a/example/basic/server.dart +++ b/example/basic/server.dart @@ -9,6 +9,7 @@ import 'src/foo.dart'; main() async { var hot = new HotReloader(createServer, [ + new Directory('src'), new Directory('src'), 'server.dart', Uri.parse('package:angel_hot/angel_hot.dart') diff --git a/lib/angel_hot.dart b/lib/angel_hot.dart index e14192a5..eae4fa62 100644 --- a/lib/angel_hot.dart +++ b/lib/angel_hot.dart @@ -16,6 +16,8 @@ typedef FutureOr AngelGenerator(); class HotReloader { VMServiceClient _client; + final StreamController _onChange = + new StreamController.broadcast(); final List _paths = []; final StringRenderer _renderer = new StringRenderer(pretty: false); final Queue _requestQueue = new Queue(); @@ -25,6 +27,10 @@ class HotReloader { /// Invoked to load a new instance of [Angel] on file changes. final AngelGenerator generator; + /// Fires whenever a file change. You might consider using this to trigger + /// page reloads in a client. + Stream get onChange => _onChange.stream; + /// The maximum amount of time to queue incoming requests for if there is no [server] available. /// /// If the timeout expires, then the request will be immediately terminated with a `502 Bad Gateway` error. @@ -46,6 +52,10 @@ class HotReloader { _paths.addAll(paths ?? []); } + Future close() async { + _onChange.close(); + } + Future handleRequest(HttpRequest request) async { if (_server != null) return await _server.handleRequest(request); @@ -109,6 +119,10 @@ class HotReloader { var s = _server = await _generateServer(); while (!_requestQueue.isEmpty) await s.handleRequest(_requestQueue.removeFirst()); + + _onChange.stream + .transform(new _Debounce(new Duration(seconds: 1))) + .listen(_handleWatchEvent); await _listenToFilesystem(); var server = await HttpServer.bind( @@ -130,7 +144,10 @@ class HotReloader { } else if (path is Uri) { if (path.scheme == 'package') { var uri = await Isolate.resolvePackageUri(path); - await _listenToStat(uri.toFilePath()); + if (uri != null) + await _listenToStat(uri.toFilePath()); + else + await _listenToStat(path.toFilePath()); } else await _listenToStat(path.toFilePath()); } else { @@ -158,8 +175,11 @@ class HotReloader { return null; var watcher = new Watcher(path); - //await watcher.ready; - watcher.events.listen(_handleWatchEvent); + + watcher.events.listen(_onChange.add, onError: (e) { + stderr.writeln('Could not listen to file changes at ${path}: $e'); + }); + print('Listening for file changes at ${path}...'); return true; } catch (e) { @@ -218,3 +238,23 @@ class HotReloader { await s.handleRequest(_requestQueue.removeFirst()); } } + +class _Debounce implements StreamTransformer { + final Duration _delay; + + const _Debounce(this._delay); + + Stream bind(Stream stream) { + var initial = new DateTime.now(); + var next = initial.subtract(this._delay); + return stream.where((S data) { + var now = new DateTime.now(); + if (now.isAfter(next)) { + next = now.add(this._delay); + return true; + } else { + return false; + } + }); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 258157ba..0429ba91 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: angel_hot description: Supports hot reloading of Angel servers on file changes. -version: 1.0.0-rc.3 +version: 1.0.0 author: Tobe O homepage: https://github.com/angel-dart/hot environment: