enableHotReload function
- NitroServer server, {
- Iterable<
String> ? watch, - void log(
- String message
Wires server's route NitroServer.reload to every VM hot reload. Returns
the HotReloader; call .stop() to detach. A failing setup is printed
to stderr rather than killing the reload loop.
hotreloader only auto-watches bin/, lib/ and test/, so a server whose
entry lives elsewhere (an example/ or benchmark/ file) would never
reload. watch fixes that: the directories to watch for .dart changes,
defaulting to the running script's own directory. Pass const [] to rely
on hotreloader's watchers alone.
Pass log to trace reload activity — the watched directories, each change
that triggers a reload, the VM reload result and the route rebuild. Left
null it is silent (only a failing setup still goes to stderr). Pass
print, stderr.writeln, or route it into your own logger.
Implementation
Future<HotReloader> enableHotReload(
NitroServer server, {
Iterable<String>? watch,
void Function(String message)? log,
}) async {
final reloader = await HotReloader.create(
onAfterReload: (ctx) {
log?.call('hot reload: VM ${ctx.result.name}; rebuilding routes');
server.reload().then(
(_) => log?.call(
'hot reload: routes rebuilt (${server.isolates} '
'isolate(s))',
),
onError: (Object error, StackTrace stack) {
stderr.writeln('nitro_server: hot reload failed: $error\n$stack');
},
);
},
);
// Tear everything down when the server stops — cancel the watchers and the
// pending debounce BEFORE stopping the reloader, so a save racing shutdown
// never fires a reload against a disposed VM service.
var stopped = false;
final watchers = <StreamSubscription<FileSystemEvent>>[];
Timer? debounce;
Future<void> teardown() async {
if (stopped) return;
stopped = true;
debounce?.cancel();
for (final w in watchers) {
await w.cancel();
}
await reloader.stop();
}
unawaited(server.onClose.then((_) => teardown()));
final dirs = {...?watch, if (watch == null) ?_scriptDir()};
log?.call('hot reload: watching ${dirs.join(', ')}');
for (final dir in dirs) {
if (!Directory(dir).existsSync()) continue;
watchers.add(
Directory(dir).watch(recursive: true).listen((event) {
if (stopped || !event.path.endsWith('.dart')) return;
final path = event.path;
// Trailing debounce: fire after the writes settle, so an editor's
// temp-file-then-rename save is fully on disk before the VM re-reads it.
debounce?.cancel();
debounce = Timer(const Duration(milliseconds: 300), () async {
if (stopped) return;
log?.call('hot reload: $path changed, reloading');
try {
await reloader.reloadCode();
} catch (error, stack) {
// A reload can fail (a compile error in the edit) or race shutdown
// (the VM service connection disposes mid-reload). Log it with the
// stack trace; never let it crash the running server.
stderr.writeln('nitro_server: hot reload skipped: $error\n$stack');
}
});
}),
);
}
return reloader;
}