summaryrefslogtreecommitdiff
path: root/cli/util/file_watcher.rs
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2023-10-31 01:25:58 +0100
committerGitHub <noreply@github.com>2023-10-31 01:25:58 +0100
commit1713df13524ad20c6bd0413bcf4aa57adc3f9735 (patch)
treea558239a7f483cab10b83305b333d5ef9657bd3e /cli/util/file_watcher.rs
parent48c5c3a3fb2f43716528db8915b36e55c411d94f (diff)
feat: deno run --unstable-hmr (#20876)
This commit adds `--unstable-hmr` flag, that enabled Hot Module Replacement. This flag works like `--watch` and accepts the same arguments. If HMR is not possible the process will be restarted instead. Currently HMR is only supported in `deno run` subcommand. Upon HMR a `CustomEvent("hmr")` will be dispatched that contains information which file was changed in its `details` property. --------- Co-authored-by: Valentin Anger <syrupthinker@gryphno.de> Co-authored-by: David Sherret <dsherret@gmail.com>
Diffstat (limited to 'cli/util/file_watcher.rs')
-rw-r--r--cli/util/file_watcher.rs141
1 files changed, 97 insertions, 44 deletions
diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs
index 8d6b4e8fb..5a316139c 100644
--- a/cli/util/file_watcher.rs
+++ b/cli/util/file_watcher.rs
@@ -8,6 +8,7 @@ use deno_core::error::AnyError;
use deno_core::error::JsError;
use deno_core::futures::Future;
use deno_core::futures::FutureExt;
+use deno_core::parking_lot::Mutex;
use deno_runtime::fmt_errors::format_js_error;
use log::info;
use notify::event::Event as NotifyEvent;
@@ -16,9 +17,11 @@ use notify::Error as NotifyError;
use notify::RecommendedWatcher;
use notify::RecursiveMode;
use notify::Watcher;
+use std::cell::RefCell;
use std::collections::HashSet;
use std::io::IsTerminal;
use std::path::PathBuf;
+use std::rc::Rc;
use std::sync::Arc;
use std::time::Duration;
use tokio::select;
@@ -91,20 +94,49 @@ where
}
pub struct PrintConfig {
- /// printing watcher status to terminal.
- pub job_name: String,
- /// determine whether to clear the terminal screen; applicable to TTY environments only.
- pub clear_screen: bool,
+ banner: &'static str,
+ /// Printing watcher status to terminal.
+ job_name: &'static str,
+ /// Determine whether to clear the terminal screen; applicable to TTY environments only.
+ clear_screen: bool,
}
-fn create_print_after_restart_fn(clear_screen: bool) -> impl Fn() {
+impl PrintConfig {
+ /// By default `PrintConfig` uses "Watcher" as a banner name that will
+ /// be printed in color. If you need to customize it, use
+ /// `PrintConfig::new_with_banner` instead.
+ pub fn new(job_name: &'static str, clear_screen: bool) -> Self {
+ Self {
+ banner: "Watcher",
+ job_name,
+ clear_screen,
+ }
+ }
+
+ pub fn new_with_banner(
+ banner: &'static str,
+ job_name: &'static str,
+ clear_screen: bool,
+ ) -> Self {
+ Self {
+ banner,
+ job_name,
+ clear_screen,
+ }
+ }
+}
+
+fn create_print_after_restart_fn(
+ banner: &'static str,
+ clear_screen: bool,
+) -> impl Fn() {
move || {
if clear_screen && std::io::stderr().is_terminal() {
eprint!("{CLEAR_SCREEN}");
}
info!(
"{} File change detected! Restarting!",
- colors::intense_blue("Watcher"),
+ colors::intense_blue(banner),
);
}
}
@@ -120,22 +152,38 @@ pub struct WatcherCommunicator {
/// Send a message to force a restart.
restart_tx: tokio::sync::mpsc::UnboundedSender<()>,
-}
-impl Clone for WatcherCommunicator {
- fn clone(&self) -> Self {
- Self {
- paths_to_watch_tx: self.paths_to_watch_tx.clone(),
- changed_paths_rx: self.changed_paths_rx.resubscribe(),
- restart_tx: self.restart_tx.clone(),
- }
- }
+ restart_mode: Mutex<WatcherRestartMode>,
+
+ banner: String,
}
impl WatcherCommunicator {
pub fn watch_paths(&self, paths: Vec<PathBuf>) -> Result<(), AnyError> {
self.paths_to_watch_tx.send(paths).map_err(AnyError::from)
}
+
+ pub fn force_restart(&self) -> Result<(), AnyError> {
+ // Change back to automatic mode, so that HMR can set up watching
+ // from scratch.
+ *self.restart_mode.lock() = WatcherRestartMode::Automatic;
+ self.restart_tx.send(()).map_err(AnyError::from)
+ }
+
+ pub async fn watch_for_changed_paths(
+ &self,
+ ) -> Result<Option<Vec<PathBuf>>, AnyError> {
+ let mut rx = self.changed_paths_rx.resubscribe();
+ rx.recv().await.map_err(AnyError::from)
+ }
+
+ pub fn change_restart_mode(&self, restart_mode: WatcherRestartMode) {
+ *self.restart_mode.lock() = restart_mode;
+ }
+
+ pub fn print(&self, msg: String) {
+ log::info!("{} {}", self.banner, msg);
+ }
}
/// Creates a file watcher.
@@ -151,7 +199,7 @@ pub async fn watch_func<O, F>(
where
O: FnMut(
Flags,
- WatcherCommunicator,
+ Arc<WatcherCommunicator>,
Option<Vec<PathBuf>>,
) -> Result<F, AnyError>,
F: Future<Output = Result<(), AnyError>>,
@@ -173,9 +221,7 @@ pub enum WatcherRestartMode {
Automatic,
/// When a file path changes the caller will trigger a restart, using
- /// `WatcherCommunicator.restart_tx`.
- // TODO(bartlomieju): this mode will be used in a follow up PR
- #[allow(dead_code)]
+ /// `WatcherInterface.restart_tx`.
Manual,
}
@@ -193,7 +239,7 @@ pub async fn watch_recv<O, F>(
where
O: FnMut(
Flags,
- WatcherCommunicator,
+ Arc<WatcherCommunicator>,
Option<Vec<PathBuf>>,
) -> Result<F, AnyError>,
F: Future<Output = Result<(), AnyError>>,
@@ -206,19 +252,42 @@ where
DebouncedReceiver::new_with_sender();
let PrintConfig {
+ banner,
job_name,
clear_screen,
} = print_config;
- let print_after_restart = create_print_after_restart_fn(clear_screen);
- let watcher_communicator = WatcherCommunicator {
+ let print_after_restart = create_print_after_restart_fn(banner, clear_screen);
+ let watcher_communicator = Arc::new(WatcherCommunicator {
paths_to_watch_tx: paths_to_watch_tx.clone(),
changed_paths_rx: changed_paths_rx.resubscribe(),
restart_tx: restart_tx.clone(),
- };
- info!("{} {} started.", colors::intense_blue("Watcher"), job_name,);
+ restart_mode: Mutex::new(restart_mode),
+ banner: colors::intense_blue(banner).to_string(),
+ });
+ info!("{} {} started.", colors::intense_blue(banner), job_name);
+
+ let changed_paths = Rc::new(RefCell::new(None));
+ let changed_paths_ = changed_paths.clone();
+ let watcher_ = watcher_communicator.clone();
+
+ deno_core::unsync::spawn(async move {
+ loop {
+ let received_changed_paths = watcher_receiver.recv().await;
+ *changed_paths_.borrow_mut() = received_changed_paths.clone();
+
+ match *watcher_.restart_mode.lock() {
+ WatcherRestartMode::Automatic => {
+ let _ = restart_tx.send(());
+ }
+ WatcherRestartMode::Manual => {
+ // TODO(bartlomieju): should we fail on sending changed paths?
+ let _ = changed_paths_tx.send(received_changed_paths);
+ }
+ }
+ }
+ });
- let mut changed_paths = None;
loop {
// We may need to give the runtime a tick to settle, as cancellations may need to propagate
// to tasks. We choose yielding 10 times to the runtime as a decent heuristic. If watch tests
@@ -239,7 +308,7 @@ where
let operation_future = error_handler(operation(
flags.clone(),
watcher_communicator.clone(),
- changed_paths.take(),
+ changed_paths.borrow_mut().take(),
)?);
// don't reload dependencies after the first run
@@ -251,26 +320,12 @@ where
print_after_restart();
continue;
},
- received_changed_paths = watcher_receiver.recv() => {
- changed_paths = received_changed_paths.clone();
-
- match restart_mode {
- WatcherRestartMode::Automatic => {
- print_after_restart();
- continue;
- },
- WatcherRestartMode::Manual => {
- // TODO(bartlomieju): should we fail on sending changed paths?
- let _ = changed_paths_tx.send(received_changed_paths);
- }
- }
- },
success = operation_future => {
consume_paths_to_watch(&mut watcher, &mut paths_to_watch_rx);
// TODO(bartlomieju): print exit code here?
info!(
"{} {} {}. Restarting on file change...",
- colors::intense_blue("Watcher"),
+ colors::intense_blue(banner),
job_name,
if success {
"finished"
@@ -280,7 +335,6 @@ where
);
},
};
-
let receiver_future = async {
loop {
let maybe_paths = paths_to_watch_rx.recv().await;
@@ -293,9 +347,8 @@ where
// watched paths has changed.
select! {
_ = receiver_future => {},
- received_changed_paths = watcher_receiver.recv() => {
+ _ = restart_rx.recv() => {
print_after_restart();
- changed_paths = received_changed_paths;
continue;
},
};