Add 'packages/hot/' from commit 'ed1422b4fbfdc0bfdd44dd54900f03fb9838ad9b'
git-subtree-dir: packages/hot git-subtree-mainline:0aab437ca1
git-subtree-split:ed1422b4fb
This commit is contained in:
commit
6890bbf53f
16 changed files with 723 additions and 0 deletions
56
packages/hot/.gitignore
vendored
Normal file
56
packages/hot/.gitignore
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
.packages
|
||||
.pub/
|
||||
build/
|
||||
# If you're building an application, you may want to check-in your pubspec.lock
|
||||
pubspec.lock
|
||||
|
||||
# Directory created by dartdoc
|
||||
# If you don't generate documentation locally you can remove this line.
|
||||
doc/api/
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff:
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
/out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
17
packages/hot/.idea/hot.iml
Normal file
17
packages/hot/.idea/hot.iml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||
</component>
|
||||
</module>
|
8
packages/hot/.idea/modules.xml
Normal file
8
packages/hot/.idea/modules.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/hot.iml" filepath="$PROJECT_DIR$/.idea/hot.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
8
packages/hot/.idea/runConfigurations/main_dart.xml
Normal file
8
packages/hot/.idea/runConfigurations/main_dart.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="main.dart" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true" nameIsGenerated="true">
|
||||
<option name="VMOptions" value="--observe" />
|
||||
<option name="filePath" value="$PROJECT_DIR$/example/basic/main.dart" />
|
||||
<option name="workingDirectory" value="$PROJECT_DIR$/example/basic" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -0,0 +1,7 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="main.dart (No VM service)" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/example/basic/main.dart" />
|
||||
<option name="workingDirectory" value="$PROJECT_DIR$/example/basic" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
6
packages/hot/.idea/vcs.xml
Normal file
6
packages/hot/.idea/vcs.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
32
packages/hot/CHANGELOG.md
Normal file
32
packages/hot/CHANGELOG.md
Normal file
|
@ -0,0 +1,32 @@
|
|||
# 2.0.6
|
||||
* Support `--observe=*`, `--enable-vm-service=*` (`startsWith`, instead of `==`).
|
||||
|
||||
# 2.0.5
|
||||
* Use `dart:developer` to find the Observatory URI.
|
||||
* Use the app's logger when necessary.
|
||||
* Apply `package:pedantic`.
|
||||
|
||||
# 2.0.4
|
||||
* Forcibly close app loggers on shutdown.
|
||||
|
||||
# 2.0.3
|
||||
* Fixed up manual restart.
|
||||
* Remove stutter on hotkey press.
|
||||
|
||||
# 2.0.2
|
||||
* Fixed for compatibility with `package:angel_websocket@^2.0.0-alpha.5`.
|
||||
|
||||
# 2.0.1
|
||||
* Add import of `package:angel_framework/http.dart`
|
||||
* https://github.com/angel-dart/hot/pull/7
|
||||
|
||||
# 2.0.0
|
||||
* Update for Dart 2 + Angel 2.
|
||||
|
||||
# 1.1.1+1
|
||||
* Fix a bug that threw when `--observe` was not present.
|
||||
|
||||
# 1.1.1
|
||||
* Disable the observatory from pausing the isolate
|
||||
on exceptions, because Angel already handles
|
||||
all exceptions by itself.
|
21
packages/hot/LICENSE
Normal file
21
packages/hot/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 The Angel Framework
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
84
packages/hot/README.md
Normal file
84
packages/hot/README.md
Normal file
|
@ -0,0 +1,84 @@
|
|||
# hot
|
||||
[![Pub](https://img.shields.io/pub/v/angel_hot.svg)](https://pub.dartlang.org/packages/angel_hot)
|
||||
|
||||
![Screenshot of terminal](screenshots/screenshot.png)
|
||||
|
||||
Supports *hot reloading* of Angel servers on file changes. This is faster and
|
||||
more reliable than merely reactively restarting a `Process`.
|
||||
|
||||
This package only works with the [Angel framework](https://github.com/angel-dart/angel).
|
||||
|
||||
# Installation
|
||||
In your `pubspec.yaml`:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
angel_framework: ^2.0.0-alpha
|
||||
angel_hot: ^2.0.0
|
||||
```
|
||||
|
||||
# Usage
|
||||
This package is dependent on the Dart VM service, so you *must* run
|
||||
Dart with the `--observe` (or `--enable-vm-service`) argument!!!
|
||||
|
||||
Usage is fairly simple. Pass a function that creates an `Angel` server, along with a collection of paths
|
||||
to watch, to the `HotReloader` constructor. The rest is history!!!
|
||||
|
||||
The recommended pattern is to only use hot-reloading in your application entry point. Create your `Angel` instance
|
||||
within a separate function, conventionally named `createServer`.
|
||||
|
||||
**Using this in production mode is not recommended, unless you are
|
||||
specifically intending for a "hot code push" in production..**
|
||||
|
||||
You can watch:
|
||||
* Files
|
||||
* Directories
|
||||
* Globs
|
||||
* URI's
|
||||
* `package:` URI's
|
||||
|
||||
```dart
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_hot/angel_hot.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'src/foo.dart';
|
||||
|
||||
main() async {
|
||||
var hot = new HotReloader(createServer, [
|
||||
new Directory('src'),
|
||||
new Directory('src'),
|
||||
'main.dart',
|
||||
Uri.parse('package:angel_hot/angel_hot.dart')
|
||||
]);
|
||||
await hot.startServer('127.0.0.1', 3000);
|
||||
}
|
||||
|
||||
Future<Angel> createServer() async {
|
||||
var app = new Angel()..serializer = json.encode;
|
||||
|
||||
// Edit this line, and then refresh the page in your browser!
|
||||
app.get('/', (req, res) => {'hello': 'hot world!'});
|
||||
app.get('/foo', (req, res) => new Foo(bar: 'baz'));
|
||||
|
||||
app.fallback((req, res) => throw new AngelHttpException.notFound());
|
||||
|
||||
app.encoders.addAll({
|
||||
'gzip': gzip.encoder,
|
||||
'deflate': zlib.encoder,
|
||||
});
|
||||
|
||||
app.logger = new Logger('angel')
|
||||
..onRecord.listen((rec) {
|
||||
print(rec);
|
||||
if (rec.error != null) {
|
||||
print(rec.error);
|
||||
print(rec.stackTrace);
|
||||
}
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
```
|
8
packages/hot/analysis_options.yaml
Normal file
8
packages/hot/analysis_options.yaml
Normal file
|
@ -0,0 +1,8 @@
|
|||
include: package:pedantic/analysis_options.yaml
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
||||
linter:
|
||||
rules:
|
||||
- unnecessary_const
|
||||
- unnecessary_new
|
13
packages/hot/example/main.dart
Normal file
13
packages/hot/example/main.dart
Normal file
|
@ -0,0 +1,13 @@
|
|||
import 'dart:io';
|
||||
import 'package:angel_hot/angel_hot.dart';
|
||||
import 'server.dart';
|
||||
|
||||
main() async {
|
||||
var hot = HotReloader(createServer, [
|
||||
Directory('src'),
|
||||
'server.dart',
|
||||
// Also allowed: Platform.script,
|
||||
Uri.parse('package:angel_hot/angel_hot.dart')
|
||||
]);
|
||||
await hot.startServer('127.0.0.1', 3000);
|
||||
}
|
33
packages/hot/example/server.dart
Normal file
33
packages/hot/example/server.dart
Normal file
|
@ -0,0 +1,33 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'src/foo.dart';
|
||||
|
||||
Future<Angel> createServer() async {
|
||||
var app = Angel()..serializer = json.encode;
|
||||
hierarchicalLoggingEnabled = true;
|
||||
|
||||
// Edit this line, and then refresh the page in your browser!
|
||||
app.get('/', (req, res) => {'hello': 'hot world!'});
|
||||
app.get('/foo', (req, res) => Foo(bar: 'baz'));
|
||||
|
||||
app.fallback((req, res) => throw AngelHttpException.notFound());
|
||||
|
||||
app.encoders.addAll({
|
||||
'gzip': gzip.encoder,
|
||||
'deflate': zlib.encoder,
|
||||
});
|
||||
|
||||
app.logger = Logger.detached('angel')
|
||||
..onRecord.listen((rec) {
|
||||
print(rec);
|
||||
if (rec.error != null) {
|
||||
print(rec.error);
|
||||
print(rec.stackTrace);
|
||||
}
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
9
packages/hot/example/src/foo.dart
Normal file
9
packages/hot/example/src/foo.dart
Normal file
|
@ -0,0 +1,9 @@
|
|||
class Foo {
|
||||
final String bar;
|
||||
|
||||
Foo({this.bar});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {'bar': bar};
|
||||
}
|
||||
}
|
400
packages/hot/lib/angel_hot.dart
Normal file
400
packages/hot/lib/angel_hot.dart
Normal file
|
@ -0,0 +1,400 @@
|
|||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer' as dev;
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_framework/http.dart';
|
||||
import 'package:angel_websocket/server.dart';
|
||||
import 'package:charcode/ascii.dart';
|
||||
import 'package:glob/glob.dart';
|
||||
import 'package:html_builder/elements.dart';
|
||||
import 'package:html_builder/html_builder.dart';
|
||||
import 'package:io/ansi.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:vm_service_lib/vm_service_lib.dart' as vm;
|
||||
import 'package:vm_service_lib/vm_service_lib_io.dart' as vm;
|
||||
import 'package:watcher/watcher.dart';
|
||||
|
||||
/// A utility class that watches the filesystem for changes, and starts new instances of an Angel server.
|
||||
class HotReloader {
|
||||
vm.VmService _client;
|
||||
vm.IsolateRef _mainIsolate;
|
||||
final StreamController<WatchEvent> _onChange =
|
||||
StreamController<WatchEvent>.broadcast();
|
||||
final List _paths = [];
|
||||
final StringRenderer _renderer = StringRenderer(pretty: false);
|
||||
final Queue<HttpRequest> _requestQueue = Queue<HttpRequest>();
|
||||
HttpServer _io;
|
||||
AngelHttp _server;
|
||||
Duration _timeout;
|
||||
vm.VM _vmachine;
|
||||
|
||||
/// If `true` (default), then developers can `press 'r' to reload` the application on-the-fly.
|
||||
///
|
||||
/// This option triggers printing a Flutter-like output to the terminal.
|
||||
final bool enableHotkeys;
|
||||
|
||||
/// Invoked to load a new instance of [Angel] on file changes.
|
||||
final FutureOr<Angel> Function() generator;
|
||||
|
||||
/// Fires whenever a file change. You might consider using this to trigger
|
||||
/// page reloads in a client.
|
||||
Stream<WatchEvent> 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.
|
||||
/// Default: `5s`
|
||||
Duration get timeout => _timeout;
|
||||
|
||||
/// The Dart VM service host.
|
||||
///
|
||||
/// Default: `localhost`.
|
||||
final String vmServiceHost;
|
||||
|
||||
/// The port to connect to the Dart VM service.
|
||||
///
|
||||
/// Default: `8181`.
|
||||
final int vmServicePort;
|
||||
|
||||
/// Initializes a hot reloader that proxies the server created by [generator].
|
||||
///
|
||||
/// [paths] can contain [FileSystemEntity], [Uri], [String] and [Glob] only.
|
||||
/// URI's can be `package:` URI's as well.
|
||||
HotReloader(this.generator, Iterable paths,
|
||||
{Duration timeout,
|
||||
this.vmServiceHost = 'localhost',
|
||||
this.vmServicePort = 8181,
|
||||
this.enableHotkeys = true}) {
|
||||
_timeout = timeout ?? Duration(seconds: 5);
|
||||
_paths.addAll(paths ?? []);
|
||||
}
|
||||
|
||||
Future close() async {
|
||||
await _io?.close(force: true);
|
||||
await _onChange.close();
|
||||
}
|
||||
|
||||
void sendError(HttpRequest request, int status, String title_, e) {
|
||||
var doc = html(lang: 'en', c: [
|
||||
head(c: [
|
||||
meta(name: 'viewport', content: 'width=device-width, initial-scale=1'),
|
||||
title(c: [text(title_)])
|
||||
]),
|
||||
body(c: [
|
||||
h1(c: [text(title_)]),
|
||||
i(c: [text(e.toString())])
|
||||
])
|
||||
]);
|
||||
|
||||
var response = request.response;
|
||||
response.statusCode = HttpStatus.badGateway;
|
||||
response.headers
|
||||
..contentType = ContentType.html
|
||||
..set(HttpHeaders.serverHeader, 'angel_hot');
|
||||
|
||||
if (request.headers
|
||||
.value(HttpHeaders.acceptEncodingHeader)
|
||||
?.toLowerCase()
|
||||
?.contains('gzip') ==
|
||||
true) {
|
||||
response
|
||||
..headers.set(HttpHeaders.contentEncodingHeader, 'gzip')
|
||||
..add(gzip.encode(utf8.encode(_renderer.render(doc))));
|
||||
} else
|
||||
response.write(_renderer.render(doc));
|
||||
response.close();
|
||||
}
|
||||
|
||||
Future _handle(HttpRequest request) {
|
||||
return _server.handleRequest(request);
|
||||
}
|
||||
|
||||
Future handleRequest(HttpRequest request) async {
|
||||
if (_server != null)
|
||||
return await _handle(request);
|
||||
else if (timeout == null)
|
||||
_requestQueue.add(request);
|
||||
else {
|
||||
_requestQueue.add(request);
|
||||
Timer(timeout, () {
|
||||
if (_requestQueue.remove(request)) {
|
||||
// Send 502 response
|
||||
sendError(request, HttpStatus.badGateway, '502 Bad Gateway',
|
||||
'Request timed out after ${timeout.inMilliseconds}ms.');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<AngelHttp> _generateServer() async {
|
||||
var s = await generator();
|
||||
await Future.forEach(s.startupHooks, s.configure);
|
||||
s.optimizeForProduction();
|
||||
return AngelHttp.custom(s, startShared);
|
||||
}
|
||||
|
||||
void _logWarning(String msg) {
|
||||
if (_server?.app?.logger != null) {
|
||||
_server.app.logger.warning(msg);
|
||||
} else {
|
||||
print(yellow.wrap('WARNING: $msg'));
|
||||
}
|
||||
}
|
||||
|
||||
void _logInfo(String msg) {
|
||||
if (_server?.app?.logger != null) {
|
||||
_server.app.logger.info(msg);
|
||||
} else {
|
||||
print(lightGray.wrap(msg));
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts listening to requests and filesystem events.
|
||||
Future<HttpServer> startServer([address, int port]) async {
|
||||
var isHot = true;
|
||||
_server = await _generateServer();
|
||||
|
||||
if (_paths?.isNotEmpty != true)
|
||||
_logWarning(
|
||||
'You have instantiated a HotReloader without providing any filesystem paths to watch.');
|
||||
|
||||
bool _sw(String s) {
|
||||
return Platform.executableArguments.any((ss) => ss.startsWith(s));
|
||||
}
|
||||
|
||||
if (!_sw('--observe') && !_sw('--enable-vm-service')) {
|
||||
_logWarning(
|
||||
'You have instantiated a HotReloader without passing `--enable-vm-service` or `--observe` to the Dart VM. Hot reloading will be disabled.');
|
||||
isHot = false;
|
||||
} else {
|
||||
var info = await dev.Service.getInfo();
|
||||
var uri = info.serverUri;
|
||||
uri = uri.replace(path: p.join(uri.path, 'ws'));
|
||||
if (uri.scheme == 'https')
|
||||
uri = uri.replace(scheme: 'wss');
|
||||
else
|
||||
uri = uri.replace(scheme: 'ws');
|
||||
_client = await vm.vmServiceConnectUri(uri.toString());
|
||||
_vmachine ??= await _client.getVM();
|
||||
_mainIsolate ??= _vmachine.isolates.first;
|
||||
|
||||
for (var isolate in _vmachine.isolates) {
|
||||
await _client.setExceptionPauseMode(isolate.id, 'None');
|
||||
}
|
||||
|
||||
await _listenToFilesystem();
|
||||
}
|
||||
|
||||
_onChange.stream
|
||||
//.transform(new _Debounce(new Duration(seconds: 1)))
|
||||
.listen(_handleWatchEvent);
|
||||
|
||||
while (_requestQueue.isNotEmpty) await _handle(_requestQueue.removeFirst());
|
||||
var server = _io = await HttpServer.bind(address ?? '127.0.0.1', port ?? 0);
|
||||
server.listen(handleRequest);
|
||||
|
||||
// Print a Flutter-like prompt...
|
||||
if (enableHotkeys) {
|
||||
var serverUri =
|
||||
Uri(scheme: 'http', host: server.address.address, port: server.port);
|
||||
var observatoryUri = await dev.Service.getInfo().then((i) => i.serverUri);
|
||||
|
||||
print(styleBold.wrap(
|
||||
'\n🔥 To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R".'));
|
||||
stdout.write('Your Angel server is listening at: ');
|
||||
print(wrapWith('$serverUri', [cyan, styleUnderlined]));
|
||||
stdout.write(
|
||||
'An Observatory debugger and profiler on ${Platform.operatingSystem} is available at: ');
|
||||
print(wrapWith('$observatoryUri', [cyan, styleUnderlined]));
|
||||
print(
|
||||
'For a more detailed help message, press "h". To quit, press "q".\n');
|
||||
|
||||
if (_paths.isNotEmpty) {
|
||||
print(darkGray.wrap(
|
||||
'Changes to the following path(s) will also trigger a hot reload:'));
|
||||
|
||||
for (var p in _paths) {
|
||||
print(darkGray.wrap(' * $p'));
|
||||
}
|
||||
|
||||
stdout.writeln();
|
||||
}
|
||||
|
||||
// Listen for hotkeys
|
||||
try {
|
||||
stdin.lineMode = stdin.echoMode = false;
|
||||
} catch (_) {}
|
||||
|
||||
StreamSubscription<int> sub;
|
||||
|
||||
try {
|
||||
sub = stdin.expand((l) => l).listen((ch) async {
|
||||
if (ch == $r) {
|
||||
_handleWatchEvent(
|
||||
WatchEvent(ChangeType.MODIFY, '[manual-reload]'), isHot);
|
||||
}
|
||||
if (ch == $R) {
|
||||
_logInfo('Manually restarting server...\n');
|
||||
await _killServer();
|
||||
await _server.close();
|
||||
var addr = _io.address.address;
|
||||
var port = _io.port;
|
||||
await _io?.close(force: true);
|
||||
await startServer(addr, port);
|
||||
} else if (ch == $q) {
|
||||
stdin.echoMode = stdin.lineMode = true;
|
||||
await close();
|
||||
await sub.cancel();
|
||||
exit(0);
|
||||
} else if (ch == $h) {
|
||||
print(
|
||||
'Press "r" to hot reload the Dart VM, and restart the active server.');
|
||||
print(
|
||||
'Press "R" to restart the server, WITHOUT a hot reload of the VM.');
|
||||
print('Press "q" to quit the server.');
|
||||
print('Press "h" to display this help information.');
|
||||
stdout.writeln();
|
||||
}
|
||||
});
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
_listenToFilesystem() async {
|
||||
for (var path in _paths) {
|
||||
if (path is String) {
|
||||
await _listenToStat(path);
|
||||
} else if (path is Glob) {
|
||||
await for (var entity in path.list()) {
|
||||
await _listenToStat(entity.path);
|
||||
}
|
||||
} else if (path is FileSystemEntity) {
|
||||
await _listenToStat(path.path);
|
||||
} else if (path is Uri) {
|
||||
if (path.scheme == 'package') {
|
||||
var uri = await Isolate.resolvePackageUri(path);
|
||||
if (uri != null)
|
||||
await _listenToStat(uri.toFilePath());
|
||||
else
|
||||
await _listenToStat(path.toFilePath());
|
||||
} else
|
||||
await _listenToStat(path.toFilePath());
|
||||
} else {
|
||||
throw ArgumentError(
|
||||
'Hot reload paths must be a FileSystemEntity, a Uri, a String or a Glob. You provided: $path');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_listenToStat(String path) async {
|
||||
_listen() async {
|
||||
try {
|
||||
var stat = await FileStat.stat(path);
|
||||
if (stat.type == FileSystemEntityType.link) {
|
||||
var lnk = Link(path);
|
||||
var p = await lnk.resolveSymbolicLinks();
|
||||
return await _listenToStat(p);
|
||||
} else if (stat.type == FileSystemEntityType.file) {
|
||||
var file = File(path);
|
||||
if (!await file.exists()) return null;
|
||||
} else if (stat.type == FileSystemEntityType.directory) {
|
||||
var dir = Directory(path);
|
||||
if (!await dir.exists()) return null;
|
||||
} else
|
||||
return null;
|
||||
|
||||
var watcher = Watcher(path);
|
||||
|
||||
watcher.events.listen(_onChange.add, onError: (e) {
|
||||
_logWarning('Could not listen to file changes at ${path}: $e');
|
||||
});
|
||||
|
||||
// print('Listening for file changes at ${path}...');
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (e is! FileSystemException) rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
var r = await _listen();
|
||||
|
||||
if (r == null) {
|
||||
_logWarning(
|
||||
'Unable to watch path "$path" from working directory "${Directory.current.path}". Please ensure that it exists.');
|
||||
}
|
||||
}
|
||||
|
||||
Future _killServer() async {
|
||||
if (_server != null) {
|
||||
// Do this asynchronously, because we really don't care about the old server anymore.
|
||||
scheduleMicrotask(() async {
|
||||
// Disconnect active WebSockets
|
||||
try {
|
||||
var ws = _server.app.container.make<AngelWebSocket>();
|
||||
|
||||
for (var client in ws.clients) {
|
||||
try {
|
||||
await client.close();
|
||||
} catch (e) {
|
||||
_logWarning(
|
||||
'Couldn\'t close WebSocket from session #${client.request.session.id}: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// await Future.forEach(
|
||||
// _server.app.shutdownHooks, _server.app.configure);
|
||||
await _server.app.close();
|
||||
_server.app.logger?.clearListeners();
|
||||
} catch (_) {
|
||||
// Fail silently...
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_handleWatchEvent(WatchEvent e, [bool hot = true]) async {
|
||||
_logInfo('${e.path} changed. Reloading server...\n');
|
||||
await _killServer();
|
||||
_server = null;
|
||||
|
||||
if (hot) {
|
||||
var report = await _client.reloadSources(_mainIsolate.id);
|
||||
|
||||
if (!report.success) {
|
||||
_logWarning(
|
||||
'Hot reload failed - perhaps some sources have not been generated yet.');
|
||||
}
|
||||
}
|
||||
|
||||
var s = await _generateServer();
|
||||
_server = s;
|
||||
while (_requestQueue.isNotEmpty) await _handle(_requestQueue.removeFirst());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
class _Debounce<S> extends StreamTransformerBase<S, S> {
|
||||
final Duration _delay;
|
||||
|
||||
const _Debounce(this._delay);
|
||||
|
||||
Stream<S> bind(Stream<S> stream) {
|
||||
var initial = DateTime.now();
|
||||
var next = initial.subtract(this._delay);
|
||||
return stream.where((S data) {
|
||||
var now = DateTime.now();
|
||||
if (now.isAfter(next)) {
|
||||
next = now.add(this._delay);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
*/
|
21
packages/hot/pubspec.yaml
Normal file
21
packages/hot/pubspec.yaml
Normal file
|
@ -0,0 +1,21 @@
|
|||
name: angel_hot
|
||||
description: Supports hot reloading/hot code push of Angel servers on file changes.
|
||||
version: 2.0.6
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
homepage: https://github.com/angel-dart/hot
|
||||
environment:
|
||||
sdk: ">=2.0.0 <3.0.0"
|
||||
dependencies:
|
||||
angel_framework: ^2.0.0-alpha
|
||||
angel_websocket: ^2.0.0-alpha
|
||||
charcode: ^1.0.0
|
||||
glob: ^1.0.0
|
||||
html_builder: ^1.0.0
|
||||
io: ^0.3.2
|
||||
path: ^1.0.0
|
||||
vm_service_lib: ^0.3.5
|
||||
watcher: ^0.9.0
|
||||
dev_dependencies:
|
||||
http: ^0.11.3
|
||||
logging: ^0.11.0
|
||||
pedantic: ^1.0.0
|
BIN
packages/hot/screenshots/screenshot.png
Normal file
BIN
packages/hot/screenshots/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 80 KiB |
Loading…
Reference in a new issue