From 2698f136e98a6c5193e628b8ed695bd3641425f1 Mon Sep 17 00:00:00 2001 From: Valentin Anger Date: Sun, 20 Aug 2023 19:33:32 +0200 Subject: [PATCH 01/79] Prototype module hot-reloading --- cli/args/flags.rs | 28 ++++++ cli/args/mod.rs | 12 +++ cli/factory.rs | 29 ++++-- cli/standalone/mod.rs | 2 + cli/tools/bench/mod.rs | 2 +- cli/tools/bundle.rs | 2 +- cli/tools/run/hot_reload/json_types.rs | 53 ++++++++++ cli/tools/run/hot_reload/mod.rs | 131 +++++++++++++++++++++++++ cli/tools/{run.rs => run/mod.rs} | 112 ++++++++++++++------- cli/tools/test/mod.rs | 2 +- cli/util/file_watcher.rs | 71 ++++++++++++++ cli/worker.rs | 59 ++++++++++- 12 files changed, 454 insertions(+), 49 deletions(-) create mode 100644 cli/tools/run/hot_reload/json_types.rs create mode 100644 cli/tools/run/hot_reload/mod.rs rename cli/tools/{run.rs => run/mod.rs} (68%) diff --git a/cli/args/flags.rs b/cli/args/flags.rs index bd3740bdb54c33..35da699be01ab8 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -203,11 +203,13 @@ impl RunFlags { #[derive(Clone, Default, Debug, Eq, PartialEq)] pub struct WatchFlags { + pub hot_reload: bool, pub no_clear_screen: bool, } #[derive(Clone, Default, Debug, Eq, PartialEq)] pub struct WatchFlagsWithPaths { + pub hot_reload: bool, pub paths: Vec, pub no_clear_screen: bool, } @@ -1806,6 +1808,7 @@ fn run_subcommand() -> Command { .conflicts_with("inspect-wait") .conflicts_with("inspect-brk"), ) + .arg(hot_reload_arg()) .arg(no_clear_screen_arg()) .arg(executable_ext_arg()) .arg( @@ -2674,6 +2677,14 @@ fn seed_arg() -> Arg { .value_parser(value_parser!(u64)) } +fn hot_reload_arg() -> Arg { + Arg::new("hot-reload") + .requires("watch") + .long("hot-reload") + .help("UNSTABLE: Enable hot reload") + .action(ArgAction::SetTrue) +} + fn watch_arg(takes_files: bool) -> Arg { let arg = Arg::new("watch") .long("watch") @@ -3774,6 +3785,7 @@ fn reload_arg_validate(urlstr: &str) -> Result { fn watch_arg_parse(matches: &mut ArgMatches) -> Option { if matches.get_flag("watch") { Some(WatchFlags { + hot_reload: matches.get_flag("hot-reload"), no_clear_screen: matches.get_flag("no-clear-screen"), }) } else { @@ -3788,6 +3800,7 @@ fn watch_arg_parse_with_paths( .remove_many::("watch") .map(|f| WatchFlagsWithPaths { paths: f.collect(), + hot_reload: matches.get_flag("hot-reload"), no_clear_screen: matches.get_flag("no-clear-screen"), }) } @@ -3905,6 +3918,7 @@ mod tests { subcommand: DenoSubcommand::Run(RunFlags { script: "script.ts".to_string(), watch: Some(WatchFlagsWithPaths { + hot_reload: false, paths: vec![], no_clear_screen: false, }), @@ -3925,6 +3939,7 @@ mod tests { subcommand: DenoSubcommand::Run(RunFlags { script: "script.ts".to_string(), watch: Some(WatchFlagsWithPaths { + hot_reload: false, paths: vec![PathBuf::from("file1"), PathBuf::from("file2")], no_clear_screen: false, }), @@ -3951,6 +3966,7 @@ mod tests { subcommand: DenoSubcommand::Run(RunFlags { script: "script.ts".to_string(), watch: Some(WatchFlagsWithPaths { + hot_reload: false, paths: vec![], no_clear_screen: true, }) @@ -4273,6 +4289,7 @@ mod tests { prose_wrap: None, no_semicolons: None, watch: Some(WatchFlags { + hot_reload: false, no_clear_screen: false, }) }), @@ -4299,6 +4316,7 @@ mod tests { prose_wrap: None, no_semicolons: None, watch: Some(WatchFlags { + hot_reload: false, no_clear_screen: true, }) }), @@ -4331,6 +4349,7 @@ mod tests { prose_wrap: None, no_semicolons: None, watch: Some(WatchFlags { + hot_reload: false, no_clear_screen: false, }) }), @@ -4387,6 +4406,7 @@ mod tests { prose_wrap: None, no_semicolons: None, watch: Some(WatchFlags { + hot_reload: false, no_clear_screen: false, }) }), @@ -4513,6 +4533,7 @@ mod tests { json: false, compact: false, watch: Some(WatchFlags { + hot_reload: false, no_clear_screen: false, }) }), @@ -4546,6 +4567,7 @@ mod tests { json: false, compact: false, watch: Some(WatchFlags { + hot_reload: false, no_clear_screen: true, }) }), @@ -5749,6 +5771,7 @@ mod tests { source_file: "source.ts".to_string(), out_file: None, watch: Some(WatchFlags { + hot_reload: false, no_clear_screen: false, }), }), @@ -5774,6 +5797,7 @@ mod tests { source_file: "source.ts".to_string(), out_file: None, watch: Some(WatchFlags { + hot_reload: false, no_clear_screen: true, }), }), @@ -6928,6 +6952,7 @@ mod tests { trace_ops: false, coverage_dir: None, watch: Some(WatchFlags { + hot_reload: false, no_clear_screen: false, }), reporter: Default::default(), @@ -6960,6 +6985,7 @@ mod tests { trace_ops: false, coverage_dir: None, watch: Some(WatchFlags { + hot_reload: false, no_clear_screen: false, }), reporter: Default::default(), @@ -6994,6 +7020,7 @@ mod tests { trace_ops: false, coverage_dir: None, watch: Some(WatchFlags { + hot_reload: false, no_clear_screen: true, }), reporter: Default::default(), @@ -7704,6 +7731,7 @@ mod tests { ignore: vec![], }, watch: Some(WatchFlags { + hot_reload: false, no_clear_screen: false, }), }), diff --git a/cli/args/mod.rs b/cli/args/mod.rs index f7f73fd4aeaaf2..f156dc8415c857 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -1054,6 +1054,18 @@ impl CliOptions { &self.flags.ext } + pub fn has_hot_reload(&self) -> bool { + if let DenoSubcommand::Run(RunFlags { + watch: Some(WatchFlagsWithPaths { hot_reload, .. }), + .. + }) = &self.flags.subcommand + { + *hot_reload + } else { + false + } + } + /// If the --inspect or --inspect-brk flags are used. pub fn is_inspecting(&self) -> bool { self.flags.inspect.is_some() diff --git a/cli/factory.rs b/cli/factory.rs index dbf9bd95ba1c0e..0628d6e02e54b0 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -63,21 +63,30 @@ use std::cell::RefCell; use std::future::Future; use std::path::PathBuf; use std::sync::Arc; +use tokio::sync::broadcast::Receiver; pub struct CliFactoryBuilder { - maybe_sender: Option>>, + maybe_watch_path_sender: + Option>>, + maybe_changed_path_receiver: + Option>>, } impl CliFactoryBuilder { pub fn new() -> Self { - Self { maybe_sender: None } + Self { + maybe_watch_path_sender: None, + maybe_changed_path_receiver: None, + } } pub fn with_watcher( mut self, sender: tokio::sync::mpsc::UnboundedSender>, + receiver: Option>>, ) -> Self { - self.maybe_sender = Some(sender); + self.maybe_watch_path_sender = Some(sender); + self.maybe_changed_path_receiver = receiver; self } @@ -90,7 +99,8 @@ impl CliFactoryBuilder { pub fn build_from_cli_options(self, options: Arc) -> CliFactory { CliFactory { - maybe_sender: RefCell::new(self.maybe_sender), + maybe_watch_path_sender: RefCell::new(self.maybe_watch_path_sender), + maybe_changed_path_receiver: self.maybe_changed_path_receiver, options, services: Default::default(), } @@ -168,8 +178,10 @@ struct CliFactoryServices { } pub struct CliFactory { - maybe_sender: + maybe_watch_path_sender: RefCell>>>, + maybe_changed_path_receiver: + Option>>, options: Arc, services: CliFactoryServices, } @@ -443,7 +455,7 @@ impl CliFactory { } pub fn maybe_file_watcher_reporter(&self) -> &Option { - let maybe_sender = self.maybe_sender.borrow_mut().take(); + let maybe_sender = self.maybe_watch_path_sender.borrow_mut().take(); self .services .maybe_file_watcher_reporter @@ -645,6 +657,10 @@ impl CliFactory { )), self.root_cert_store_provider().clone(), self.fs().clone(), + self + .maybe_changed_path_receiver + .as_ref() + .map(Receiver::resubscribe), self.maybe_inspector_server().clone(), self.maybe_lockfile().clone(), self.create_cli_main_worker_options()?, @@ -660,6 +676,7 @@ impl CliFactory { coverage_dir: self.options.coverage_dir(), enable_testing_features: self.options.enable_testing_features(), has_node_modules_dir: self.options.has_node_modules_dir(), + hot_reload: self.options.has_hot_reload(), inspect_brk: self.options.inspect_brk().is_some(), inspect_wait: self.options.inspect_wait().is_some(), is_inspecting: self.options.is_inspecting(), diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index f6c489487da229..071b0e3c5f11da 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -435,12 +435,14 @@ pub async fn run( fs, None, None, + None, CliMainWorkerOptions { argv: metadata.argv, log_level: WorkerLogLevel::Info, coverage_dir: None, enable_testing_features: false, has_node_modules_dir, + hot_reload: false, inspect_brk: false, inspect_wait: false, is_inspecting: false, diff --git a/cli/tools/bench/mod.rs b/cli/tools/bench/mod.rs index 34ce46bc381320..e8d4b77a1620cd 100644 --- a/cli/tools/bench/mod.rs +++ b/cli/tools/bench/mod.rs @@ -419,7 +419,7 @@ pub async fn run_benchmarks_with_watch( let bench_flags = bench_flags.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(sender.clone()) + .with_watcher(sender.clone(), None) .build_from_flags(flags) .await?; let cli_options = factory.cli_options(); diff --git a/cli/tools/bundle.rs b/cli/tools/bundle.rs index dc944d6464df79..ee3f063bdf34da 100644 --- a/cli/tools/bundle.rs +++ b/cli/tools/bundle.rs @@ -39,7 +39,7 @@ pub async fn bundle( let bundle_flags = bundle_flags.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(sender.clone()) + .with_watcher(sender.clone(), None) .build_from_flags(flags) .await?; let cli_options = factory.cli_options(); diff --git a/cli/tools/run/hot_reload/json_types.rs b/cli/tools/run/hot_reload/json_types.rs new file mode 100644 index 00000000000000..6fd69d4cc864b8 --- /dev/null +++ b/cli/tools/run/hot_reload/json_types.rs @@ -0,0 +1,53 @@ +use deno_core::serde_json::Value; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +pub struct RpcNotification { + pub method: String, + pub params: Value, +} + +#[derive(Debug, Deserialize)] +pub struct SetScriptSourceReturnObject { + pub status: Status, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ScriptParsed { + pub script_id: String, + pub url: String, +} + +#[derive(Debug, Deserialize)] +pub enum Status { + Ok, + CompileError, + BlockedByActiveGenerator, + BlockedByActiveFunction, + BlockedByTopLevelEsModuleChange, +} + +impl Status { + pub(crate) fn explain(&self) -> &'static str { + match self { + Status::Ok => "OK", + Status::CompileError => "compile error", + Status::BlockedByActiveGenerator => "blocked by active generator", + Status::BlockedByActiveFunction => "blocked by active function", + Status::BlockedByTopLevelEsModuleChange => { + "blocked by top-level ES module change" + } + } + } + + pub(crate) fn should_retry(&self) -> bool { + match self { + Status::Ok => false, + Status::CompileError => false, + Status::BlockedByActiveGenerator => true, + Status::BlockedByActiveFunction => true, + Status::BlockedByTopLevelEsModuleChange => false, + } + } +} diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs new file mode 100644 index 00000000000000..04fba428e2d778 --- /dev/null +++ b/cli/tools/run/hot_reload/mod.rs @@ -0,0 +1,131 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use std::{collections::HashMap, path::PathBuf}; + +use deno_core::{ + error::AnyError, + futures::StreamExt, + serde_json::{self, json}, + LocalInspectorSession, +}; +use deno_runtime::colors; +use tokio::select; + +use crate::tools::run::hot_reload::json_types::{ + RpcNotification, ScriptParsed, SetScriptSourceReturnObject, Status, +}; + +mod json_types; + +pub struct HotReloadManager { + session: LocalInspectorSession, + path_change_receiver: tokio::sync::broadcast::Receiver>, + script_ids: HashMap, +} + +impl HotReloadManager { + pub fn new( + session: LocalInspectorSession, + path_change_receiver: tokio::sync::broadcast::Receiver>, + ) -> Self { + Self { + session, + path_change_receiver, + script_ids: HashMap::new(), + } + } + + // FIXME(SyrupThinker): Inspector code duplication + + pub async fn start(&mut self) -> Result<(), AnyError> { + self.enable_debugger().await?; + + Ok(()) + } + + pub async fn stop(&mut self) -> Result<(), AnyError> { + self.disable_debugger().await?; + + Ok(()) + } + + pub async fn run(&mut self) -> Result<(), AnyError> { + let mut session_rx = self.session.take_notification_rx(); + loop { + select! { + biased; + // TODO(SyrupThinker): Deferred retry with timeout + Some(notification) = session_rx.next() => { + let notification = serde_json::from_value::(notification)?; + if notification.method == "Debugger.scriptParsed" { + let params = serde_json::from_value::(notification.params)?; + if params.url.starts_with("file://") { + self.script_ids.insert(params.url, params.script_id); + } + } + } + changed_paths = self.path_change_receiver.recv() => { + for path in changed_paths?.iter().filter(|p| p.extension().map_or(false, |ext| ext == "js")) { + if let Some(path_str) = path.to_str() { + let module_url = "file://".to_owned() + path_str; + log::info!("{} Reloading changed module {}", colors::intense_blue("Hot-reload"), module_url); + + if let Some(id) = self.script_ids.get(&module_url).map(String::clone) { + let src = tokio::fs::read_to_string(path).await?; + + loop { + let result = self.set_script_source(&id, &src).await?; + if !matches!(result.status, Status::Ok) { + log::warn!("{} Failed to reload module {}: {}", colors::intense_blue("Hot-reload"), module_url, colors::red(result.status.explain())); + } + if !result.status.should_retry() { + break; + } + } + } + } + } + } + _ = self.session.receive_from_v8_session() => {} + } + } + } + + async fn enable_debugger(&mut self) -> Result<(), AnyError> { + self + .session + .post_message::<()>("Debugger.enable", None) + .await?; + Ok(()) + } + + async fn disable_debugger(&mut self) -> Result<(), AnyError> { + self + .session + .post_message::<()>("Debugger.disable", None) + .await?; + Ok(()) + } + + async fn set_script_source( + &mut self, + script_id: &str, + source: &str, + ) -> Result { + let result = self + .session + .post_message( + "Debugger.setScriptSource", + Some(json!({ + "scriptId": script_id, + "scriptSource": source, + "allowTopFrameEditing": true, + })), + ) + .await?; + + Ok(serde_json::from_value::( + result, + )?) + } +} diff --git a/cli/tools/run.rs b/cli/tools/run/mod.rs similarity index 68% rename from cli/tools/run.rs rename to cli/tools/run/mod.rs index 6ded628ea6db40..f458f8ee9762ba 100644 --- a/cli/tools/run.rs +++ b/cli/tools/run/mod.rs @@ -16,6 +16,8 @@ use crate::factory::CliFactoryBuilder; use crate::file_fetcher::File; use crate::util; +pub mod hot_reload; + pub async fn run_script( flags: Flags, run_flags: RunFlags, @@ -104,42 +106,80 @@ async fn run_with_watch( flags: Flags, watch_flags: WatchFlagsWithPaths, ) -> Result { - util::file_watcher::watch_func( - flags, - util::file_watcher::PrintConfig { - job_name: "Process".to_string(), - clear_screen: !watch_flags.no_clear_screen, - }, - move |flags, sender, _changed_paths| { - Ok(async move { - let factory = CliFactoryBuilder::new() - .with_watcher(sender.clone()) - .build_from_flags(flags) - .await?; - let cli_options = factory.cli_options(); - let main_module = cli_options.resolve_main_module()?; - - maybe_npm_install(&factory).await?; - - let _ = sender.send(cli_options.watch_paths()); - - let permissions = PermissionsContainer::new(Permissions::from_options( - &cli_options.permissions_options(), - )?); - let worker = factory - .create_cli_main_worker_factory() - .await? - .create_main_worker(main_module, permissions) - .await?; - worker.run_for_watcher().await?; - - Ok(()) - }) - }, - ) - .await?; - - Ok(0) + if watch_flags.hot_reload { + util::file_watcher::watch_recv( + util::file_watcher::PrintConfig { + job_name: "Process".to_string(), + clear_screen: false, + }, + move |watch_path_sender, changed_path_receiver| { + Ok(async move { + let factory = CliFactoryBuilder::new() + .with_watcher( + watch_path_sender.clone(), + Some(changed_path_receiver), + ) + .build_from_flags(flags) + .await?; + let cli_options = factory.cli_options(); + let main_module = cli_options.resolve_main_module()?; + + maybe_npm_install(&factory).await?; + + let _ = watch_path_sender.send(cli_options.watch_paths()); + + let permissions = PermissionsContainer::new( + Permissions::from_options(&cli_options.permissions_options())?, + ); + let mut worker = factory + .create_cli_main_worker_factory() + .await? + .create_main_worker(main_module, permissions) + .await?; + + worker.run().await + }) + }, + ) + .await + } else { + util::file_watcher::watch_func( + flags, + util::file_watcher::PrintConfig { + job_name: "Process".to_string(), + clear_screen: !watch_flags.no_clear_screen, + }, + move |flags, sender, _changed_paths| { + Ok(async move { + let factory = CliFactoryBuilder::new() + .with_watcher(sender.clone(), None) + .build_from_flags(flags) + .await?; + let cli_options = factory.cli_options(); + let main_module = cli_options.resolve_main_module()?; + + maybe_npm_install(&factory).await?; + + let _ = sender.send(cli_options.watch_paths()); + + let permissions = PermissionsContainer::new( + Permissions::from_options(&cli_options.permissions_options())?, + ); + let worker = factory + .create_cli_main_worker_factory() + .await? + .create_main_worker(main_module, permissions) + .await?; + worker.run_for_watcher().await?; + + Ok(()) + }) + }, + ) + .await?; + + Ok(0) + } } pub async fn eval_command( diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index 101817ac9d1360..2bc52d52b79131 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -1202,7 +1202,7 @@ pub async fn run_tests_with_watch( let test_flags = test_flags.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(sender.clone()) + .with_watcher(sender.clone(), None) .build_from_flags(flags) .await?; let cli_options = factory.cli_options(); diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index ddeedb74150edc..b50940ea28e636 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -227,6 +227,77 @@ where } } +/// Creates a file watcher. +/// +/// - `operation` is the actual operation we want to run and notify every time +/// the watcher detects file changes. +pub async fn watch_recv( + print_config: PrintConfig, + operation: O, +) -> Result +where + O: FnOnce( + UnboundedSender>, + tokio::sync::broadcast::Receiver>, + ) -> Result, + F: Future>, +{ + let (paths_to_watch_sender, mut paths_to_watch_receiver) = + tokio::sync::mpsc::unbounded_channel(); + let (changed_paths_sender, changed_paths_receiver) = + tokio::sync::broadcast::channel(4); + let (watcher_sender, mut watcher_receiver) = + DebouncedReceiver::new_with_sender(); + + let PrintConfig { job_name, .. } = print_config; + + info!("{} {} started.", colors::intense_blue("Watcher"), job_name,); + + fn consume_paths_to_watch( + watcher: &mut RecommendedWatcher, + receiver: &mut UnboundedReceiver>, + ) { + loop { + match receiver.try_recv() { + Ok(paths) => { + add_paths_to_watcher(watcher, &paths); + } + Err(e) => match e { + mpsc::error::TryRecvError::Empty => { + break; + } + // there must be at least one receiver alive + _ => unreachable!(), + }, + } + } + } + + let mut watcher = new_watcher(watcher_sender.clone())?; + consume_paths_to_watch(&mut watcher, &mut paths_to_watch_receiver); + + let operation_future = + operation(paths_to_watch_sender, changed_paths_receiver)?; + tokio::pin!(operation_future); + + loop { + select! { + maybe_paths = paths_to_watch_receiver.recv() => { + add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); + }, + received_changed_paths = watcher_receiver.recv() => { + if let Some(changed_paths) = received_changed_paths { + changed_paths_sender.send(changed_paths)?; + } + }, + exit_code = &mut operation_future => { + // TODO(SyrupThinker) Exit code + return exit_code; + }, + }; + } +} + fn new_watcher( sender: Arc>>, ) -> Result { diff --git a/cli/worker.rs b/cli/worker.rs index b451cdbed5c7fe..1dbdfbc96887ce 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -39,6 +39,7 @@ use deno_runtime::worker::WorkerOptions; use deno_runtime::BootstrapOptions; use deno_runtime::WorkerLogLevel; use deno_semver::npm::NpmPackageReqReference; +use tokio::sync::broadcast::Receiver; use crate::args::StorageKeyResolver; use crate::errors; @@ -46,6 +47,7 @@ use crate::npm::CliNpmResolver; use crate::ops; use crate::tools; use crate::tools::coverage::CoverageCollector; +use crate::tools::run::hot_reload::HotReloadManager; use crate::util::checksum; use crate::version; @@ -78,6 +80,7 @@ pub struct CliMainWorkerOptions { pub coverage_dir: Option, pub enable_testing_features: bool, pub has_node_modules_dir: bool, + pub hot_reload: bool, pub inspect_brk: bool, pub inspect_wait: bool, pub is_inspecting: bool, @@ -102,6 +105,8 @@ struct SharedWorkerState { module_loader_factory: Box, root_cert_store_provider: Arc, fs: Arc, + maybe_changed_path_receiver: + Option>>, maybe_inspector_server: Option>, maybe_lockfile: Option>>, } @@ -130,6 +135,9 @@ impl CliMainWorker { pub async fn run(&mut self) -> Result { let mut maybe_coverage_collector = self.maybe_setup_coverage_collector().await?; + let mut maybe_hot_reload_manager = + self.maybe_setup_hot_reload_manager().await?; + log::debug!("main_module {}", self.main_module); if self.is_main_cjs { @@ -146,10 +154,18 @@ impl CliMainWorker { self.worker.dispatch_load_event(located_script_name!())?; loop { - self - .worker - .run_event_loop(maybe_coverage_collector.is_none()) - .await?; + if let Some(hot_reload_manager) = maybe_hot_reload_manager.as_mut() { + self + .worker + .with_event_loop(hot_reload_manager.run().boxed_local()) + .await?; + } else { + self + .worker + .run_event_loop(maybe_coverage_collector.is_none()) + .await?; + } + if !self .worker .dispatch_beforeunload_event(located_script_name!())? @@ -166,6 +182,12 @@ impl CliMainWorker { .with_event_loop(coverage_collector.stop_collecting().boxed_local()) .await?; } + if let Some(hot_reload_manager) = maybe_hot_reload_manager.as_mut() { + self + .worker + .with_event_loop(hot_reload_manager.stop().boxed_local()) + .await?; + } Ok(self.worker.exit_code()) } @@ -279,6 +301,31 @@ impl CliMainWorker { Ok(None) } } + + pub async fn maybe_setup_hot_reload_manager( + &mut self, + ) -> Result, AnyError> { + if !self.shared.options.hot_reload { + return Ok(None); + } + + if let Some(receiver) = self + .shared + .maybe_changed_path_receiver + .as_ref() + .map(Receiver::resubscribe) + { + let session = self.worker.create_inspector_session().await; + let mut hot_reload_manager = HotReloadManager::new(session, receiver); + self + .worker + .with_event_loop(hot_reload_manager.start().boxed_local()) + .await?; + Ok(Some(hot_reload_manager)) + } else { + Ok(None) + } + } } pub struct CliMainWorkerFactory { @@ -295,6 +342,9 @@ impl CliMainWorkerFactory { module_loader_factory: Box, root_cert_store_provider: Arc, fs: Arc, + maybe_changed_path_receiver: Option< + tokio::sync::broadcast::Receiver>, + >, maybe_inspector_server: Option>, maybe_lockfile: Option>>, options: CliMainWorkerOptions, @@ -312,6 +362,7 @@ impl CliMainWorkerFactory { module_loader_factory, root_cert_store_provider, fs, + maybe_changed_path_receiver, maybe_inspector_server, maybe_lockfile, }), From 139d09cb06018f0ed68783d4ae6938935ec1c960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 11 Oct 2023 01:24:46 +0200 Subject: [PATCH 02/79] update patch in Cargo.toml --- Cargo.lock | 10 ++-------- Cargo.toml | 3 +++ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 19964e9dcc56e7..f4825b44a3cc67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1206,8 +1206,6 @@ dependencies = [ [[package]] name = "deno_core" version = "0.221.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf694d66a55050bdfad2036765f93547392ba7c131adc7fcd5e91fa31e291c51" dependencies = [ "anyhow", "bytes", @@ -1595,8 +1593,6 @@ dependencies = [ [[package]] name = "deno_ops" version = "0.97.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aab46b645231decaf3b03d9eb99a5207e4e66c8c53b7774000b44e3ae43e0f99" dependencies = [ "deno-proc-macro-rules", "lazy-regex", @@ -4822,8 +4818,6 @@ dependencies = [ [[package]] name = "serde_v8" version = "0.130.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6078fc5e16615b092b26199e7f1d7d2d336848623d32e00f95e74618274111" dependencies = [ "bytes", "derive_more", @@ -6301,9 +6295,9 @@ dependencies = [ [[package]] name = "v8" -version = "0.79.1" +version = "0.79.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b87d5248d1a7e321a264d21dc7839675fc0bb456e489102272a55b44047869f0" +checksum = "b15561535230812a1db89a696f1f16a12ae6c2c370c6b2241c68d4cb33963faf" dependencies = [ "bitflags 1.3.2", "fslock", diff --git a/Cargo.toml b/Cargo.toml index 3720eb74f267f3..866050cc1be43e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -331,3 +331,6 @@ opt-level = 3 opt-level = 3 [profile.release.package.base64-simd] opt-level = 3 + +[patch.crates-io] +deno_core = { path = "../deno_core/core" } From e763c48d75f56e9c4bb33673eb146bb7062998a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 11 Oct 2023 01:34:58 +0200 Subject: [PATCH 03/79] add hmr.js --- hmr.js | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 hmr.js diff --git a/hmr.js b/hmr.js new file mode 100644 index 00000000000000..e1a6b18497fc1f --- /dev/null +++ b/hmr.js @@ -0,0 +1,6 @@ +console.log("Hello there 123!"); + +Deno.serve((req) => { + console.log("request", req); + return new Response("hello there 12"); +}); From cfe431fa8703cee8abee0c53ee96ba6a775c040f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 14 Oct 2023 22:58:54 +0200 Subject: [PATCH 04/79] fix after merge, use deno_core from git --- Cargo.lock | 9 +++------ Cargo.toml | 2 +- cli/standalone/mod.rs | 1 + 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6b740fc663d5e6..d47bf42c173284 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1206,8 +1206,7 @@ dependencies = [ [[package]] name = "deno_core" version = "0.222.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b13c81b9ea8462680e7b77088a44fc36390bab3dbfa5a205a285e11b64e0919c" +source = "git+https://github.com/bartlomieju/deno_core.git?branch=hmr#b3d6ba6c731eb25c463aa50c8c9fe67d134825b6" dependencies = [ "anyhow", "bytes", @@ -1595,8 +1594,7 @@ dependencies = [ [[package]] name = "deno_ops" version = "0.98.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf89da1a3e50ff7c89956495b53d9bcad29e1f1b3f3d2bc54cad7155f55419c4" +source = "git+https://github.com/bartlomieju/deno_core.git?branch=hmr#b3d6ba6c731eb25c463aa50c8c9fe67d134825b6" dependencies = [ "deno-proc-macro-rules", "lazy-regex", @@ -4822,8 +4820,7 @@ dependencies = [ [[package]] name = "serde_v8" version = "0.131.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38cafa16d0a4288d75925351bb54d06d2e830118ad3fad393947bb11f91b18f3" +source = "git+https://github.com/bartlomieju/deno_core.git?branch=hmr#b3d6ba6c731eb25c463aa50c8c9fe67d134825b6" dependencies = [ "bytes", "derive_more", diff --git a/Cargo.toml b/Cargo.toml index 972f4d521a0a04..f5b02d6a468974 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -333,4 +333,4 @@ opt-level = 3 opt-level = 3 [patch.crates-io] -deno_core = { path = "../deno_core/core" } +deno_core = { git = "https://github.com/bartlomieju/deno_core.git", branch = "hmr" } diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index ef0300c8ab4b58..e65541785a61ca 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -446,6 +446,7 @@ pub async fn run( fs, None, None, + None, feature_checker, CliMainWorkerOptions { argv: metadata.argv, From 86d2a6965df382643c8ff05e134265a09cf161c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 15 Oct 2023 01:46:24 +0200 Subject: [PATCH 05/79] rename to --hot --- cli/args/flags.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/args/flags.rs b/cli/args/flags.rs index f92366ca6516d9..319830d51f5285 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -2703,7 +2703,7 @@ fn seed_arg() -> Arg { fn hot_reload_arg() -> Arg { Arg::new("hot-reload") .requires("watch") - .long("hot-reload") + .long("hot") .help("UNSTABLE: Enable hot reload") .action(ArgAction::SetTrue) } From 43047d51a2cdcc47d0621d0e0a92c74c74b87219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 16 Oct 2023 00:22:34 +0200 Subject: [PATCH 06/79] add todos --- cli/tools/run/hot_reload/mod.rs | 66 ++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 04fba428e2d778..16594796f7ac96 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -1,19 +1,20 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -use std::{collections::HashMap, path::PathBuf}; - -use deno_core::{ - error::AnyError, - futures::StreamExt, - serde_json::{self, json}, - LocalInspectorSession, -}; +use std::collections::HashMap; +use std::path::PathBuf; + +use deno_core::error::AnyError; +use deno_core::futures::StreamExt; +use deno_core::serde_json::json; +use deno_core::serde_json::{self}; +use deno_core::LocalInspectorSession; use deno_runtime::colors; use tokio::select; -use crate::tools::run::hot_reload::json_types::{ - RpcNotification, ScriptParsed, SetScriptSourceReturnObject, Status, -}; +use crate::tools::run::hot_reload::json_types::RpcNotification; +use crate::tools::run::hot_reload::json_types::ScriptParsed; +use crate::tools::run::hot_reload::json_types::SetScriptSourceReturnObject; +use crate::tools::run::hot_reload::json_types::Status; mod json_types; @@ -35,20 +36,17 @@ impl HotReloadManager { } } - // FIXME(SyrupThinker): Inspector code duplication - + // TODO(bartlomieju): this code is duplicated in `cli/tools/coverage/mod.rs` pub async fn start(&mut self) -> Result<(), AnyError> { - self.enable_debugger().await?; - - Ok(()) + self.enable_debugger().await } + // TODO(bartlomieju): this code is duplicated in `cli/tools/coverage/mod.rs` pub async fn stop(&mut self) -> Result<(), AnyError> { - self.disable_debugger().await?; - - Ok(()) + self.disable_debugger().await } + // TODO(bartlomieju): Shouldn't use `tokio::select!` here, as futures are not cancel safe pub async fn run(&mut self) -> Result<(), AnyError> { let mut session_rx = self.session.take_notification_rx(); loop { @@ -65,22 +63,28 @@ impl HotReloadManager { } } changed_paths = self.path_change_receiver.recv() => { + // TODO(bartlomieju): check for other extensions for path in changed_paths?.iter().filter(|p| p.extension().map_or(false, |ext| ext == "js")) { if let Some(path_str) = path.to_str() { let module_url = "file://".to_owned() + path_str; log::info!("{} Reloading changed module {}", colors::intense_blue("Hot-reload"), module_url); - if let Some(id) = self.script_ids.get(&module_url).map(String::clone) { - let src = tokio::fs::read_to_string(path).await?; - - loop { - let result = self.set_script_source(&id, &src).await?; - if !matches!(result.status, Status::Ok) { - log::warn!("{} Failed to reload module {}: {}", colors::intense_blue("Hot-reload"), module_url, colors::red(result.status.explain())); - } - if !result.status.should_retry() { - break; - } + let Some(id) = self.script_ids.get(&module_url).cloned() else { + continue; + }; + + // TODO(bartlomieju): this should use `FileFetcher` interface instead + let src = tokio::fs::read_to_string(path).await?; + + // TODO(bartlomieju): this loop seems fishy + loop { + let result = self.set_script_source(&id, &src).await?; + if !matches!(result.status, Status::Ok) { + log::warn!("{} Failed to reload module {}: {}", colors::intense_blue("Hot-reload"), module_url, colors::red(result.status.explain())); + } + if !result.status.should_retry() { + // TODO(bartlomieju): Force a reload by the file watcher. + break; } } } @@ -91,6 +95,7 @@ impl HotReloadManager { } } + // TODO(bartlomieju): this code is duplicated in `cli/tools/coverage/mod.rs` async fn enable_debugger(&mut self) -> Result<(), AnyError> { self .session @@ -99,6 +104,7 @@ impl HotReloadManager { Ok(()) } + // TODO(bartlomieju): this code is duplicated in `cli/tools/coverage/mod.rs` async fn disable_debugger(&mut self) -> Result<(), AnyError> { self .session From 769c5736b737cb0f5cd5173698ae0e93b7af225e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 16 Oct 2023 00:28:15 +0200 Subject: [PATCH 07/79] transpilation todo --- cli/tools/run/hot_reload/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 16594796f7ac96..650e417dd3a1c3 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -74,6 +74,7 @@ impl HotReloadManager { }; // TODO(bartlomieju): this should use `FileFetcher` interface instead + // TODO(bartlomieju): we need to run the file through our transpile infrastructure as well let src = tokio::fs::read_to_string(path).await?; // TODO(bartlomieju): this loop seems fishy From 44ada3de036d9948af821e7e5e7425bcc01c7232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 16 Oct 2023 21:51:09 +0200 Subject: [PATCH 08/79] restart process on blocked hmr --- cli/factory.rs | 9 ++++ cli/standalone/mod.rs | 1 + cli/tools/bench/mod.rs | 2 +- cli/tools/bundle.rs | 2 +- cli/tools/run/hot_reload/mod.rs | 12 +++-- cli/tools/run/mod.rs | 9 +++- cli/tools/test/mod.rs | 2 +- cli/util/file_watcher.rs | 77 ++++++++++++++++++++++----------- cli/worker.rs | 14 +++++- foo.js | 5 +++ server.js | 11 +++++ 11 files changed, 110 insertions(+), 34 deletions(-) create mode 100644 foo.js create mode 100644 server.js diff --git a/cli/factory.rs b/cli/factory.rs index 07ac88b4a0e0a0..67c31f4e21741e 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -70,6 +70,8 @@ pub struct CliFactoryBuilder { Option>>, maybe_changed_path_receiver: Option>>, + maybe_file_watcher_restart_sender: + Option>, } impl CliFactoryBuilder { @@ -77,6 +79,7 @@ impl CliFactoryBuilder { Self { maybe_watch_path_sender: None, maybe_changed_path_receiver: None, + maybe_file_watcher_restart_sender: None, } } @@ -84,9 +87,11 @@ impl CliFactoryBuilder { mut self, sender: tokio::sync::mpsc::UnboundedSender>, receiver: Option>>, + restart_sender: Option>, ) -> Self { self.maybe_watch_path_sender = Some(sender); self.maybe_changed_path_receiver = receiver; + self.maybe_file_watcher_restart_sender = restart_sender; self } @@ -101,6 +106,7 @@ impl CliFactoryBuilder { CliFactory { maybe_watch_path_sender: RefCell::new(self.maybe_watch_path_sender), maybe_changed_path_receiver: self.maybe_changed_path_receiver, + maybe_file_watcher_restart_sender: self.maybe_file_watcher_restart_sender, options, services: Default::default(), } @@ -180,6 +186,8 @@ pub struct CliFactory { RefCell>>>, maybe_changed_path_receiver: Option>>, + maybe_file_watcher_restart_sender: + Option>, options: Arc, services: CliFactoryServices, } @@ -633,6 +641,7 @@ impl CliFactory { .maybe_changed_path_receiver .as_ref() .map(Receiver::resubscribe), + self.maybe_file_watcher_restart_sender.clone(), self.maybe_inspector_server().clone(), self.maybe_lockfile().clone(), self.feature_checker().clone(), diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index e65541785a61ca..99efb7b7d12c81 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -447,6 +447,7 @@ pub async fn run( None, None, None, + None, feature_checker, CliMainWorkerOptions { argv: metadata.argv, diff --git a/cli/tools/bench/mod.rs b/cli/tools/bench/mod.rs index bc71e0bc67a541..57f63747514692 100644 --- a/cli/tools/bench/mod.rs +++ b/cli/tools/bench/mod.rs @@ -421,7 +421,7 @@ pub async fn run_benchmarks_with_watch( let bench_flags = bench_flags.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(sender.clone(), None) + .with_watcher(sender.clone(), None, None) .build_from_flags(flags) .await?; let cli_options = factory.cli_options(); diff --git a/cli/tools/bundle.rs b/cli/tools/bundle.rs index 63d936fd1015b0..d329063e4854d8 100644 --- a/cli/tools/bundle.rs +++ b/cli/tools/bundle.rs @@ -39,7 +39,7 @@ pub async fn bundle( let bundle_flags = bundle_flags.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(sender.clone(), None) + .with_watcher(sender.clone(), None, None) .build_from_flags(flags) .await?; let cli_options = factory.cli_options(); diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 650e417dd3a1c3..5bb1c567954c56 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -21,6 +21,7 @@ mod json_types; pub struct HotReloadManager { session: LocalInspectorSession, path_change_receiver: tokio::sync::broadcast::Receiver>, + file_watcher_restart_sender: tokio::sync::mpsc::UnboundedSender<()>, script_ids: HashMap, } @@ -28,10 +29,12 @@ impl HotReloadManager { pub fn new( session: LocalInspectorSession, path_change_receiver: tokio::sync::broadcast::Receiver>, + file_watcher_restart_sender: tokio::sync::mpsc::UnboundedSender<()>, ) -> Self { Self { session, path_change_receiver, + file_watcher_restart_sender, script_ids: HashMap::new(), } } @@ -63,11 +66,12 @@ impl HotReloadManager { } } changed_paths = self.path_change_receiver.recv() => { + eprintln!("changed paths {:#?}", changed_paths); // TODO(bartlomieju): check for other extensions for path in changed_paths?.iter().filter(|p| p.extension().map_or(false, |ext| ext == "js")) { if let Some(path_str) = path.to_str() { let module_url = "file://".to_owned() + path_str; - log::info!("{} Reloading changed module {}", colors::intense_blue("Hot-reload"), module_url); + log::info!("{} Reloading changed module {}", colors::intense_blue("HMR"), module_url); let Some(id) = self.script_ids.get(&module_url).cloned() else { continue; @@ -81,10 +85,12 @@ impl HotReloadManager { loop { let result = self.set_script_source(&id, &src).await?; if !matches!(result.status, Status::Ok) { - log::warn!("{} Failed to reload module {}: {}", colors::intense_blue("Hot-reload"), module_url, colors::red(result.status.explain())); + log::info!("{} Failed to reload module {}: {}.", colors::intense_blue("HMR"), module_url, colors::gray(result.status.explain())); } if !result.status.should_retry() { - // TODO(bartlomieju): Force a reload by the file watcher. + log::info!("{} Restarting the process...", colors::intense_blue("HMR")); + // TODO(bartlomieju): Print into that sending failed? + let _ = self.file_watcher_restart_sender.send(()); break; } } diff --git a/cli/tools/run/mod.rs b/cli/tools/run/mod.rs index 95b0851a0b7b77..4fd5db155c1d9c 100644 --- a/cli/tools/run/mod.rs +++ b/cli/tools/run/mod.rs @@ -108,16 +108,21 @@ async fn run_with_watch( ) -> Result { if watch_flags.hot_reload { util::file_watcher::watch_recv( + flags, util::file_watcher::PrintConfig { job_name: "Process".to_string(), clear_screen: false, }, - move |watch_path_sender, changed_path_receiver| { + move |flags, + watch_path_sender, + watcher_restart_sender, + changed_path_receiver| { Ok(async move { let factory = CliFactoryBuilder::new() .with_watcher( watch_path_sender.clone(), Some(changed_path_receiver), + Some(watcher_restart_sender), ) .build_from_flags(flags) .await?; @@ -152,7 +157,7 @@ async fn run_with_watch( move |flags, sender, _changed_paths| { Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(sender.clone(), None) + .with_watcher(sender.clone(), None, None) .build_from_flags(flags) .await?; let cli_options = factory.cli_options(); diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index 9cad0e96e8c699..5ca8da6d79396b 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -1217,7 +1217,7 @@ pub async fn run_tests_with_watch( let test_flags = test_flags.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(sender.clone(), None) + .with_watcher(sender.clone(), None, None) .build_from_flags(flags) .await?; let cli_options = factory.cli_options(); diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 89e0c79797cbad..8ec9b0b2ac2705 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -233,26 +233,29 @@ where /// - `operation` is the actual operation we want to run and notify every time /// the watcher detects file changes. pub async fn watch_recv( + flags: Flags, print_config: PrintConfig, - operation: O, + mut operation: O, ) -> Result where - O: FnOnce( + O: FnMut( + Flags, UnboundedSender>, + tokio::sync::mpsc::UnboundedSender<()>, tokio::sync::broadcast::Receiver>, ) -> Result, F: Future>, { let (paths_to_watch_sender, mut paths_to_watch_receiver) = tokio::sync::mpsc::unbounded_channel(); - let (changed_paths_sender, changed_paths_receiver) = - tokio::sync::broadcast::channel(4); + let (watcher_restart_sender, mut watcher_restart_receiver) = + tokio::sync::mpsc::unbounded_channel(); let (watcher_sender, mut watcher_receiver) = DebouncedReceiver::new_with_sender(); let PrintConfig { job_name, .. } = print_config; - info!("{} {} started.", colors::intense_blue("Watcher"), job_name,); + info!("{} {} started.", colors::intense_blue("HMR"), job_name,); fn consume_paths_to_watch( watcher: &mut RecommendedWatcher, @@ -261,6 +264,7 @@ where loop { match receiver.try_recv() { Ok(paths) => { + // eprintln!("add paths to watch {:#?}", paths); add_paths_to_watcher(watcher, &paths); } Err(e) => match e { @@ -274,28 +278,51 @@ where } } - let mut watcher = new_watcher(watcher_sender.clone())?; - consume_paths_to_watch(&mut watcher, &mut paths_to_watch_receiver); + 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 + // start to fail, this may need to be increased. + for _ in 0..10 { + tokio::task::yield_now().await; + } - let operation_future = - operation(paths_to_watch_sender, changed_paths_receiver)?; - tokio::pin!(operation_future); + let mut watcher = new_watcher(watcher_sender.clone())?; + consume_paths_to_watch(&mut watcher, &mut paths_to_watch_receiver); - loop { - select! { - maybe_paths = paths_to_watch_receiver.recv() => { - add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); - }, - received_changed_paths = watcher_receiver.recv() => { - if let Some(changed_paths) = received_changed_paths { - changed_paths_sender.send(changed_paths)?; - } - }, - exit_code = &mut operation_future => { - // TODO(SyrupThinker) Exit code - return exit_code; - }, - }; + let (changed_paths_sender, changed_paths_receiver) = + tokio::sync::broadcast::channel(4); + + // TODO(bartlomieju): wrap in error handler + let operation_future = operation( + flags.clone(), + paths_to_watch_sender.clone(), + watcher_restart_sender.clone(), + changed_paths_receiver, + )?; + tokio::pin!(operation_future); + + loop { + select! { + maybe_paths = paths_to_watch_receiver.recv() => { + // eprintln!("add paths to watcher {:#?}", maybe_paths); + add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); + }, + received_changed_paths = watcher_receiver.recv() => { + // eprintln!("changed paths for watcher {:#?}", received_changed_paths); + if let Some(changed_paths) = received_changed_paths { + changed_paths_sender.send(changed_paths)?; + } + }, + _ = watcher_restart_receiver.recv() => { + drop(operation_future); + break; + }, + exit_code = &mut operation_future => { + // TODO(SyrupThinker) Exit code + return exit_code; + }, + }; + } } } diff --git a/cli/worker.rs b/cli/worker.rs index a07c73518f2b4d..5ed962ccb41297 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -113,6 +113,8 @@ struct SharedWorkerState { fs: Arc, maybe_changed_path_receiver: Option>>, + maybe_file_watcher_restart_sender: + Option>, maybe_inspector_server: Option>, maybe_lockfile: Option>>, feature_checker: Arc, @@ -322,8 +324,14 @@ impl CliMainWorker { .as_ref() .map(Receiver::resubscribe) { + let restart_sender = self + .shared + .maybe_file_watcher_restart_sender + .clone() + .unwrap(); let session = self.worker.create_inspector_session().await; - let mut hot_reload_manager = HotReloadManager::new(session, receiver); + let mut hot_reload_manager = + HotReloadManager::new(session, receiver, restart_sender); self .worker .with_event_loop(hot_reload_manager.start().boxed_local()) @@ -363,6 +371,9 @@ impl CliMainWorkerFactory { maybe_changed_path_receiver: Option< tokio::sync::broadcast::Receiver>, >, + maybe_file_watcher_restart_sender: Option< + tokio::sync::mpsc::UnboundedSender<()>, + >, maybe_inspector_server: Option>, maybe_lockfile: Option>>, feature_checker: Arc, @@ -382,6 +393,7 @@ impl CliMainWorkerFactory { root_cert_store_provider, fs, maybe_changed_path_receiver, + maybe_file_watcher_restart_sender, maybe_inspector_server, maybe_lockfile, feature_checker, diff --git a/foo.js b/foo.js new file mode 100644 index 00000000000000..f472881c26b56c --- /dev/null +++ b/foo.js @@ -0,0 +1,5 @@ +export function foobar() { + console.log("as123"); +} + +foobar(); diff --git a/server.js b/server.js new file mode 100644 index 00000000000000..f8db46d12b9c97 --- /dev/null +++ b/server.js @@ -0,0 +1,11 @@ +globalThis.state = { i: 0 }; + +// function bar() { +// } + +function handler(req) { + console.log("req", req); + return new Response("hello12"); +} + +Deno.serve(handler); From b93c09005e9974d31c4f8d2ee359c6ff2b6c4fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 16 Oct 2023 22:03:59 +0200 Subject: [PATCH 09/79] simplify cli --- cli/args/flags.rs | 58 +++++++++++++++++++++++++-------- cli/tools/run/hot_reload/mod.rs | 2 +- server.js | 4 +-- 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/cli/args/flags.rs b/cli/args/flags.rs index 319830d51f5285..44cf83f7409ada 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -1830,8 +1830,8 @@ fn repl_subcommand() -> Command { fn run_subcommand() -> Command { runtime_args(Command::new("run"), true, true) .arg(check_arg(false)) - .arg(hot_reload_arg()) .arg(watch_arg(true)) + .arg(hmr_arg(true)) .arg(no_clear_screen_arg()) .arg(executable_ext_arg()) .arg( @@ -2700,12 +2700,31 @@ fn seed_arg() -> Arg { .value_parser(value_parser!(u64)) } -fn hot_reload_arg() -> Arg { - Arg::new("hot-reload") - .requires("watch") - .long("hot") - .help("UNSTABLE: Enable hot reload") - .action(ArgAction::SetTrue) +fn hmr_arg(takes_files: bool) -> Arg { + let arg = Arg::new("hmr") + .long("hmr") + .help("UNSTABLE: Watch for file changes and hot replace modules") + .conflicts_with("watch"); + + if takes_files { + arg + .value_name("FILES") + .num_args(0..) + .value_parser(value_parser!(PathBuf)) + .use_value_delimiter(true) + .require_equals(true) + .long_help( + "Watch for file changes and restart process automatically. +Local files from entry point module graph are watched by default. +Additional paths might be watched by passing them as arguments to this flag.", + ) + .value_hint(ValueHint::AnyPath) + } else { + arg.action(ArgAction::SetTrue).long_help( + "Watch for file changes and restart process automatically. + Only local files from entry point module graph are watched.", + ) + } } fn watch_arg(takes_files: bool) -> Arg { @@ -3821,7 +3840,12 @@ fn reload_arg_validate(urlstr: &str) -> Result { fn watch_arg_parse(matches: &mut ArgMatches) -> Option { if matches.get_flag("watch") { Some(WatchFlags { - hot_reload: matches.get_flag("hot-reload"), + hot_reload: false, + no_clear_screen: matches.get_flag("no-clear-screen"), + }) + } else if matches.get_flag("hmr") { + Some(WatchFlags { + hot_reload: true, no_clear_screen: matches.get_flag("no-clear-screen"), }) } else { @@ -3832,13 +3856,21 @@ fn watch_arg_parse(matches: &mut ArgMatches) -> Option { fn watch_arg_parse_with_paths( matches: &mut ArgMatches, ) -> Option { - matches - .remove_many::("watch") - .map(|f| WatchFlagsWithPaths { - paths: f.collect(), - hot_reload: matches.get_flag("hot-reload"), + if let Some(paths) = matches.remove_many::("watch") { + Some(WatchFlagsWithPaths { + paths: paths.collect(), + hot_reload: false, + no_clear_screen: matches.get_flag("no-clear-screen"), + }) + } else if let Some(paths) = matches.remove_many::("hmr") { + Some(WatchFlagsWithPaths { + paths: paths.collect(), + hot_reload: true, no_clear_screen: matches.get_flag("no-clear-screen"), }) + } else { + None + } } // TODO(ry) move this to utility module and add test. diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 5bb1c567954c56..609d8a5ef4a23b 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -66,7 +66,7 @@ impl HotReloadManager { } } changed_paths = self.path_change_receiver.recv() => { - eprintln!("changed paths {:#?}", changed_paths); + // eprintln!("changed paths {:#?}", changed_paths); // TODO(bartlomieju): check for other extensions for path in changed_paths?.iter().filter(|p| p.extension().map_or(false, |ext| ext == "js")) { if let Some(path_str) = path.to_str() { diff --git a/server.js b/server.js index f8db46d12b9c97..825a86cab175b6 100644 --- a/server.js +++ b/server.js @@ -1,7 +1,7 @@ globalThis.state = { i: 0 }; -// function bar() { -// } +function bar() { +} function handler(req) { console.log("req", req); From 8b07090dd9b69a299335cc631c8a1aece2c6255c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 16 Oct 2023 22:25:01 +0200 Subject: [PATCH 10/79] copyright, todo --- cli/tools/run/hot_reload/json_types.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cli/tools/run/hot_reload/json_types.rs b/cli/tools/run/hot_reload/json_types.rs index 6fd69d4cc864b8..3ac80344b6ce6a 100644 --- a/cli/tools/run/hot_reload/json_types.rs +++ b/cli/tools/run/hot_reload/json_types.rs @@ -1,3 +1,9 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +// TODO(bartlomieju): this code should be factored out to `cli/cdp.rs` along +// with code in `cli/tools/repl/` and `cli/tools/coverage/`. These are all +// Chrome Devtools Protocol message types. + use deno_core::serde_json::Value; use serde::Deserialize; From 0b58bac02c7fe47e8e230b3db92794514c4e4a7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 16 Oct 2023 23:14:22 +0200 Subject: [PATCH 11/79] transpile --- cli/factory.rs | 1 + cli/standalone/mod.rs | 1 + cli/tools/run/hot_reload/mod.rs | 85 +++++++++++++++++++-------------- cli/worker.rs | 7 ++- server.ts | 11 +++++ 5 files changed, 69 insertions(+), 36 deletions(-) create mode 100644 server.ts diff --git a/cli/factory.rs b/cli/factory.rs index 67c31f4e21741e..baf395bd5b01a8 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -637,6 +637,7 @@ impl CliFactory { )), self.root_cert_store_provider().clone(), self.fs().clone(), + Some(self.emitter()?.clone()), self .maybe_changed_path_receiver .as_ref() diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index 99efb7b7d12c81..cc86fb815995b3 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -448,6 +448,7 @@ pub async fn run( None, None, None, + None, feature_checker, CliMainWorkerOptions { argv: metadata.argv, diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 609d8a5ef4a23b..81b98aba6b7b6f 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -1,38 +1,44 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -use std::collections::HashMap; -use std::path::PathBuf; - +use crate::emit::Emitter; +use deno_ast::MediaType; use deno_core::error::AnyError; use deno_core::futures::StreamExt; use deno_core::serde_json::json; use deno_core::serde_json::{self}; +use deno_core::url::Url; use deno_core::LocalInspectorSession; use deno_runtime::colors; +use std::collections::HashMap; +use std::path::PathBuf; +use std::sync::Arc; use tokio::select; -use crate::tools::run::hot_reload::json_types::RpcNotification; -use crate::tools::run::hot_reload::json_types::ScriptParsed; -use crate::tools::run::hot_reload::json_types::SetScriptSourceReturnObject; -use crate::tools::run::hot_reload::json_types::Status; - mod json_types; +use json_types::RpcNotification; +use json_types::ScriptParsed; +use json_types::SetScriptSourceReturnObject; +use json_types::Status; + pub struct HotReloadManager { session: LocalInspectorSession, path_change_receiver: tokio::sync::broadcast::Receiver>, file_watcher_restart_sender: tokio::sync::mpsc::UnboundedSender<()>, script_ids: HashMap, + emitter: Arc, } impl HotReloadManager { pub fn new( + emitter: Arc, session: LocalInspectorSession, path_change_receiver: tokio::sync::broadcast::Receiver>, file_watcher_restart_sender: tokio::sync::mpsc::UnboundedSender<()>, ) -> Self { Self { session, + emitter, path_change_receiver, file_watcher_restart_sender, script_ids: HashMap::new(), @@ -66,33 +72,42 @@ impl HotReloadManager { } } changed_paths = self.path_change_receiver.recv() => { - // eprintln!("changed paths {:#?}", changed_paths); - // TODO(bartlomieju): check for other extensions - for path in changed_paths?.iter().filter(|p| p.extension().map_or(false, |ext| ext == "js")) { - if let Some(path_str) = path.to_str() { - let module_url = "file://".to_owned() + path_str; - log::info!("{} Reloading changed module {}", colors::intense_blue("HMR"), module_url); - - let Some(id) = self.script_ids.get(&module_url).cloned() else { - continue; - }; - - // TODO(bartlomieju): this should use `FileFetcher` interface instead - // TODO(bartlomieju): we need to run the file through our transpile infrastructure as well - let src = tokio::fs::read_to_string(path).await?; - - // TODO(bartlomieju): this loop seems fishy - loop { - let result = self.set_script_source(&id, &src).await?; - if !matches!(result.status, Status::Ok) { - log::info!("{} Failed to reload module {}: {}.", colors::intense_blue("HMR"), module_url, colors::gray(result.status.explain())); - } - if !result.status.should_retry() { - log::info!("{} Restarting the process...", colors::intense_blue("HMR")); - // TODO(bartlomieju): Print into that sending failed? - let _ = self.file_watcher_restart_sender.send(()); - break; - } + let changed_paths = changed_paths?; + let filtered_paths: Vec = changed_paths.into_iter().filter(|p| p.extension().map_or(false, |ext| { + let ext_str = ext.to_str().unwrap(); + matches!(ext_str, "js" | "ts" | "jsx" | "tsx") + })).collect(); + + for path in filtered_paths { + let Some(path_str) = path.to_str() else { + continue; + }; + let Ok(module_url) = Url::from_file_path(path_str) else { + continue; + }; + + log::info!("{} Reloading changed module {}", colors::intense_blue("HMR"), module_url.as_str()); + + let Some(id) = self.script_ids.get(module_url.as_str()).cloned() else { + continue; + }; + + let media_type = MediaType::from_path(&path); + let source = tokio::fs::read_to_string(path).await?; + let source_arc: Arc = Arc::from(source.as_str()); + let source_code = self.emitter.emit_parsed_source(&module_url, media_type, &source_arc)?; + + // TODO(bartlomieju): this loop seems fishy + loop { + let result = self.set_script_source(&id, source_code.as_str()).await?; + if !matches!(result.status, Status::Ok) { + log::info!("{} Failed to reload module {}: {}.", colors::intense_blue("HMR"), module_url, colors::gray(result.status.explain())); + } + if !result.status.should_retry() { + log::info!("{} Restarting the process...", colors::intense_blue("HMR")); + // TODO(bartlomieju): Print into that sending failed? + let _ = self.file_watcher_restart_sender.send(()); + break; } } } diff --git a/cli/worker.rs b/cli/worker.rs index 5ed962ccb41297..bd965e118a027b 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -47,6 +47,7 @@ use tokio::sync::broadcast::Receiver; use crate::args::package_json::PackageJsonDeps; use crate::args::StorageKeyResolver; +use crate::emit::Emitter; use crate::errors; use crate::npm::CliNpmResolver; use crate::ops; @@ -111,6 +112,7 @@ struct SharedWorkerState { module_loader_factory: Box, root_cert_store_provider: Arc, fs: Arc, + emitter: Option>, maybe_changed_path_receiver: Option>>, maybe_file_watcher_restart_sender: @@ -329,9 +331,10 @@ impl CliMainWorker { .maybe_file_watcher_restart_sender .clone() .unwrap(); + let emitter = self.shared.emitter.clone().unwrap(); let session = self.worker.create_inspector_session().await; let mut hot_reload_manager = - HotReloadManager::new(session, receiver, restart_sender); + HotReloadManager::new(emitter, session, receiver, restart_sender); self .worker .with_event_loop(hot_reload_manager.start().boxed_local()) @@ -368,6 +371,7 @@ impl CliMainWorkerFactory { module_loader_factory: Box, root_cert_store_provider: Arc, fs: Arc, + emitter: Option>, maybe_changed_path_receiver: Option< tokio::sync::broadcast::Receiver>, >, @@ -391,6 +395,7 @@ impl CliMainWorkerFactory { compiled_wasm_module_store: Default::default(), module_loader_factory, root_cert_store_provider, + emitter, fs, maybe_changed_path_receiver, maybe_file_watcher_restart_sender, diff --git a/server.ts b/server.ts new file mode 100644 index 00000000000000..6da28ec925c966 --- /dev/null +++ b/server.ts @@ -0,0 +1,11 @@ +globalThis.state = { i: 0 }; + +function bar() { +} + +function handler(req) { + console.log("req", req); + return new Response("hello1234"); +} + +Deno.serve(handler); From f467c8eca9d539c89890123fcb130fce84f56c56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 16 Oct 2023 23:29:27 +0200 Subject: [PATCH 12/79] fix --- cli/tools/run/hot_reload/mod.rs | 12 ++++++------ foo.js | 6 ++---- main.js | 4 ++++ 3 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 main.js diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 81b98aba6b7b6f..ffb662da6c2eb9 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -102,12 +102,12 @@ impl HotReloadManager { let result = self.set_script_source(&id, source_code.as_str()).await?; if !matches!(result.status, Status::Ok) { log::info!("{} Failed to reload module {}: {}.", colors::intense_blue("HMR"), module_url, colors::gray(result.status.explain())); - } - if !result.status.should_retry() { - log::info!("{} Restarting the process...", colors::intense_blue("HMR")); - // TODO(bartlomieju): Print into that sending failed? - let _ = self.file_watcher_restart_sender.send(()); - break; + if !result.status.should_retry() { + log::info!("{} Restarting the process...", colors::intense_blue("HMR")); + // TODO(bartlomieju): Print into that sending failed? + let _ = self.file_watcher_restart_sender.send(()); + break; + } } } } diff --git a/foo.js b/foo.js index f472881c26b56c..4f559c9fefcedd 100644 --- a/foo.js +++ b/foo.js @@ -1,5 +1,3 @@ -export function foobar() { - console.log("as123"); +export function getFoo() { + return "foo"; // change this line } - -foobar(); diff --git a/main.js b/main.js new file mode 100644 index 00000000000000..565b9cc54a5b3e --- /dev/null +++ b/main.js @@ -0,0 +1,4 @@ +import { getFoo } from "./foo.js"; + +let i = 0; +setInterval(() => console.log(i++, getFoo())); From dd10397c47880f1114abafc766ae747fc4105571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 17 Oct 2023 00:46:48 +0200 Subject: [PATCH 13/79] fix multiple updates --- cli/tools/run/hot_reload/mod.rs | 30 ++++++++++++++++++------------ foo.js | 2 +- main.js | 3 ++- server.js | 4 ++-- server.ts | 2 +- 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index ffb662da6c2eb9..c2d856741ec753 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -92,22 +92,28 @@ impl HotReloadManager { continue; }; - let media_type = MediaType::from_path(&path); - let source = tokio::fs::read_to_string(path).await?; - let source_arc: Arc = Arc::from(source.as_str()); - let source_code = self.emitter.emit_parsed_source(&module_url, media_type, &source_arc)?; + // TODO(bartlomieju): reenable transpilation; but the fact that + // #sourceMappingURL=data:... is present at the end of transpiled + // output, prohibits HMR updates for anything non-JS. + // let media_type = MediaType::from_path(&path); + let source_code = tokio::fs::read_to_string(path).await?; + // let source_arc: Arc = Arc::from(source_code.as_str()); + // let source_code = self.emitter.emit_parsed_source(&module_url, media_type, &source_arc)?; // TODO(bartlomieju): this loop seems fishy loop { let result = self.set_script_source(&id, source_code.as_str()).await?; - if !matches!(result.status, Status::Ok) { - log::info!("{} Failed to reload module {}: {}.", colors::intense_blue("HMR"), module_url, colors::gray(result.status.explain())); - if !result.status.should_retry() { - log::info!("{} Restarting the process...", colors::intense_blue("HMR")); - // TODO(bartlomieju): Print into that sending failed? - let _ = self.file_watcher_restart_sender.send(()); - break; - } + + if matches!(result.status, Status::Ok) { + break; + } + + log::info!("{} Failed to reload module {}: {}.", colors::intense_blue("HMR"), module_url, colors::gray(result.status.explain())); + if !result.status.should_retry() { + log::info!("{} Restarting the process...", colors::intense_blue("HMR")); + // TODO(bartlomieju): Print into that sending failed? + let _ = self.file_watcher_restart_sender.send(()); + break; } } } diff --git a/foo.js b/foo.js index 4f559c9fefcedd..b8499df81974a9 100644 --- a/foo.js +++ b/foo.js @@ -1,3 +1,3 @@ export function getFoo() { - return "foo"; // change this line + return "foo123434"; // change this line } diff --git a/main.js b/main.js index 565b9cc54a5b3e..3eab855ce56998 100644 --- a/main.js +++ b/main.js @@ -1,4 +1,5 @@ import { getFoo } from "./foo.js"; +//// asdf let i = 0; -setInterval(() => console.log(i++, getFoo())); +setInterval(() => console.log(i++, getFoo()), 1000); diff --git a/server.js b/server.js index 825a86cab175b6..45c35f2e462576 100644 --- a/server.js +++ b/server.js @@ -4,8 +4,8 @@ function bar() { } function handler(req) { - console.log("req", req); - return new Response("hello12"); + // console.log("req111123", req); + return new Response("hello122334"); } Deno.serve(handler); diff --git a/server.ts b/server.ts index 6da28ec925c966..f329853a5ed116 100644 --- a/server.ts +++ b/server.ts @@ -5,7 +5,7 @@ function bar() { function handler(req) { console.log("req", req); - return new Response("hello1234"); + return new Response("hello"); } Deno.serve(handler); From 1680d2442bffefcc86585b6ca4309db60211375d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 17 Oct 2023 00:52:49 +0200 Subject: [PATCH 14/79] fix transpilation by disabling source maps --- cli/emit.rs | 4 ++-- cli/tools/run/hot_reload/mod.rs | 23 +++++++++++++++++------ server.ts | 4 ++-- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/cli/emit.rs b/cli/emit.rs index e81d2e83c61ac5..f2f78c0d3d537e 100644 --- a/cli/emit.rs +++ b/cli/emit.rs @@ -14,8 +14,8 @@ use std::sync::Arc; pub struct Emitter { emit_cache: EmitCache, - parsed_source_cache: Arc, - emit_options: deno_ast::EmitOptions, + pub parsed_source_cache: Arc, + pub emit_options: deno_ast::EmitOptions, // cached hash of the emit options emit_options_hash: u64, } diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index c2d856741ec753..8a0a0664b5ced5 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -92,14 +92,25 @@ impl HotReloadManager { continue; }; - // TODO(bartlomieju): reenable transpilation; but the fact that - // #sourceMappingURL=data:... is present at the end of transpiled - // output, prohibits HMR updates for anything non-JS. - // let media_type = MediaType::from_path(&path); + // TODO(bartlomieju): I really don't like `self.emitter` etc here. + // Maybe use `deno_ast` directly? + let media_type = MediaType::from_path(&path); let source_code = tokio::fs::read_to_string(path).await?; - // let source_arc: Arc = Arc::from(source_code.as_str()); - // let source_code = self.emitter.emit_parsed_source(&module_url, media_type, &source_arc)?; + let source_arc: Arc = Arc::from(source_code.as_str()); + let source_code = { + let parsed_source = self.emitter.parsed_source_cache.get_or_parse_module( + &module_url, + source_arc.clone(), + media_type, + )?; + let mut options = self.emitter.emit_options.clone(); + options.inline_source_map = false; + let transpiled_source = parsed_source.transpile(&options)?; + transpiled_source.text.to_string() + // self.emitter.emit_parsed_source(&module_url, media_type, &source_arc)? + }; + // eprintln!("transpiled source code {:#?}", source_code); // TODO(bartlomieju): this loop seems fishy loop { let result = self.set_script_source(&id, source_code.as_str()).await?; diff --git a/server.ts b/server.ts index f329853a5ed116..07c0e5053435a6 100644 --- a/server.ts +++ b/server.ts @@ -4,8 +4,8 @@ function bar() { } function handler(req) { - console.log("req", req); - return new Response("hello"); + // console.log("req123", req); + return new Response("hello124353"); } Deno.serve(handler); From 1df1d6eefa7ba5b9deb9aa5bc8ee1bc33b60cf4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 17 Oct 2023 01:05:36 +0200 Subject: [PATCH 15/79] dispatch 'hmr' event on module replace --- cli/tools/run/hot_reload/mod.rs | 24 ++++++++++++++++++++++++ server.ts | 6 +++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 8a0a0664b5ced5..25e008101400a5 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -116,6 +116,7 @@ impl HotReloadManager { let result = self.set_script_source(&id, source_code.as_str()).await?; if matches!(result.status, Status::Ok) { + self.dispatch_hmr_event(module_url.as_str()).await?; break; } @@ -173,4 +174,27 @@ impl HotReloadManager { result, )?) } + + async fn dispatch_hmr_event( + &mut self, + script_id: &str, + ) -> Result<(), AnyError> { + let expr = format!( + "dispatchEvent(new CustomEvent(\"hmr\", {{ detail: {{ path: \"{}\" }} }}));", + script_id + ); + + let _result = self + .session + .post_message( + "Runtime.evaluate", + Some(json!({ + "expression": expr, + "contextId": Some(1), + })), + ) + .await?; + + Ok(()) + } } diff --git a/server.ts b/server.ts index 07c0e5053435a6..155236df56237b 100644 --- a/server.ts +++ b/server.ts @@ -5,7 +5,11 @@ function bar() { function handler(req) { // console.log("req123", req); - return new Response("hello124353"); + return new Response("Hello world!123"); } Deno.serve(handler); + +addEventListener("hmr", (ev) => { + console.log("event", ev.detail); +}); From 91b71623a1b9c1bbd0df8aa18894d75e5f516441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 17 Oct 2023 04:09:12 +0200 Subject: [PATCH 16/79] ignore exit code --- cli/tools/run/mod.rs | 4 +++- cli/util/file_watcher.rs | 24 ++++++++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/cli/tools/run/mod.rs b/cli/tools/run/mod.rs index 4fd5db155c1d9c..792714621e1bb1 100644 --- a/cli/tools/run/mod.rs +++ b/cli/tools/run/mod.rs @@ -142,7 +142,9 @@ async fn run_with_watch( .create_main_worker(main_module, permissions) .await?; - worker.run().await + worker.run().await?; + + Ok(()) }) }, ) diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 8ec9b0b2ac2705..e034362b89525a 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -244,7 +244,7 @@ where tokio::sync::mpsc::UnboundedSender<()>, tokio::sync::broadcast::Receiver>, ) -> Result, - F: Future>, + F: Future>, { let (paths_to_watch_sender, mut paths_to_watch_receiver) = tokio::sync::mpsc::unbounded_channel(); @@ -292,13 +292,12 @@ where let (changed_paths_sender, changed_paths_receiver) = tokio::sync::broadcast::channel(4); - // TODO(bartlomieju): wrap in error handler - let operation_future = operation( + let operation_future = error_handler(operation( flags.clone(), paths_to_watch_sender.clone(), watcher_restart_sender.clone(), changed_paths_receiver, - )?; + )?); tokio::pin!(operation_future); loop { @@ -314,12 +313,21 @@ where } }, _ = watcher_restart_receiver.recv() => { - drop(operation_future); + // drop(operation_future); break; }, - exit_code = &mut operation_future => { - // TODO(SyrupThinker) Exit code - return exit_code; + success = &mut operation_future => { + // TODO(bartlomieju): print exit code here? + info!( + "{} {} {}. Restarting on file change...", + colors::intense_blue("Watcher"), + job_name, + if success { + "finished" + } else { + "failed" + } + ); }, }; } From a7e9f43a67a711ec0a5482de203429dd295a8e84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 17 Oct 2023 04:34:00 +0200 Subject: [PATCH 17/79] Add temporary WatcherInterface --- cli/factory.rs | 51 +++++++++++++++------------------------- cli/tools/bench/mod.rs | 5 ++-- cli/tools/bundle.rs | 5 ++-- cli/tools/fmt.rs | 3 ++- cli/tools/lint.rs | 4 ++-- cli/tools/run/mod.rs | 17 +++++--------- cli/tools/test/mod.rs | 5 ++-- cli/util/file_watcher.rs | 39 +++++++++++++++++------------- 8 files changed, 61 insertions(+), 68 deletions(-) diff --git a/cli/factory.rs b/cli/factory.rs index baf395bd5b01a8..6b80e3790cfbab 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -40,6 +40,7 @@ use crate::resolver::CliGraphResolver; use crate::resolver::CliGraphResolverOptions; use crate::standalone::DenoCompileBinaryWriter; use crate::tools::check::TypeChecker; +use crate::util::file_watcher::WatcherInterface; use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; use crate::worker::CliMainWorkerFactory; @@ -59,39 +60,24 @@ use deno_runtime::inspector_server::InspectorServer; use deno_semver::npm::NpmPackageReqReference; use import_map::ImportMap; use log::warn; -use std::cell::RefCell; use std::future::Future; -use std::path::PathBuf; use std::sync::Arc; use tokio::sync::broadcast::Receiver; pub struct CliFactoryBuilder { - maybe_watch_path_sender: - Option>>, - maybe_changed_path_receiver: - Option>>, - maybe_file_watcher_restart_sender: - Option>, + // TODO(bartlomieju): this is a bad name; change it + watcher_interface: Option, } impl CliFactoryBuilder { pub fn new() -> Self { Self { - maybe_watch_path_sender: None, - maybe_changed_path_receiver: None, - maybe_file_watcher_restart_sender: None, + watcher_interface: None, } } - pub fn with_watcher( - mut self, - sender: tokio::sync::mpsc::UnboundedSender>, - receiver: Option>>, - restart_sender: Option>, - ) -> Self { - self.maybe_watch_path_sender = Some(sender); - self.maybe_changed_path_receiver = receiver; - self.maybe_file_watcher_restart_sender = restart_sender; + pub fn with_watcher(mut self, watcher_interface: WatcherInterface) -> Self { + self.watcher_interface = Some(watcher_interface); self } @@ -104,9 +90,7 @@ impl CliFactoryBuilder { pub fn build_from_cli_options(self, options: Arc) -> CliFactory { CliFactory { - maybe_watch_path_sender: RefCell::new(self.maybe_watch_path_sender), - maybe_changed_path_receiver: self.maybe_changed_path_receiver, - maybe_file_watcher_restart_sender: self.maybe_file_watcher_restart_sender, + watcher_interface: self.watcher_interface, options, services: Default::default(), } @@ -182,12 +166,7 @@ struct CliFactoryServices { } pub struct CliFactory { - maybe_watch_path_sender: - RefCell>>>, - maybe_changed_path_receiver: - Option>>, - maybe_file_watcher_restart_sender: - Option>, + watcher_interface: Option, options: Arc, services: CliFactoryServices, } @@ -404,7 +383,11 @@ impl CliFactory { } pub fn maybe_file_watcher_reporter(&self) -> &Option { - let maybe_sender = self.maybe_watch_path_sender.borrow_mut().take(); + // TODO(bartlomieju): clean this up + let maybe_sender = self + .watcher_interface + .as_ref() + .map(|i| i.paths_to_watch_sender.clone()); self .services .maybe_file_watcher_reporter @@ -639,10 +622,14 @@ impl CliFactory { self.fs().clone(), Some(self.emitter()?.clone()), self - .maybe_changed_path_receiver + .watcher_interface .as_ref() + .and_then(|i| i.changed_paths_receiver.as_ref()) .map(Receiver::resubscribe), - self.maybe_file_watcher_restart_sender.clone(), + self + .watcher_interface + .as_ref() + .and_then(|i| i.restart_sender.clone()), self.maybe_inspector_server().clone(), self.maybe_lockfile().clone(), self.feature_checker().clone(), diff --git a/cli/tools/bench/mod.rs b/cli/tools/bench/mod.rs index 57f63747514692..b2a8984c0c27d5 100644 --- a/cli/tools/bench/mod.rs +++ b/cli/tools/bench/mod.rs @@ -417,11 +417,12 @@ pub async fn run_benchmarks_with_watch( .map(|w| !w.no_clear_screen) .unwrap_or(true), }, - move |flags, sender, changed_paths| { + move |flags, watcher_interface, changed_paths| { let bench_flags = bench_flags.clone(); + let sender = watcher_interface.paths_to_watch_sender.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(sender.clone(), None, None) + .with_watcher(watcher_interface) .build_from_flags(flags) .await?; let cli_options = factory.cli_options(); diff --git a/cli/tools/bundle.rs b/cli/tools/bundle.rs index d329063e4854d8..2228e3a402ebcb 100644 --- a/cli/tools/bundle.rs +++ b/cli/tools/bundle.rs @@ -35,11 +35,12 @@ pub async fn bundle( job_name: "Bundle".to_string(), clear_screen: !watch_flags.no_clear_screen, }, - move |flags, sender, _changed_paths| { + move |flags, watcher_interface, _changed_paths| { + let sender = watcher_interface.paths_to_watch_sender.clone(); let bundle_flags = bundle_flags.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(sender.clone(), None, None) + .with_watcher(watcher_interface) .build_from_flags(flags) .await?; let cli_options = factory.cli_options(); diff --git a/cli/tools/fmt.rs b/cli/tools/fmt.rs index 284f20ddaa44ee..285cf5cb23a41e 100644 --- a/cli/tools/fmt.rs +++ b/cli/tools/fmt.rs @@ -68,8 +68,9 @@ pub async fn format(flags: Flags, fmt_flags: FmtFlags) -> Result<(), AnyError> { job_name: "Fmt".to_string(), clear_screen: !watch_flags.no_clear_screen, }, - move |flags, sender, changed_paths| { + move |flags, watcher_interface, changed_paths| { let fmt_flags = fmt_flags.clone(); + let sender = watcher_interface.paths_to_watch_sender.clone(); Ok(async move { let factory = CliFactory::from_flags(flags).await?; let cli_options = factory.cli_options(); diff --git a/cli/tools/lint.rs b/cli/tools/lint.rs index 6a308b5990f4d9..ae22cb2f9a61a3 100644 --- a/cli/tools/lint.rs +++ b/cli/tools/lint.rs @@ -63,7 +63,7 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> { job_name: "Lint".to_string(), clear_screen: !watch_flags.no_clear_screen, }, - move |flags, sender, changed_paths| { + move |flags, watcher_interface, changed_paths| { let lint_flags = lint_flags.clone(); Ok(async move { let factory = CliFactory::from_flags(flags).await?; @@ -77,7 +77,7 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> { Ok(files) } })?; - _ = sender.send(files.clone()); + _ = watcher_interface.paths_to_watch_sender.send(files.clone()); let lint_paths = if let Some(paths) = changed_paths { // lint all files on any changed (https://github.com/denoland/deno/issues/12446) diff --git a/cli/tools/run/mod.rs b/cli/tools/run/mod.rs index 792714621e1bb1..071340adbb5a57 100644 --- a/cli/tools/run/mod.rs +++ b/cli/tools/run/mod.rs @@ -113,17 +113,11 @@ async fn run_with_watch( job_name: "Process".to_string(), clear_screen: false, }, - move |flags, - watch_path_sender, - watcher_restart_sender, - changed_path_receiver| { + move |flags, watcher_interface| { + let watch_path_sender = watcher_interface.paths_to_watch_sender.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher( - watch_path_sender.clone(), - Some(changed_path_receiver), - Some(watcher_restart_sender), - ) + .with_watcher(watcher_interface) .build_from_flags(flags) .await?; let cli_options = factory.cli_options(); @@ -156,10 +150,11 @@ async fn run_with_watch( job_name: "Process".to_string(), clear_screen: !watch_flags.no_clear_screen, }, - move |flags, sender, _changed_paths| { + move |flags, watcher_interface, _changed_paths| { + let sender = watcher_interface.paths_to_watch_sender.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(sender.clone(), None, None) + .with_watcher(watcher_interface) .build_from_flags(flags) .await?; let cli_options = factory.cli_options(); diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index 5ca8da6d79396b..6abdbe5bccdeec 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -1213,11 +1213,12 @@ pub async fn run_tests_with_watch( .map(|w| !w.no_clear_screen) .unwrap_or(true), }, - move |flags, sender, changed_paths| { + move |flags, watcher_interface, changed_paths| { let test_flags = test_flags.clone(); + let sender = watcher_interface.paths_to_watch_sender.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(sender.clone(), None, None) + .with_watcher(watcher_interface) .build_from_flags(flags) .await?; let cli_options = factory.cli_options(); diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index e034362b89525a..13639115e9b7de 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -23,7 +23,6 @@ use std::time::Duration; use tokio::select; use tokio::sync::mpsc; use tokio::sync::mpsc::UnboundedReceiver; -use tokio::sync::mpsc::UnboundedSender; use tokio::time::sleep; const CLEAR_SCREEN: &str = "\x1B[2J\x1B[1;1H"; @@ -109,6 +108,16 @@ fn create_print_after_restart_fn(clear_screen: bool) -> impl Fn() { } } +// TODO(bartlomieju): this is a poor name; change it +pub struct WatcherInterface { + pub paths_to_watch_sender: tokio::sync::mpsc::UnboundedSender>, + // TODO(bartlomieju): can we make it non-optional? + pub changed_paths_receiver: + Option>>, + // TODO(bartlomieju): can we make it non-optional? + pub restart_sender: Option>, +} + /// Creates a file watcher. /// /// - `operation` is the actual operation we want to run every time the watcher detects file @@ -120,11 +129,8 @@ pub async fn watch_func( mut operation: O, ) -> Result<(), AnyError> where - O: FnMut( - Flags, - UnboundedSender>, - Option>, - ) -> Result, + O: + FnMut(Flags, WatcherInterface, Option>) -> Result, F: Future>, { let (paths_to_watch_sender, mut paths_to_watch_receiver) = @@ -181,7 +187,11 @@ where }; let operation_future = error_handler(operation( flags.clone(), - paths_to_watch_sender.clone(), + WatcherInterface { + paths_to_watch_sender: paths_to_watch_sender.clone(), + changed_paths_receiver: None, + restart_sender: None, + }, changed_paths.take(), )?); @@ -238,12 +248,7 @@ pub async fn watch_recv( mut operation: O, ) -> Result where - O: FnMut( - Flags, - UnboundedSender>, - tokio::sync::mpsc::UnboundedSender<()>, - tokio::sync::broadcast::Receiver>, - ) -> Result, + O: FnMut(Flags, WatcherInterface) -> Result, F: Future>, { let (paths_to_watch_sender, mut paths_to_watch_receiver) = @@ -294,9 +299,11 @@ where let operation_future = error_handler(operation( flags.clone(), - paths_to_watch_sender.clone(), - watcher_restart_sender.clone(), - changed_paths_receiver, + WatcherInterface { + paths_to_watch_sender: paths_to_watch_sender.clone(), + changed_paths_receiver: Some(changed_paths_receiver), + restart_sender: Some(watcher_restart_sender.clone()), + }, )?); tokio::pin!(operation_future); From b56c1f5350761650739e5aa75be9dab7be65b3de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 17 Oct 2023 14:55:26 +0200 Subject: [PATCH 18/79] handle errors --- cli/factory.rs | 1 + cli/util/file_watcher.rs | 2 ++ cli/worker.rs | 2 +- foo.js | 3 --- foo.ts | 3 +++ main.js | 13 ++++++++++--- runtime/worker.rs | 19 +++++++++++++++++++ 7 files changed, 36 insertions(+), 7 deletions(-) delete mode 100644 foo.js create mode 100644 foo.ts diff --git a/cli/factory.rs b/cli/factory.rs index 6b80e3790cfbab..cecb6a133bf54d 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -76,6 +76,7 @@ impl CliFactoryBuilder { } } + // TODO(bartlomieju): change into `build_for_watcher` and accept flags. pub fn with_watcher(mut self, watcher_interface: WatcherInterface) -> Self { self.watcher_interface = Some(watcher_interface); self diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 13639115e9b7de..ff985a072e10e3 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -335,6 +335,8 @@ where "failed" } ); + let _ = watcher_receiver.recv().await; + break; }, }; } diff --git a/cli/worker.rs b/cli/worker.rs index bd965e118a027b..214430b421f2e0 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -168,7 +168,7 @@ impl CliMainWorker { if let Some(hot_reload_manager) = maybe_hot_reload_manager.as_mut() { self .worker - .with_event_loop(hot_reload_manager.run().boxed_local()) + .with_event_loop_fallible(hot_reload_manager.run().boxed_local()) .await?; } else { self diff --git a/foo.js b/foo.js deleted file mode 100644 index b8499df81974a9..00000000000000 --- a/foo.js +++ /dev/null @@ -1,3 +0,0 @@ -export function getFoo() { - return "foo123434"; // change this line -} diff --git a/foo.ts b/foo.ts new file mode 100644 index 00000000000000..075789b0eae9e9 --- /dev/null +++ b/foo.ts @@ -0,0 +1,3 @@ +export function getFoo() { + return "foo12asdfasf434"; // change this line +} diff --git a/main.js b/main.js index 3eab855ce56998..6e50c28c60ff2d 100644 --- a/main.js +++ b/main.js @@ -1,5 +1,12 @@ -import { getFoo } from "./foo.js"; +import { getFoo } from "./foo.ts"; -//// asdf let i = 0; -setInterval(() => console.log(i++, getFoo()), 1000); +setInterval(() => { + if (i === 5) { + // Uncaught exception isn't shown in the terminal and + // it breaks watch + hmr + console.log("Before 123throw"); + throw new Error("fail"); + } + console.log(i++, getFoo()); +}, 250); diff --git a/runtime/worker.rs b/runtime/worker.rs index e8d9ca6bcccab5..07e2cb7a29684e 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -523,6 +523,25 @@ impl MainWorker { } } + pub async fn with_event_loop_fallible<'a>( + &mut self, + mut fut: Pin> + 'a>>, + ) -> Result<(), AnyError> { + loop { + tokio::select! { + biased; + result = &mut fut => { + return result; + } + r = self.run_event_loop(false) => { + if r.is_err() { + return r; + } + } + }; + } + } + /// Return exit code set by the executed code (either in main worker /// or one of child web workers). pub fn exit_code(&self) -> i32 { From 4df3cae9b908b040032f0226977772e7974c6b63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 17 Oct 2023 16:40:39 +0200 Subject: [PATCH 19/79] try to debug rejections --- Cargo.toml | 1 + cli/tools/run/hot_reload/mod.rs | 5 ++++- cli/tools/run/mod.rs | 4 +++- cli/worker.rs | 2 ++ main.js | 20 +++++++++++++------- runtime/worker.rs | 2 ++ 6 files changed, 25 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f5b02d6a468974..58be7a876ae06f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -334,3 +334,4 @@ opt-level = 3 [patch.crates-io] deno_core = { git = "https://github.com/bartlomieju/deno_core.git", branch = "hmr" } +# deno_core = { path = "../deno_core/core" } diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 25e008101400a5..c8c283ea67e7f8 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -64,6 +64,7 @@ impl HotReloadManager { // TODO(SyrupThinker): Deferred retry with timeout Some(notification) = session_rx.next() => { let notification = serde_json::from_value::(notification)?; + eprintln!("notification {:?}", notification.method); if notification.method == "Debugger.scriptParsed" { let params = serde_json::from_value::(notification.params)?; if params.url.starts_with("file://") { @@ -130,7 +131,9 @@ impl HotReloadManager { } } } - _ = self.session.receive_from_v8_session() => {} + _ = self.session.receive_from_v8_session() => { + eprintln!("receive from v8 session"); + } } } } diff --git a/cli/tools/run/mod.rs b/cli/tools/run/mod.rs index 071340adbb5a57..62d56122ca215d 100644 --- a/cli/tools/run/mod.rs +++ b/cli/tools/run/mod.rs @@ -136,7 +136,9 @@ async fn run_with_watch( .create_main_worker(main_module, permissions) .await?; - worker.run().await?; + let r = worker.run().await; + eprintln!("worker run result {:#?}", r); + r?; Ok(()) }) diff --git a/cli/worker.rs b/cli/worker.rs index 214430b421f2e0..913aa6da569d99 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -166,10 +166,12 @@ impl CliMainWorker { loop { if let Some(hot_reload_manager) = maybe_hot_reload_manager.as_mut() { + eprintln!("before event loop"); self .worker .with_event_loop_fallible(hot_reload_manager.run().boxed_local()) .await?; + eprintln!("after event loop"); } else { self .worker diff --git a/main.js b/main.js index 6e50c28c60ff2d..f504f233bb688e 100644 --- a/main.js +++ b/main.js @@ -1,12 +1,18 @@ import { getFoo } from "./foo.ts"; -let i = 0; -setInterval(() => { +async function bar() { + throw new Error("fail2"); +} + +let i = 1; +setInterval(async () => { if (i === 5) { - // Uncaught exception isn't shown in the terminal and - // it breaks watch + hmr - console.log("Before 123throw"); - throw new Error("fail"); + // unhandled promise rejection is not shown + await bar(); } console.log(i++, getFoo()); -}, 250); +}, 100); + +// addEventListener("unhandledrejection", (e) => { +// console.log("unhandledrejection", e.reason); +// }); diff --git a/runtime/worker.rs b/runtime/worker.rs index 07e2cb7a29684e..7b3a0920e341ef 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -531,9 +531,11 @@ impl MainWorker { tokio::select! { biased; result = &mut fut => { + eprintln!("result {:#?}", result); return result; } r = self.run_event_loop(false) => { + eprintln!("event loop result {:#?}", r); if r.is_err() { return r; } From c9f7bd0a4d37092b8164002d772ecd30136e032b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 17 Oct 2023 18:02:39 +0200 Subject: [PATCH 20/79] watcher refactor --- cli/factory.rs | 22 ++++++++++++---------- cli/tools/bench/mod.rs | 3 +-- cli/tools/bundle.rs | 3 +-- cli/tools/run/mod.rs | 6 ++---- cli/tools/test/mod.rs | 3 +-- 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/cli/factory.rs b/cli/factory.rs index cecb6a133bf54d..410548e976d741 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -76,12 +76,6 @@ impl CliFactoryBuilder { } } - // TODO(bartlomieju): change into `build_for_watcher` and accept flags. - pub fn with_watcher(mut self, watcher_interface: WatcherInterface) -> Self { - self.watcher_interface = Some(watcher_interface); - self - } - pub async fn build_from_flags( self, flags: Flags, @@ -89,6 +83,15 @@ impl CliFactoryBuilder { Ok(self.build_from_cli_options(Arc::new(CliOptions::from_flags(flags)?))) } + pub async fn build_from_flags_for_watcher( + mut self, + flags: Flags, + watcher_interface: WatcherInterface, + ) -> Result { + self.watcher_interface = Some(watcher_interface); + self.build_from_flags(flags).await + } + pub fn build_from_cli_options(self, options: Arc) -> CliFactory { CliFactory { watcher_interface: self.watcher_interface, @@ -384,15 +387,14 @@ impl CliFactory { } pub fn maybe_file_watcher_reporter(&self) -> &Option { - // TODO(bartlomieju): clean this up - let maybe_sender = self + let maybe_file_watcher_reporter = self .watcher_interface .as_ref() - .map(|i| i.paths_to_watch_sender.clone()); + .map(|i| FileWatcherReporter::new(i.paths_to_watch_sender.clone())); self .services .maybe_file_watcher_reporter - .get_or_init(|| maybe_sender.map(FileWatcherReporter::new)) + .get_or_init(|| maybe_file_watcher_reporter) } pub fn emit_cache(&self) -> Result<&EmitCache, AnyError> { diff --git a/cli/tools/bench/mod.rs b/cli/tools/bench/mod.rs index b2a8984c0c27d5..92132cb90289b4 100644 --- a/cli/tools/bench/mod.rs +++ b/cli/tools/bench/mod.rs @@ -422,8 +422,7 @@ pub async fn run_benchmarks_with_watch( let sender = watcher_interface.paths_to_watch_sender.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(watcher_interface) - .build_from_flags(flags) + .build_from_flags_for_watcher(flags, watcher_interface) .await?; let cli_options = factory.cli_options(); let bench_options = cli_options.resolve_bench_options(bench_flags)?; diff --git a/cli/tools/bundle.rs b/cli/tools/bundle.rs index 2228e3a402ebcb..668d8d2d5c2561 100644 --- a/cli/tools/bundle.rs +++ b/cli/tools/bundle.rs @@ -40,8 +40,7 @@ pub async fn bundle( let bundle_flags = bundle_flags.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(watcher_interface) - .build_from_flags(flags) + .build_from_flags_for_watcher(flags, watcher_interface) .await?; let cli_options = factory.cli_options(); let _ = sender.send(cli_options.watch_paths()); diff --git a/cli/tools/run/mod.rs b/cli/tools/run/mod.rs index 62d56122ca215d..48121dec4fd6c1 100644 --- a/cli/tools/run/mod.rs +++ b/cli/tools/run/mod.rs @@ -117,8 +117,7 @@ async fn run_with_watch( let watch_path_sender = watcher_interface.paths_to_watch_sender.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(watcher_interface) - .build_from_flags(flags) + .build_from_flags_for_watcher(flags, watcher_interface) .await?; let cli_options = factory.cli_options(); let main_module = cli_options.resolve_main_module()?; @@ -156,8 +155,7 @@ async fn run_with_watch( let sender = watcher_interface.paths_to_watch_sender.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(watcher_interface) - .build_from_flags(flags) + .build_from_flags_for_watcher(flags, watcher_interface) .await?; let cli_options = factory.cli_options(); let main_module = cli_options.resolve_main_module()?; diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index 6abdbe5bccdeec..a9fca78bcbc428 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -1218,8 +1218,7 @@ pub async fn run_tests_with_watch( let sender = watcher_interface.paths_to_watch_sender.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(watcher_interface) - .build_from_flags(flags) + .build_from_flags_for_watcher(flags, watcher_interface) .await?; let cli_options = factory.cli_options(); let test_options = cli_options.resolve_test_options(test_flags)?; From 4afa6ab7633ad2bd029e8ef1e75fad60641c130d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 17 Oct 2023 18:12:08 +0200 Subject: [PATCH 21/79] flags test --- cli/args/flags.rs | 118 +++++++++++++++++++++++++++++++--------------- 1 file changed, 81 insertions(+), 37 deletions(-) diff --git a/cli/args/flags.rs b/cli/args/flags.rs index 44cf83f7409ada..b81ab0eddbcdd8 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -3843,11 +3843,6 @@ fn watch_arg_parse(matches: &mut ArgMatches) -> Option { hot_reload: false, no_clear_screen: matches.get_flag("no-clear-screen"), }) - } else if matches.get_flag("hmr") { - Some(WatchFlags { - hot_reload: true, - no_clear_screen: matches.get_flag("no-clear-screen"), - }) } else { None } @@ -3994,6 +3989,79 @@ mod tests { ..Flags::default() } ); + + let r = flags_from_vec(svec![ + "deno", + "run", + "--watch", + "--no-clear-screen", + "script.ts" + ]); + let flags = r.unwrap(); + assert_eq!( + flags, + Flags { + subcommand: DenoSubcommand::Run(RunFlags { + script: "script.ts".to_string(), + watch: Some(WatchFlagsWithPaths { + hot_reload: false, + paths: vec![], + no_clear_screen: true, + }), + }), + ..Flags::default() + } + ); + + let r = flags_from_vec(svec![ + "deno", + "run", + "--hmr", + "--no-clear-screen", + "script.ts" + ]); + let flags = r.unwrap(); + assert_eq!( + flags, + Flags { + subcommand: DenoSubcommand::Run(RunFlags { + script: "script.ts".to_string(), + watch: Some(WatchFlagsWithPaths { + hot_reload: true, + paths: vec![], + no_clear_screen: true, + }), + }), + ..Flags::default() + } + ); + + let r = flags_from_vec(svec![ + "deno", + "run", + "--hmr=foo.txt", + "--no-clear-screen", + "script.ts" + ]); + let flags = r.unwrap(); + assert_eq!( + flags, + Flags { + subcommand: DenoSubcommand::Run(RunFlags { + script: "script.ts".to_string(), + watch: Some(WatchFlagsWithPaths { + hot_reload: true, + paths: vec![PathBuf::from("foo.txt")], + no_clear_screen: true, + }), + }), + ..Flags::default() + } + ); + + let r = + flags_from_vec(svec!["deno", "run", "--hmr", "--watch", "script.ts"]); + assert!(r.is_err()); } #[test] @@ -4356,10 +4424,7 @@ mod tests { single_quote: None, prose_wrap: None, no_semicolons: None, - watch: Some(WatchFlags { - hot_reload: false, - no_clear_screen: false, - }) + watch: Some(Default::default()), }), ext: Some("ts".to_string()), ..Flags::default() @@ -4416,10 +4481,7 @@ mod tests { single_quote: None, prose_wrap: None, no_semicolons: None, - watch: Some(WatchFlags { - hot_reload: false, - no_clear_screen: false, - }) + watch: Some(Default::default()), }), ext: Some("ts".to_string()), ..Flags::default() @@ -4473,10 +4535,7 @@ mod tests { single_quote: None, prose_wrap: None, no_semicolons: None, - watch: Some(WatchFlags { - hot_reload: false, - no_clear_screen: false, - }) + watch: Some(Default::default()), }), config_flag: ConfigFlag::Path("deno.jsonc".to_string()), ext: Some("ts".to_string()), @@ -4600,10 +4659,7 @@ mod tests { maybe_rules_exclude: None, json: false, compact: false, - watch: Some(WatchFlags { - hot_reload: false, - no_clear_screen: false, - }) + watch: Some(Default::default()), }), ..Flags::default() } @@ -5838,10 +5894,7 @@ mod tests { subcommand: DenoSubcommand::Bundle(BundleFlags { source_file: "source.ts".to_string(), out_file: None, - watch: Some(WatchFlags { - hot_reload: false, - no_clear_screen: false, - }), + watch: Some(Default::default()), }), type_check_mode: TypeCheckMode::Local, ..Flags::default() @@ -7034,10 +7087,7 @@ mod tests { concurrent_jobs: None, trace_ops: false, coverage_dir: None, - watch: Some(WatchFlags { - hot_reload: false, - no_clear_screen: false, - }), + watch: Some(Default::default()), reporter: Default::default(), junit_path: None, }), @@ -7067,10 +7117,7 @@ mod tests { concurrent_jobs: None, trace_ops: false, coverage_dir: None, - watch: Some(WatchFlags { - hot_reload: false, - no_clear_screen: false, - }), + watch: Some(Default::default()), reporter: Default::default(), junit_path: None, }), @@ -7813,10 +7860,7 @@ mod tests { include: vec![], ignore: vec![], }, - watch: Some(WatchFlags { - hot_reload: false, - no_clear_screen: false, - }), + watch: Some(Default::default()), }), no_prompt: true, type_check_mode: TypeCheckMode::Local, From 417e4555e5dbb82887e31a7a69f151a6ed23d3f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 17 Oct 2023 19:14:15 +0200 Subject: [PATCH 22/79] flatten a function --- cli/worker.rs | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/cli/worker.rs b/cli/worker.rs index 913aa6da569d99..a0e168039f470f 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -322,29 +322,33 @@ impl CliMainWorker { return Ok(None); } - if let Some(receiver) = self + // TODO(bartlomieju): would be so much nicer if we could clone a + // `WatcherInterface` here + let receiver = self .shared .maybe_changed_path_receiver .as_ref() .map(Receiver::resubscribe) - { - let restart_sender = self - .shared - .maybe_file_watcher_restart_sender - .clone() - .unwrap(); - let emitter = self.shared.emitter.clone().unwrap(); - let session = self.worker.create_inspector_session().await; - let mut hot_reload_manager = - HotReloadManager::new(emitter, session, receiver, restart_sender); - self - .worker - .with_event_loop(hot_reload_manager.start().boxed_local()) - .await?; - Ok(Some(hot_reload_manager)) - } else { - Ok(None) - } + .unwrap(); + let restart_sender = self + .shared + .maybe_file_watcher_restart_sender + .clone() + .unwrap(); + // TODO(bartlomieju): this is a code smell, refactor so we don't have + // to pass `emitter` here + let emitter = self.shared.emitter.clone().unwrap(); + + let session = self.worker.create_inspector_session().await; + let mut hot_reload_manager = + HotReloadManager::new(emitter, session, receiver, restart_sender); + + self + .worker + .with_event_loop(hot_reload_manager.start().boxed_local()) + .await?; + + Ok(Some(hot_reload_manager)) } pub fn execute_script_static( From 13022efd818c6342d59deaed3b63dee3f76cd117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 17 Oct 2023 19:19:09 +0200 Subject: [PATCH 23/79] make restart watcher required --- cli/factory.rs | 2 +- cli/util/file_watcher.rs | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/cli/factory.rs b/cli/factory.rs index 410548e976d741..665ec1e02c18b0 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -632,7 +632,7 @@ impl CliFactory { self .watcher_interface .as_ref() - .and_then(|i| i.restart_sender.clone()), + .and_then(|i| Some(i.restart_sender.clone())), self.maybe_inspector_server().clone(), self.maybe_lockfile().clone(), self.feature_checker().clone(), diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index ff985a072e10e3..df9dda39b81796 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -114,8 +114,7 @@ pub struct WatcherInterface { // TODO(bartlomieju): can we make it non-optional? pub changed_paths_receiver: Option>>, - // TODO(bartlomieju): can we make it non-optional? - pub restart_sender: Option>, + pub restart_sender: tokio::sync::mpsc::UnboundedSender<()>, } /// Creates a file watcher. @@ -135,6 +134,8 @@ where { let (paths_to_watch_sender, mut paths_to_watch_receiver) = tokio::sync::mpsc::unbounded_channel(); + let (watcher_restart_sender, mut watcher_restart_receiver) = + tokio::sync::mpsc::unbounded_channel(); let (watcher_sender, mut watcher_receiver) = DebouncedReceiver::new_with_sender(); @@ -190,7 +191,7 @@ where WatcherInterface { paths_to_watch_sender: paths_to_watch_sender.clone(), changed_paths_receiver: None, - restart_sender: None, + restart_sender: watcher_restart_sender.clone(), }, changed_paths.take(), )?); @@ -200,6 +201,10 @@ where select! { _ = receiver_future => {}, + _ = watcher_restart_receiver.recv() => { + print_after_restart(); + continue; + }, received_changed_paths = watcher_receiver.recv() => { print_after_restart(); changed_paths = received_changed_paths; @@ -302,7 +307,7 @@ where WatcherInterface { paths_to_watch_sender: paths_to_watch_sender.clone(), changed_paths_receiver: Some(changed_paths_receiver), - restart_sender: Some(watcher_restart_sender.clone()), + restart_sender: watcher_restart_sender.clone(), }, )?); tokio::pin!(operation_future); From 62e49426d162b1b6dbaa82ea2240df99a46094d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 17 Oct 2023 19:27:46 +0200 Subject: [PATCH 24/79] simplify passing channels --- cli/factory.rs | 29 ++++++++++++++-------- cli/standalone/mod.rs | 1 - cli/tools/run/hot_reload/mod.rs | 22 ++++++++++++++--- cli/worker.rs | 44 ++++++++++++++------------------- 4 files changed, 56 insertions(+), 40 deletions(-) diff --git a/cli/factory.rs b/cli/factory.rs index 665ec1e02c18b0..964087640d0225 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -40,6 +40,7 @@ use crate::resolver::CliGraphResolver; use crate::resolver::CliGraphResolverOptions; use crate::standalone::DenoCompileBinaryWriter; use crate::tools::check::TypeChecker; +use crate::tools::run::hot_reload::HotReloadInterface; use crate::util::file_watcher::WatcherInterface; use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; @@ -62,7 +63,6 @@ use import_map::ImportMap; use log::warn; use std::future::Future; use std::sync::Arc; -use tokio::sync::broadcast::Receiver; pub struct CliFactoryBuilder { // TODO(bartlomieju): this is a bad name; change it @@ -601,6 +601,23 @@ impl CliFactory { let npm_resolver = self.npm_resolver().await?; let fs = self.fs(); let cli_node_resolver = self.cli_node_resolver().await?; + + // TODO(bartlomieju): can we just clone `WatcherInterface` here? + let maybe_hot_reload_interface = self + .watcher_interface + .as_ref() + .map(|i| { + if let Some(receiver) = i.changed_paths_receiver.as_ref() { + Some(HotReloadInterface { + path_change_receiver: receiver.resubscribe(), + file_watcher_restart_sender: i.restart_sender.clone(), + }) + } else { + None + } + }) + .flatten(); + Ok(CliMainWorkerFactory::new( StorageKeyResolver::from_options(&self.options), npm_resolver.clone(), @@ -624,15 +641,7 @@ impl CliFactory { self.root_cert_store_provider().clone(), self.fs().clone(), Some(self.emitter()?.clone()), - self - .watcher_interface - .as_ref() - .and_then(|i| i.changed_paths_receiver.as_ref()) - .map(Receiver::resubscribe), - self - .watcher_interface - .as_ref() - .and_then(|i| Some(i.restart_sender.clone())), + maybe_hot_reload_interface, self.maybe_inspector_server().clone(), self.maybe_lockfile().clone(), self.feature_checker().clone(), diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index cc86fb815995b3..99efb7b7d12c81 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -448,7 +448,6 @@ pub async fn run( None, None, None, - None, feature_checker, CliMainWorkerOptions { argv: metadata.argv, diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index c8c283ea67e7f8..94deab1942095c 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -21,6 +21,21 @@ use json_types::ScriptParsed; use json_types::SetScriptSourceReturnObject; use json_types::Status; +// TODO(bartlomieju): this is a poor name; change it +pub struct HotReloadInterface { + pub path_change_receiver: tokio::sync::broadcast::Receiver>, + pub file_watcher_restart_sender: tokio::sync::mpsc::UnboundedSender<()>, +} + +impl Clone for HotReloadInterface { + fn clone(&self) -> Self { + Self { + path_change_receiver: self.path_change_receiver.resubscribe(), + file_watcher_restart_sender: self.file_watcher_restart_sender.clone(), + } + } +} + pub struct HotReloadManager { session: LocalInspectorSession, path_change_receiver: tokio::sync::broadcast::Receiver>, @@ -33,14 +48,13 @@ impl HotReloadManager { pub fn new( emitter: Arc, session: LocalInspectorSession, - path_change_receiver: tokio::sync::broadcast::Receiver>, - file_watcher_restart_sender: tokio::sync::mpsc::UnboundedSender<()>, + interface: HotReloadInterface, ) -> Self { Self { session, emitter, - path_change_receiver, - file_watcher_restart_sender, + path_change_receiver: interface.path_change_receiver, + file_watcher_restart_sender: interface.file_watcher_restart_sender, script_ids: HashMap::new(), } } diff --git a/cli/worker.rs b/cli/worker.rs index a0e168039f470f..e1584fdf7d8637 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -43,7 +43,6 @@ use deno_runtime::BootstrapOptions; use deno_runtime::WorkerLogLevel; use deno_semver::npm::NpmPackageReqReference; use deno_semver::package::PackageReqReference; -use tokio::sync::broadcast::Receiver; use crate::args::package_json::PackageJsonDeps; use crate::args::StorageKeyResolver; @@ -53,6 +52,7 @@ use crate::npm::CliNpmResolver; use crate::ops; use crate::tools; use crate::tools::coverage::CoverageCollector; +use crate::tools::run::hot_reload::HotReloadInterface; use crate::tools::run::hot_reload::HotReloadManager; use crate::util::checksum; use crate::version; @@ -113,10 +113,7 @@ struct SharedWorkerState { root_cert_store_provider: Arc, fs: Arc, emitter: Option>, - maybe_changed_path_receiver: - Option>>, - maybe_file_watcher_restart_sender: - Option>, + maybe_hot_reload_interface: Option, maybe_inspector_server: Option>, maybe_lockfile: Option>>, feature_checker: Arc, @@ -324,24 +321,27 @@ impl CliMainWorker { // TODO(bartlomieju): would be so much nicer if we could clone a // `WatcherInterface` here - let receiver = self - .shared - .maybe_changed_path_receiver - .as_ref() - .map(Receiver::resubscribe) - .unwrap(); - let restart_sender = self - .shared - .maybe_file_watcher_restart_sender - .clone() - .unwrap(); + let interface = self.shared.maybe_hot_reload_interface.clone().unwrap(); + + // let receiver = self + // .shared + // .maybe_changed_path_receiver + // .as_ref() + // .map(Receiver::resubscribe) + // .unwrap(); + // let restart_sender = self + // .shared + // .maybe_file_watcher_restart_sender + // .clone() + // .unwrap(); + // TODO(bartlomieju): this is a code smell, refactor so we don't have // to pass `emitter` here let emitter = self.shared.emitter.clone().unwrap(); let session = self.worker.create_inspector_session().await; let mut hot_reload_manager = - HotReloadManager::new(emitter, session, receiver, restart_sender); + HotReloadManager::new(emitter, session, interface); self .worker @@ -378,12 +378,7 @@ impl CliMainWorkerFactory { root_cert_store_provider: Arc, fs: Arc, emitter: Option>, - maybe_changed_path_receiver: Option< - tokio::sync::broadcast::Receiver>, - >, - maybe_file_watcher_restart_sender: Option< - tokio::sync::mpsc::UnboundedSender<()>, - >, + maybe_hot_reload_interface: Option, maybe_inspector_server: Option>, maybe_lockfile: Option>>, feature_checker: Arc, @@ -403,8 +398,7 @@ impl CliMainWorkerFactory { root_cert_store_provider, emitter, fs, - maybe_changed_path_receiver, - maybe_file_watcher_restart_sender, + maybe_hot_reload_interface, maybe_inspector_server, maybe_lockfile, feature_checker, From 3079c91b57730cb356fe62742039f663f6de7cef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 17 Oct 2023 22:29:22 +0200 Subject: [PATCH 25/79] debug --- cli/tools/run/hot_reload/mod.rs | 8 ++++++++ cli/worker.rs | 12 ------------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 94deab1942095c..6eafff141a389b 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -158,6 +158,10 @@ impl HotReloadManager { .session .post_message::<()>("Debugger.enable", None) .await?; + self + .session + .post_message::<()>("Runtime.enable", None) + .await?; Ok(()) } @@ -167,6 +171,10 @@ impl HotReloadManager { .session .post_message::<()>("Debugger.disable", None) .await?; + self + .session + .post_message::<()>("Runtime.disable", None) + .await?; Ok(()) } diff --git a/cli/worker.rs b/cli/worker.rs index e1584fdf7d8637..c810e63c5c850f 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -323,18 +323,6 @@ impl CliMainWorker { // `WatcherInterface` here let interface = self.shared.maybe_hot_reload_interface.clone().unwrap(); - // let receiver = self - // .shared - // .maybe_changed_path_receiver - // .as_ref() - // .map(Receiver::resubscribe) - // .unwrap(); - // let restart_sender = self - // .shared - // .maybe_file_watcher_restart_sender - // .clone() - // .unwrap(); - // TODO(bartlomieju): this is a code smell, refactor so we don't have // to pass `emitter` here let emitter = self.shared.emitter.clone().unwrap(); From 524ba869986975a3fa0a6b4e2d170191e8b8e36d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 17 Oct 2023 22:59:40 +0200 Subject: [PATCH 26/79] handle unhandled rejections --- cli/tools/run/hot_reload/mod.rs | 14 ++++++++++---- cli/tools/run/mod.rs | 2 +- cli/worker.rs | 2 -- main.js | 3 ++- runtime/worker.rs | 2 -- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 6eafff141a389b..29fc3074b2943c 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -78,12 +78,20 @@ impl HotReloadManager { // TODO(SyrupThinker): Deferred retry with timeout Some(notification) = session_rx.next() => { let notification = serde_json::from_value::(notification)?; - eprintln!("notification {:?}", notification.method); if notification.method == "Debugger.scriptParsed" { let params = serde_json::from_value::(notification.params)?; if params.url.starts_with("file://") { self.script_ids.insert(params.url, params.script_id); } + // TODO(bartlomieju): this is not great... and the code is duplicated with the REPL. + } else if notification.method == "Runtime.exceptionThrown" { + let params = notification.params; + let exception_details = params.get("exceptionDetails").unwrap().as_object().unwrap(); + let text = exception_details.get("text").unwrap().as_str().unwrap(); + let exception = exception_details.get("exception").unwrap().as_object().unwrap(); + let description = exception.get("description").and_then(|d| d.as_str()).unwrap_or("undefined"); + println!("{text} {description}"); + break Ok(()); } } changed_paths = self.path_change_receiver.recv() => { @@ -145,9 +153,7 @@ impl HotReloadManager { } } } - _ = self.session.receive_from_v8_session() => { - eprintln!("receive from v8 session"); - } + _ = self.session.receive_from_v8_session() => {} } } } diff --git a/cli/tools/run/mod.rs b/cli/tools/run/mod.rs index 48121dec4fd6c1..a471ff69149943 100644 --- a/cli/tools/run/mod.rs +++ b/cli/tools/run/mod.rs @@ -136,7 +136,7 @@ async fn run_with_watch( .await?; let r = worker.run().await; - eprintln!("worker run result {:#?}", r); + // eprintln!("worker run result {:#?}", r); r?; Ok(()) diff --git a/cli/worker.rs b/cli/worker.rs index c810e63c5c850f..9e465dc21bc5f1 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -163,12 +163,10 @@ impl CliMainWorker { loop { if let Some(hot_reload_manager) = maybe_hot_reload_manager.as_mut() { - eprintln!("before event loop"); self .worker .with_event_loop_fallible(hot_reload_manager.run().boxed_local()) .await?; - eprintln!("after event loop"); } else { self .worker diff --git a/main.js b/main.js index f504f233bb688e..577457d9157918 100644 --- a/main.js +++ b/main.js @@ -1,7 +1,7 @@ import { getFoo } from "./foo.ts"; async function bar() { - throw new Error("fail2"); + throw new Error("fail"); } let i = 1; @@ -15,4 +15,5 @@ setInterval(async () => { // addEventListener("unhandledrejection", (e) => { // console.log("unhandledrejection", e.reason); +// e.preventDefault(); // }); diff --git a/runtime/worker.rs b/runtime/worker.rs index 7b3a0920e341ef..07e2cb7a29684e 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -531,11 +531,9 @@ impl MainWorker { tokio::select! { biased; result = &mut fut => { - eprintln!("result {:#?}", result); return result; } r = self.run_event_loop(false) => { - eprintln!("event loop result {:#?}", r); if r.is_err() { return r; } From 00822222a72462d1b8ca2a754380cb52965d449b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 17 Oct 2023 23:06:26 +0200 Subject: [PATCH 27/79] nicer error --- cli/tools/run/hot_reload/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 29fc3074b2943c..5aa1810cfc492f 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -2,6 +2,7 @@ use crate::emit::Emitter; use deno_ast::MediaType; +use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::futures::StreamExt; use deno_core::serde_json::json; @@ -90,8 +91,7 @@ impl HotReloadManager { let text = exception_details.get("text").unwrap().as_str().unwrap(); let exception = exception_details.get("exception").unwrap().as_object().unwrap(); let description = exception.get("description").and_then(|d| d.as_str()).unwrap_or("undefined"); - println!("{text} {description}"); - break Ok(()); + break Err(generic_error(format!("{text} {description}"))); } } changed_paths = self.path_change_receiver.recv() => { From 56ccc3e37ed5230f191fa6fe312155065f3b92db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 01:32:50 +0200 Subject: [PATCH 28/79] more unification! --- cli/util/file_watcher.rs | 176 +++++++++++++++++++-------------------- main2.js | 1 + server.ts | 2 +- 3 files changed, 88 insertions(+), 91 deletions(-) create mode 100644 main2.js diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index df9dda39b81796..b1b46f67a7b85a 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -148,26 +148,6 @@ where info!("{} {} started.", colors::intense_blue("Watcher"), job_name,); - fn consume_paths_to_watch( - watcher: &mut RecommendedWatcher, - receiver: &mut UnboundedReceiver>, - ) { - loop { - match receiver.try_recv() { - Ok(paths) => { - add_paths_to_watcher(watcher, &paths); - } - Err(e) => match e { - mpsc::error::TryRecvError::Empty => { - break; - } - // there must be at least one receiver alive - _ => unreachable!(), - }, - } - } - } - let mut changed_paths = None; loop { // We may need to give the runtime a tick to settle, as cancellations may need to propagate @@ -267,27 +247,6 @@ where info!("{} {} started.", colors::intense_blue("HMR"), job_name,); - fn consume_paths_to_watch( - watcher: &mut RecommendedWatcher, - receiver: &mut UnboundedReceiver>, - ) { - loop { - match receiver.try_recv() { - Ok(paths) => { - // eprintln!("add paths to watch {:#?}", paths); - add_paths_to_watcher(watcher, &paths); - } - Err(e) => match e { - mpsc::error::TryRecvError::Empty => { - break; - } - // there must be at least one receiver alive - _ => unreachable!(), - }, - } - } - } - 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 @@ -302,6 +261,12 @@ where let (changed_paths_sender, changed_paths_receiver) = tokio::sync::broadcast::channel(4); + let receiver_future = async { + loop { + let maybe_paths = paths_to_watch_receiver.recv().await; + add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); + } + }; let operation_future = error_handler(operation( flags.clone(), WatcherInterface { @@ -310,67 +275,78 @@ where restart_sender: watcher_restart_sender.clone(), }, )?); - tokio::pin!(operation_future); - loop { - select! { - maybe_paths = paths_to_watch_receiver.recv() => { - // eprintln!("add paths to watcher {:#?}", maybe_paths); - add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); - }, - received_changed_paths = watcher_receiver.recv() => { - // eprintln!("changed paths for watcher {:#?}", received_changed_paths); - if let Some(changed_paths) = received_changed_paths { - changed_paths_sender.send(changed_paths)?; + // TODO(bartlomieju): this is almost identical with `watch_func`, besides + // `received_changed_paths` arm - figure out how to remove it. + select! { + _ = receiver_future => {}, + received_changed_paths = watcher_receiver.recv() => { + // eprintln!("changed paths for watcher {:#?}", received_changed_paths); + if let Some(changed_paths) = received_changed_paths { + changed_paths_sender.send(changed_paths)?; + } + }, + _ = watcher_restart_receiver.recv() => { + continue; + }, + success = operation_future => { + consume_paths_to_watch(&mut watcher, &mut paths_to_watch_receiver); + // TODO(bartlomieju): print exit code here? + info!( + "{} {} {}. Restarting on file change...", + colors::intense_blue("Watcher"), + job_name, + if success { + "finished" + } else { + "failed" } - }, - _ = watcher_restart_receiver.recv() => { - // drop(operation_future); - break; - }, - success = &mut operation_future => { - // TODO(bartlomieju): print exit code here? - info!( - "{} {} {}. Restarting on file change...", - colors::intense_blue("Watcher"), - job_name, - if success { - "finished" - } else { - "failed" - } - ); - let _ = watcher_receiver.recv().await; - break; - }, - }; - } + ); + }, + }; + + let receiver_future = async { + loop { + let maybe_paths = paths_to_watch_receiver.recv().await; + add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); + } + }; + select! { + _ = receiver_future => {}, + _received_changed_paths = watcher_receiver.recv() => { + // print_after_restart(); + // changed_paths = received_changed_paths; + continue; + }, + }; } } fn new_watcher( sender: Arc>>, ) -> Result { - let watcher = Watcher::new( + Ok(Watcher::new( move |res: Result| { - if let Ok(event) = res { - if matches!( - event.kind, - EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_) - ) { - let paths = event - .paths - .iter() - .filter_map(|path| canonicalize_path(path).ok()) - .collect(); - sender.send(paths).unwrap(); - } + let Ok(event) = res else { + return; + }; + + if !matches!( + event.kind, + EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_) + ) { + return; } + + let paths = event + .paths + .iter() + .filter_map(|path| canonicalize_path(path).ok()) + .collect(); + sender.send(paths).unwrap(); }, Default::default(), - )?; - - Ok(watcher) + )?) } fn add_paths_to_watcher(watcher: &mut RecommendedWatcher, paths: &[PathBuf]) { @@ -380,3 +356,23 @@ fn add_paths_to_watcher(watcher: &mut RecommendedWatcher, paths: &[PathBuf]) { } log::debug!("Watching paths: {:?}", paths); } + +fn consume_paths_to_watch( + watcher: &mut RecommendedWatcher, + receiver: &mut UnboundedReceiver>, +) { + loop { + match receiver.try_recv() { + Ok(paths) => { + add_paths_to_watcher(watcher, &paths); + } + Err(e) => match e { + mpsc::error::TryRecvError::Empty => { + break; + } + // there must be at least one receiver alive + _ => unreachable!(), + }, + } + } +} diff --git a/main2.js b/main2.js new file mode 100644 index 00000000000000..7a412448e9269c --- /dev/null +++ b/main2.js @@ -0,0 +1 @@ +console.log("hello world123"); diff --git a/server.ts b/server.ts index 155236df56237b..0678d675793528 100644 --- a/server.ts +++ b/server.ts @@ -5,7 +5,7 @@ function bar() { function handler(req) { // console.log("req123", req); - return new Response("Hello world!123"); + return new Response("Hello world!1234"); } Deno.serve(handler); From 62eac1c3a8b90642abbab7c9ee651115031bbf44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 01:34:36 +0200 Subject: [PATCH 29/79] channel creation hoised out of the loop --- cli/util/file_watcher.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index b1b46f67a7b85a..4f0869084393f1 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -240,6 +240,8 @@ where tokio::sync::mpsc::unbounded_channel(); let (watcher_restart_sender, mut watcher_restart_receiver) = tokio::sync::mpsc::unbounded_channel(); + let (changed_paths_sender, changed_paths_receiver) = + tokio::sync::broadcast::channel(4); let (watcher_sender, mut watcher_receiver) = DebouncedReceiver::new_with_sender(); @@ -258,9 +260,6 @@ where let mut watcher = new_watcher(watcher_sender.clone())?; consume_paths_to_watch(&mut watcher, &mut paths_to_watch_receiver); - let (changed_paths_sender, changed_paths_receiver) = - tokio::sync::broadcast::channel(4); - let receiver_future = async { loop { let maybe_paths = paths_to_watch_receiver.recv().await; @@ -271,7 +270,7 @@ where flags.clone(), WatcherInterface { paths_to_watch_sender: paths_to_watch_sender.clone(), - changed_paths_receiver: Some(changed_paths_receiver), + changed_paths_receiver: Some(changed_paths_receiver.resubscribe()), restart_sender: watcher_restart_sender.clone(), }, )?); From b832c7f84ccc0dfe5dc45708713349c37c5266d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 01:40:50 +0200 Subject: [PATCH 30/79] remove optional field --- cli/factory.rs | 23 ++++++++++------------- cli/util/file_watcher.rs | 21 ++++++++++++++++----- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/cli/factory.rs b/cli/factory.rs index 964087640d0225..2a5ae37802a6f7 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -603,20 +603,17 @@ impl CliFactory { let cli_node_resolver = self.cli_node_resolver().await?; // TODO(bartlomieju): can we just clone `WatcherInterface` here? - let maybe_hot_reload_interface = self - .watcher_interface - .as_ref() - .map(|i| { - if let Some(receiver) = i.changed_paths_receiver.as_ref() { - Some(HotReloadInterface { - path_change_receiver: receiver.resubscribe(), - file_watcher_restart_sender: i.restart_sender.clone(), - }) - } else { - None - } + let maybe_hot_reload_interface = if self.options.has_hot_reload() { + let watcher_interface = self.watcher_interface.as_ref().unwrap(); + Some(HotReloadInterface { + path_change_receiver: watcher_interface + .changed_paths_receiver + .resubscribe(), + file_watcher_restart_sender: watcher_interface.restart_sender.clone(), }) - .flatten(); + } else { + None + }; Ok(CliMainWorkerFactory::new( StorageKeyResolver::from_options(&self.options), diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 4f0869084393f1..945cd750ef676b 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -111,12 +111,20 @@ fn create_print_after_restart_fn(clear_screen: bool) -> impl Fn() { // TODO(bartlomieju): this is a poor name; change it pub struct WatcherInterface { pub paths_to_watch_sender: tokio::sync::mpsc::UnboundedSender>, - // TODO(bartlomieju): can we make it non-optional? - pub changed_paths_receiver: - Option>>, + pub changed_paths_receiver: tokio::sync::broadcast::Receiver>, pub restart_sender: tokio::sync::mpsc::UnboundedSender<()>, } +impl Clone for WatcherInterface { + fn clone(&self) -> Self { + Self { + paths_to_watch_sender: self.paths_to_watch_sender.clone(), + changed_paths_receiver: self.changed_paths_receiver.resubscribe(), + restart_sender: self.restart_sender.clone(), + } + } +} + /// Creates a file watcher. /// /// - `operation` is the actual operation we want to run every time the watcher detects file @@ -136,6 +144,9 @@ where tokio::sync::mpsc::unbounded_channel(); let (watcher_restart_sender, mut watcher_restart_receiver) = tokio::sync::mpsc::unbounded_channel(); + // TODO(bartlomieju): currently unused, unify with `watch_recv`. + let (_changed_paths_sender, changed_paths_receiver) = + tokio::sync::broadcast::channel(4); let (watcher_sender, mut watcher_receiver) = DebouncedReceiver::new_with_sender(); @@ -170,7 +181,7 @@ where flags.clone(), WatcherInterface { paths_to_watch_sender: paths_to_watch_sender.clone(), - changed_paths_receiver: None, + changed_paths_receiver: changed_paths_receiver.resubscribe(), restart_sender: watcher_restart_sender.clone(), }, changed_paths.take(), @@ -270,7 +281,7 @@ where flags.clone(), WatcherInterface { paths_to_watch_sender: paths_to_watch_sender.clone(), - changed_paths_receiver: Some(changed_paths_receiver.resubscribe()), + changed_paths_receiver: changed_paths_receiver.resubscribe(), restart_sender: watcher_restart_sender.clone(), }, )?); From 85011e8e681c5abad87f151110ecb9ae416c173e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 01:42:31 +0200 Subject: [PATCH 31/79] clone WatcherInterface --- cli/util/file_watcher.rs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 945cd750ef676b..de60e6328daac9 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -156,7 +156,11 @@ where } = print_config; let print_after_restart = create_print_after_restart_fn(clear_screen); - + let watcher_interface = WatcherInterface { + paths_to_watch_sender: paths_to_watch_sender.clone(), + changed_paths_receiver: changed_paths_receiver.resubscribe(), + restart_sender: watcher_restart_sender.clone(), + }; info!("{} {} started.", colors::intense_blue("Watcher"), job_name,); let mut changed_paths = None; @@ -179,11 +183,7 @@ where }; let operation_future = error_handler(operation( flags.clone(), - WatcherInterface { - paths_to_watch_sender: paths_to_watch_sender.clone(), - changed_paths_receiver: changed_paths_receiver.resubscribe(), - restart_sender: watcher_restart_sender.clone(), - }, + watcher_interface.clone(), changed_paths.take(), )?); @@ -257,7 +257,11 @@ where DebouncedReceiver::new_with_sender(); let PrintConfig { job_name, .. } = print_config; - + let watcher_interface = WatcherInterface { + paths_to_watch_sender: paths_to_watch_sender.clone(), + changed_paths_receiver: changed_paths_receiver.resubscribe(), + restart_sender: watcher_restart_sender.clone(), + }; info!("{} {} started.", colors::intense_blue("HMR"), job_name,); loop { @@ -277,14 +281,8 @@ where add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); } }; - let operation_future = error_handler(operation( - flags.clone(), - WatcherInterface { - paths_to_watch_sender: paths_to_watch_sender.clone(), - changed_paths_receiver: changed_paths_receiver.resubscribe(), - restart_sender: watcher_restart_sender.clone(), - }, - )?); + let operation_future = + error_handler(operation(flags.clone(), watcher_interface.clone())?); // TODO(bartlomieju): this is almost identical with `watch_func`, besides // `received_changed_paths` arm - figure out how to remove it. From 3d822c20a559e6f9c29bea6ad9a59aca1f2cd983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 01:46:28 +0200 Subject: [PATCH 32/79] remove HotReloadInterface --- cli/factory.rs | 15 +++------------ cli/tools/run/hot_reload/mod.rs | 22 ++++------------------ cli/worker.rs | 12 +++++------- 3 files changed, 12 insertions(+), 37 deletions(-) diff --git a/cli/factory.rs b/cli/factory.rs index 2a5ae37802a6f7..a606ed71228e3c 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -40,7 +40,6 @@ use crate::resolver::CliGraphResolver; use crate::resolver::CliGraphResolverOptions; use crate::standalone::DenoCompileBinaryWriter; use crate::tools::check::TypeChecker; -use crate::tools::run::hot_reload::HotReloadInterface; use crate::util::file_watcher::WatcherInterface; use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; @@ -601,16 +600,8 @@ impl CliFactory { let npm_resolver = self.npm_resolver().await?; let fs = self.fs(); let cli_node_resolver = self.cli_node_resolver().await?; - - // TODO(bartlomieju): can we just clone `WatcherInterface` here? - let maybe_hot_reload_interface = if self.options.has_hot_reload() { - let watcher_interface = self.watcher_interface.as_ref().unwrap(); - Some(HotReloadInterface { - path_change_receiver: watcher_interface - .changed_paths_receiver - .resubscribe(), - file_watcher_restart_sender: watcher_interface.restart_sender.clone(), - }) + let maybe_file_watcher_interface = if self.options.has_hot_reload() { + Some(self.watcher_interface.clone().unwrap()) } else { None }; @@ -638,7 +629,7 @@ impl CliFactory { self.root_cert_store_provider().clone(), self.fs().clone(), Some(self.emitter()?.clone()), - maybe_hot_reload_interface, + maybe_file_watcher_interface, self.maybe_inspector_server().clone(), self.maybe_lockfile().clone(), self.feature_checker().clone(), diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 5aa1810cfc492f..622929a48ab106 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -1,6 +1,7 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use crate::emit::Emitter; +use crate::util::file_watcher::WatcherInterface; use deno_ast::MediaType; use deno_core::error::generic_error; use deno_core::error::AnyError; @@ -22,21 +23,6 @@ use json_types::ScriptParsed; use json_types::SetScriptSourceReturnObject; use json_types::Status; -// TODO(bartlomieju): this is a poor name; change it -pub struct HotReloadInterface { - pub path_change_receiver: tokio::sync::broadcast::Receiver>, - pub file_watcher_restart_sender: tokio::sync::mpsc::UnboundedSender<()>, -} - -impl Clone for HotReloadInterface { - fn clone(&self) -> Self { - Self { - path_change_receiver: self.path_change_receiver.resubscribe(), - file_watcher_restart_sender: self.file_watcher_restart_sender.clone(), - } - } -} - pub struct HotReloadManager { session: LocalInspectorSession, path_change_receiver: tokio::sync::broadcast::Receiver>, @@ -49,13 +35,13 @@ impl HotReloadManager { pub fn new( emitter: Arc, session: LocalInspectorSession, - interface: HotReloadInterface, + interface: WatcherInterface, ) -> Self { Self { session, emitter, - path_change_receiver: interface.path_change_receiver, - file_watcher_restart_sender: interface.file_watcher_restart_sender, + path_change_receiver: interface.changed_paths_receiver, + file_watcher_restart_sender: interface.restart_sender, script_ids: HashMap::new(), } } diff --git a/cli/worker.rs b/cli/worker.rs index 9e465dc21bc5f1..6636309c956d30 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -52,9 +52,9 @@ use crate::npm::CliNpmResolver; use crate::ops; use crate::tools; use crate::tools::coverage::CoverageCollector; -use crate::tools::run::hot_reload::HotReloadInterface; use crate::tools::run::hot_reload::HotReloadManager; use crate::util::checksum; +use crate::util::file_watcher::WatcherInterface; use crate::version; pub trait ModuleLoaderFactory: Send + Sync { @@ -113,7 +113,7 @@ struct SharedWorkerState { root_cert_store_provider: Arc, fs: Arc, emitter: Option>, - maybe_hot_reload_interface: Option, + maybe_file_watcher_interface: Option, maybe_inspector_server: Option>, maybe_lockfile: Option>>, feature_checker: Arc, @@ -317,9 +317,7 @@ impl CliMainWorker { return Ok(None); } - // TODO(bartlomieju): would be so much nicer if we could clone a - // `WatcherInterface` here - let interface = self.shared.maybe_hot_reload_interface.clone().unwrap(); + let interface = self.shared.maybe_file_watcher_interface.clone().unwrap(); // TODO(bartlomieju): this is a code smell, refactor so we don't have // to pass `emitter` here @@ -364,7 +362,7 @@ impl CliMainWorkerFactory { root_cert_store_provider: Arc, fs: Arc, emitter: Option>, - maybe_hot_reload_interface: Option, + maybe_file_watcher_interface: Option, maybe_inspector_server: Option>, maybe_lockfile: Option>>, feature_checker: Arc, @@ -384,7 +382,7 @@ impl CliMainWorkerFactory { root_cert_store_provider, emitter, fs, - maybe_hot_reload_interface, + maybe_file_watcher_interface, maybe_inspector_server, maybe_lockfile, feature_checker, From 8c5340df82686cb3405334734135b226de3982d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 01:50:09 +0200 Subject: [PATCH 33/79] renames --- cli/factory.rs | 2 +- cli/tools/bench/mod.rs | 2 +- cli/tools/bundle.rs | 2 +- cli/tools/fmt.rs | 2 +- cli/tools/lint.rs | 2 +- cli/tools/run/hot_reload/mod.rs | 12 +++--- cli/tools/run/mod.rs | 4 +- cli/tools/test/mod.rs | 2 +- cli/util/file_watcher.rs | 67 +++++++++++++++++---------------- 9 files changed, 49 insertions(+), 46 deletions(-) diff --git a/cli/factory.rs b/cli/factory.rs index a606ed71228e3c..97821346ce50e0 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -389,7 +389,7 @@ impl CliFactory { let maybe_file_watcher_reporter = self .watcher_interface .as_ref() - .map(|i| FileWatcherReporter::new(i.paths_to_watch_sender.clone())); + .map(|i| FileWatcherReporter::new(i.paths_to_watch_tx.clone())); self .services .maybe_file_watcher_reporter diff --git a/cli/tools/bench/mod.rs b/cli/tools/bench/mod.rs index 92132cb90289b4..6edc65424db35f 100644 --- a/cli/tools/bench/mod.rs +++ b/cli/tools/bench/mod.rs @@ -419,7 +419,7 @@ pub async fn run_benchmarks_with_watch( }, move |flags, watcher_interface, changed_paths| { let bench_flags = bench_flags.clone(); - let sender = watcher_interface.paths_to_watch_sender.clone(); + let sender = watcher_interface.paths_to_watch_tx.clone(); Ok(async move { let factory = CliFactoryBuilder::new() .build_from_flags_for_watcher(flags, watcher_interface) diff --git a/cli/tools/bundle.rs b/cli/tools/bundle.rs index 668d8d2d5c2561..c7ef84c5b94d40 100644 --- a/cli/tools/bundle.rs +++ b/cli/tools/bundle.rs @@ -36,7 +36,7 @@ pub async fn bundle( clear_screen: !watch_flags.no_clear_screen, }, move |flags, watcher_interface, _changed_paths| { - let sender = watcher_interface.paths_to_watch_sender.clone(); + let sender = watcher_interface.paths_to_watch_tx.clone(); let bundle_flags = bundle_flags.clone(); Ok(async move { let factory = CliFactoryBuilder::new() diff --git a/cli/tools/fmt.rs b/cli/tools/fmt.rs index 285cf5cb23a41e..d04af99e3b69c5 100644 --- a/cli/tools/fmt.rs +++ b/cli/tools/fmt.rs @@ -70,7 +70,7 @@ pub async fn format(flags: Flags, fmt_flags: FmtFlags) -> Result<(), AnyError> { }, move |flags, watcher_interface, changed_paths| { let fmt_flags = fmt_flags.clone(); - let sender = watcher_interface.paths_to_watch_sender.clone(); + let sender = watcher_interface.paths_to_watch_tx.clone(); Ok(async move { let factory = CliFactory::from_flags(flags).await?; let cli_options = factory.cli_options(); diff --git a/cli/tools/lint.rs b/cli/tools/lint.rs index ae22cb2f9a61a3..21678cb0140ae8 100644 --- a/cli/tools/lint.rs +++ b/cli/tools/lint.rs @@ -77,7 +77,7 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> { Ok(files) } })?; - _ = watcher_interface.paths_to_watch_sender.send(files.clone()); + _ = watcher_interface.paths_to_watch_tx.send(files.clone()); let lint_paths = if let Some(paths) = changed_paths { // lint all files on any changed (https://github.com/denoland/deno/issues/12446) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 622929a48ab106..d3b0f009ab254c 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -25,8 +25,8 @@ use json_types::Status; pub struct HotReloadManager { session: LocalInspectorSession, - path_change_receiver: tokio::sync::broadcast::Receiver>, - file_watcher_restart_sender: tokio::sync::mpsc::UnboundedSender<()>, + changed_paths_rx: tokio::sync::broadcast::Receiver>, + restart_tx: tokio::sync::mpsc::UnboundedSender<()>, script_ids: HashMap, emitter: Arc, } @@ -40,8 +40,8 @@ impl HotReloadManager { Self { session, emitter, - path_change_receiver: interface.changed_paths_receiver, - file_watcher_restart_sender: interface.restart_sender, + changed_paths_rx: interface.changed_paths_rx, + restart_tx: interface.restart_tx, script_ids: HashMap::new(), } } @@ -80,7 +80,7 @@ impl HotReloadManager { break Err(generic_error(format!("{text} {description}"))); } } - changed_paths = self.path_change_receiver.recv() => { + changed_paths = self.changed_paths_rx.recv() => { let changed_paths = changed_paths?; let filtered_paths: Vec = changed_paths.into_iter().filter(|p| p.extension().map_or(false, |ext| { let ext_str = ext.to_str().unwrap(); @@ -133,7 +133,7 @@ impl HotReloadManager { if !result.status.should_retry() { log::info!("{} Restarting the process...", colors::intense_blue("HMR")); // TODO(bartlomieju): Print into that sending failed? - let _ = self.file_watcher_restart_sender.send(()); + let _ = self.restart_tx.send(()); break; } } diff --git a/cli/tools/run/mod.rs b/cli/tools/run/mod.rs index a471ff69149943..1f33c563d7fb14 100644 --- a/cli/tools/run/mod.rs +++ b/cli/tools/run/mod.rs @@ -114,7 +114,7 @@ async fn run_with_watch( clear_screen: false, }, move |flags, watcher_interface| { - let watch_path_sender = watcher_interface.paths_to_watch_sender.clone(); + let watch_path_sender = watcher_interface.paths_to_watch_tx.clone(); Ok(async move { let factory = CliFactoryBuilder::new() .build_from_flags_for_watcher(flags, watcher_interface) @@ -152,7 +152,7 @@ async fn run_with_watch( clear_screen: !watch_flags.no_clear_screen, }, move |flags, watcher_interface, _changed_paths| { - let sender = watcher_interface.paths_to_watch_sender.clone(); + let sender = watcher_interface.paths_to_watch_tx.clone(); Ok(async move { let factory = CliFactoryBuilder::new() .build_from_flags_for_watcher(flags, watcher_interface) diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index a9fca78bcbc428..f2b8f98e547564 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -1215,7 +1215,7 @@ pub async fn run_tests_with_watch( }, move |flags, watcher_interface, changed_paths| { let test_flags = test_flags.clone(); - let sender = watcher_interface.paths_to_watch_sender.clone(); + let sender = watcher_interface.paths_to_watch_tx.clone(); Ok(async move { let factory = CliFactoryBuilder::new() .build_from_flags_for_watcher(flags, watcher_interface) diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index de60e6328daac9..1a13d1b7fb31e7 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -109,18 +109,24 @@ fn create_print_after_restart_fn(clear_screen: bool) -> impl Fn() { } // TODO(bartlomieju): this is a poor name; change it +/// And interface to interact with Deno's CLI file watcher. pub struct WatcherInterface { - pub paths_to_watch_sender: tokio::sync::mpsc::UnboundedSender>, - pub changed_paths_receiver: tokio::sync::broadcast::Receiver>, - pub restart_sender: tokio::sync::mpsc::UnboundedSender<()>, + /// Send a list of paths that should be watched for changes. + pub paths_to_watch_tx: tokio::sync::mpsc::UnboundedSender>, + + /// Listen for a list of paths that were changed. + pub changed_paths_rx: tokio::sync::broadcast::Receiver>, + + /// Send a message to force a restart. + pub restart_tx: tokio::sync::mpsc::UnboundedSender<()>, } impl Clone for WatcherInterface { fn clone(&self) -> Self { Self { - paths_to_watch_sender: self.paths_to_watch_sender.clone(), - changed_paths_receiver: self.changed_paths_receiver.resubscribe(), - restart_sender: self.restart_sender.clone(), + paths_to_watch_tx: self.paths_to_watch_tx.clone(), + changed_paths_rx: self.changed_paths_rx.resubscribe(), + restart_tx: self.restart_tx.clone(), } } } @@ -140,12 +146,11 @@ where FnMut(Flags, WatcherInterface, Option>) -> Result, F: Future>, { - let (paths_to_watch_sender, mut paths_to_watch_receiver) = - tokio::sync::mpsc::unbounded_channel(); - let (watcher_restart_sender, mut watcher_restart_receiver) = + let (paths_to_watch_tx, mut paths_to_watch_rx) = tokio::sync::mpsc::unbounded_channel(); + let (restart_tx, mut restart_rx) = tokio::sync::mpsc::unbounded_channel(); // TODO(bartlomieju): currently unused, unify with `watch_recv`. - let (_changed_paths_sender, changed_paths_receiver) = + let (_changed_paths_tx, changed_paths_rx) = tokio::sync::broadcast::channel(4); let (watcher_sender, mut watcher_receiver) = DebouncedReceiver::new_with_sender(); @@ -157,9 +162,9 @@ where let print_after_restart = create_print_after_restart_fn(clear_screen); let watcher_interface = WatcherInterface { - paths_to_watch_sender: paths_to_watch_sender.clone(), - changed_paths_receiver: changed_paths_receiver.resubscribe(), - restart_sender: watcher_restart_sender.clone(), + 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,); @@ -173,11 +178,11 @@ where } let mut watcher = new_watcher(watcher_sender.clone())?; - consume_paths_to_watch(&mut watcher, &mut paths_to_watch_receiver); + consume_paths_to_watch(&mut watcher, &mut paths_to_watch_rx); let receiver_future = async { loop { - let maybe_paths = paths_to_watch_receiver.recv().await; + let maybe_paths = paths_to_watch_rx.recv().await; add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); } }; @@ -192,7 +197,7 @@ where select! { _ = receiver_future => {}, - _ = watcher_restart_receiver.recv() => { + _ = restart_rx.recv() => { print_after_restart(); continue; }, @@ -202,7 +207,7 @@ where continue; }, success = operation_future => { - consume_paths_to_watch(&mut watcher, &mut paths_to_watch_receiver); + consume_paths_to_watch(&mut watcher, &mut paths_to_watch_rx); // TODO(bartlomieju): print exit code here? info!( "{} {} {}. Restarting on file change...", @@ -219,7 +224,7 @@ where let receiver_future = async { loop { - let maybe_paths = paths_to_watch_receiver.recv().await; + let maybe_paths = paths_to_watch_rx.recv().await; add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); } }; @@ -247,20 +252,18 @@ where O: FnMut(Flags, WatcherInterface) -> Result, F: Future>, { - let (paths_to_watch_sender, mut paths_to_watch_receiver) = + let (paths_to_watch_tx, mut paths_to_watch_rx) = tokio::sync::mpsc::unbounded_channel(); - let (watcher_restart_sender, mut watcher_restart_receiver) = - tokio::sync::mpsc::unbounded_channel(); - let (changed_paths_sender, changed_paths_receiver) = - tokio::sync::broadcast::channel(4); + let (restart_tx, mut restart_rx) = tokio::sync::mpsc::unbounded_channel(); + let (changed_paths_tx, changed_paths_rx) = tokio::sync::broadcast::channel(4); let (watcher_sender, mut watcher_receiver) = DebouncedReceiver::new_with_sender(); let PrintConfig { job_name, .. } = print_config; let watcher_interface = WatcherInterface { - paths_to_watch_sender: paths_to_watch_sender.clone(), - changed_paths_receiver: changed_paths_receiver.resubscribe(), - restart_sender: watcher_restart_sender.clone(), + 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("HMR"), job_name,); @@ -273,11 +276,11 @@ where } let mut watcher = new_watcher(watcher_sender.clone())?; - consume_paths_to_watch(&mut watcher, &mut paths_to_watch_receiver); + consume_paths_to_watch(&mut watcher, &mut paths_to_watch_rx); let receiver_future = async { loop { - let maybe_paths = paths_to_watch_receiver.recv().await; + let maybe_paths = paths_to_watch_rx.recv().await; add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); } }; @@ -291,14 +294,14 @@ where received_changed_paths = watcher_receiver.recv() => { // eprintln!("changed paths for watcher {:#?}", received_changed_paths); if let Some(changed_paths) = received_changed_paths { - changed_paths_sender.send(changed_paths)?; + changed_paths_tx.send(changed_paths)?; } }, - _ = watcher_restart_receiver.recv() => { + _ = restart_rx.recv() => { continue; }, success = operation_future => { - consume_paths_to_watch(&mut watcher, &mut paths_to_watch_receiver); + consume_paths_to_watch(&mut watcher, &mut paths_to_watch_rx); // TODO(bartlomieju): print exit code here? info!( "{} {} {}. Restarting on file change...", @@ -315,7 +318,7 @@ where let receiver_future = async { loop { - let maybe_paths = paths_to_watch_receiver.recv().await; + let maybe_paths = paths_to_watch_rx.recv().await; add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); } }; From 3ee312131ce21c12c479d2895baf873af9b62a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 01:56:47 +0200 Subject: [PATCH 34/79] add a todo --- cli/tools/run/hot_reload/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index d3b0f009ab254c..9016ca82b02706 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -120,7 +120,7 @@ impl HotReloadManager { }; // eprintln!("transpiled source code {:#?}", source_code); - // TODO(bartlomieju): this loop seems fishy + // TODO(bartlomieju): this loop should do 2 retries at most loop { let result = self.set_script_source(&id, source_code.as_str()).await?; From 1390c7167dc46e32f1ac4a4e010c874d8d7c778f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 02:22:08 +0200 Subject: [PATCH 35/79] Add a copt --- cli/util/file_watcher.rs | 113 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 1a13d1b7fb31e7..9f4ebaa93c2a8e 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -239,6 +239,119 @@ where } } +/// Creates a file watcher. +/// +/// - `operation` is the actual operation we want to run every time the watcher detects file +/// changes. For example, in the case where we would like to bundle, then `operation` would +/// have the logic for it like bundling the code. +pub async fn watch_func2( + mut flags: Flags, + print_config: PrintConfig, + mut operation: O, +) -> Result<(), AnyError> +where + O: + FnMut(Flags, WatcherInterface, Option>) -> Result, + F: Future>, +{ + let (paths_to_watch_tx, mut paths_to_watch_rx) = + tokio::sync::mpsc::unbounded_channel(); + let (restart_tx, mut restart_rx) = tokio::sync::mpsc::unbounded_channel(); + let (changed_paths_tx, changed_paths_rx) = tokio::sync::broadcast::channel(4); + let (watcher_sender, mut watcher_receiver) = + DebouncedReceiver::new_with_sender(); + + let PrintConfig { + job_name, + clear_screen, + } = print_config; + + let print_after_restart = create_print_after_restart_fn(clear_screen); + let watcher_interface = WatcherInterface { + 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,); + + 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 + // start to fail, this may need to be increased. + for _ in 0..10 { + tokio::task::yield_now().await; + } + + let mut watcher = new_watcher(watcher_sender.clone())?; + consume_paths_to_watch(&mut watcher, &mut paths_to_watch_rx); + + let receiver_future = async { + loop { + let maybe_paths = paths_to_watch_rx.recv().await; + add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); + } + }; + let operation_future = error_handler(operation( + flags.clone(), + watcher_interface.clone(), + changed_paths.take(), + )?); + + // don't reload dependencies after the first run + flags.reload = false; + + select! { + _ = receiver_future => {}, + _ = restart_rx.recv() => { + print_after_restart(); + continue; + }, + received_changed_paths = watcher_receiver.recv() => { + changed_paths = received_changed_paths.clone(); + // TODO(bartlomieju): should we fail on sending changed paths? + // TODO(bartlomieju): change channel to accept Option<> + if let Some(received_changed_paths) = received_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"), + job_name, + if success { + "finished" + } else { + "failed" + } + ); + }, + }; + + let receiver_future = async { + loop { + let maybe_paths = paths_to_watch_rx.recv().await; + add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); + } + }; + + // If we got this far, it means that the `operation` has finished; let's wait + // and see if there are any new paths to watch received or any of the already + // watched paths has changed. + select! { + _ = receiver_future => {}, + received_changed_paths = watcher_receiver.recv() => { + print_after_restart(); + changed_paths = received_changed_paths; + continue; + }, + }; + } +} + /// Creates a file watcher. /// /// - `operation` is the actual operation we want to run and notify every time From 4e0081c72a7ecf979a76323efd8099c1a05fce19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 03:01:14 +0200 Subject: [PATCH 36/79] unify into a single function! --- cli/tools/run/hot_reload/mod.rs | 15 ++- cli/tools/run/mod.rs | 8 +- cli/util/file_watcher.rs | 221 +++++--------------------------- main.js | 2 +- 4 files changed, 47 insertions(+), 199 deletions(-) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 9016ca82b02706..04682f2018141f 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -65,19 +65,19 @@ impl HotReloadManager { // TODO(SyrupThinker): Deferred retry with timeout Some(notification) = session_rx.next() => { let notification = serde_json::from_value::(notification)?; - if notification.method == "Debugger.scriptParsed" { - let params = serde_json::from_value::(notification.params)?; - if params.url.starts_with("file://") { - self.script_ids.insert(params.url, params.script_id); - } // TODO(bartlomieju): this is not great... and the code is duplicated with the REPL. - } else if notification.method == "Runtime.exceptionThrown" { + if notification.method == "Runtime.exceptionThrown" { let params = notification.params; let exception_details = params.get("exceptionDetails").unwrap().as_object().unwrap(); let text = exception_details.get("text").unwrap().as_str().unwrap(); let exception = exception_details.get("exception").unwrap().as_object().unwrap(); let description = exception.get("description").and_then(|d| d.as_str()).unwrap_or("undefined"); break Err(generic_error(format!("{text} {description}"))); + } else if notification.method == "Debugger.scriptParsed" { + let params = serde_json::from_value::(notification.params)?; + if params.url.starts_with("file://") { + self.script_ids.insert(params.url, params.script_id); + } } } changed_paths = self.changed_paths_rx.recv() => { @@ -89,15 +89,18 @@ impl HotReloadManager { for path in filtered_paths { let Some(path_str) = path.to_str() else { + let _ = self.restart_tx.send(()); continue; }; let Ok(module_url) = Url::from_file_path(path_str) else { + let _ = self.restart_tx.send(()); continue; }; log::info!("{} Reloading changed module {}", colors::intense_blue("HMR"), module_url.as_str()); let Some(id) = self.script_ids.get(module_url.as_str()).cloned() else { + let _ = self.restart_tx.send(()); continue; }; diff --git a/cli/tools/run/mod.rs b/cli/tools/run/mod.rs index 1f33c563d7fb14..1fc201b9307b70 100644 --- a/cli/tools/run/mod.rs +++ b/cli/tools/run/mod.rs @@ -15,6 +15,7 @@ use crate::factory::CliFactory; use crate::factory::CliFactoryBuilder; use crate::file_fetcher::File; use crate::util; +use crate::util::file_watcher::WatcherRestartMode; pub mod hot_reload; @@ -113,7 +114,8 @@ async fn run_with_watch( job_name: "Process".to_string(), clear_screen: false, }, - move |flags, watcher_interface| { + WatcherRestartMode::Manual, + move |flags, watcher_interface, _changed_paths| { let watch_path_sender = watcher_interface.paths_to_watch_tx.clone(); Ok(async move { let factory = CliFactoryBuilder::new() @@ -143,7 +145,9 @@ async fn run_with_watch( }) }, ) - .await + .await?; + + Ok(0) } else { util::file_watcher::watch_func( flags, diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 9f4ebaa93c2a8e..f847407583ae23 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -137,106 +137,32 @@ impl Clone for WatcherInterface { /// changes. For example, in the case where we would like to bundle, then `operation` would /// have the logic for it like bundling the code. pub async fn watch_func( - mut flags: Flags, + flags: Flags, print_config: PrintConfig, - mut operation: O, + operation: O, ) -> Result<(), AnyError> where O: FnMut(Flags, WatcherInterface, Option>) -> Result, F: Future>, { - let (paths_to_watch_tx, mut paths_to_watch_rx) = - tokio::sync::mpsc::unbounded_channel(); - let (restart_tx, mut restart_rx) = tokio::sync::mpsc::unbounded_channel(); - // TODO(bartlomieju): currently unused, unify with `watch_recv`. - let (_changed_paths_tx, changed_paths_rx) = - tokio::sync::broadcast::channel(4); - let (watcher_sender, mut watcher_receiver) = - DebouncedReceiver::new_with_sender(); - - let PrintConfig { - job_name, - clear_screen, - } = print_config; - - let print_after_restart = create_print_after_restart_fn(clear_screen); - let watcher_interface = WatcherInterface { - 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,); - - 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 - // start to fail, this may need to be increased. - for _ in 0..10 { - tokio::task::yield_now().await; - } - - let mut watcher = new_watcher(watcher_sender.clone())?; - consume_paths_to_watch(&mut watcher, &mut paths_to_watch_rx); - - let receiver_future = async { - loop { - let maybe_paths = paths_to_watch_rx.recv().await; - add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); - } - }; - let operation_future = error_handler(operation( - flags.clone(), - watcher_interface.clone(), - changed_paths.take(), - )?); + watch_recv( + flags, + print_config, + WatcherRestartMode::Automatic, + operation, + ) + .await +} - // don't reload dependencies after the first run - flags.reload = false; +#[derive(Clone, Copy, Debug)] +pub enum WatcherRestartMode { + /// When a file path changes the process is restarted. + Automatic, - select! { - _ = receiver_future => {}, - _ = restart_rx.recv() => { - print_after_restart(); - continue; - }, - received_changed_paths = watcher_receiver.recv() => { - print_after_restart(); - changed_paths = received_changed_paths; - continue; - }, - 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"), - job_name, - if success { - "finished" - } else { - "failed" - } - ); - }, - }; - - let receiver_future = async { - loop { - let maybe_paths = paths_to_watch_rx.recv().await; - add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); - } - }; - select! { - _ = receiver_future => {}, - received_changed_paths = watcher_receiver.recv() => { - print_after_restart(); - changed_paths = received_changed_paths; - continue; - }, - }; - } + /// When a file path changes the caller will trigger a restart, using + /// `WatcherInterface.restart_tx`. + Manual, } /// Creates a file watcher. @@ -244,9 +170,10 @@ where /// - `operation` is the actual operation we want to run every time the watcher detects file /// changes. For example, in the case where we would like to bundle, then `operation` would /// have the logic for it like bundling the code. -pub async fn watch_func2( +pub async fn watch_recv( mut flags: Flags, print_config: PrintConfig, + restart_mode: WatcherRestartMode, mut operation: O, ) -> Result<(), AnyError> where @@ -309,10 +236,18 @@ where }, received_changed_paths = watcher_receiver.recv() => { changed_paths = received_changed_paths.clone(); - // TODO(bartlomieju): should we fail on sending changed paths? - // TODO(bartlomieju): change channel to accept Option<> - if let Some(received_changed_paths) = received_changed_paths { - let _ = changed_paths_tx.send(received_changed_paths); + + match restart_mode { + WatcherRestartMode::Automatic => { + continue; + }, + WatcherRestartMode::Manual => { + // TODO(bartlomieju): should we fail on sending changed paths? + // TODO(bartlomieju): change channel to accept Option<> + if let Some(received_changed_paths) = received_changed_paths { + let _ = changed_paths_tx.send(received_changed_paths); + } + } } }, success = operation_future => { @@ -352,100 +287,6 @@ where } } -/// Creates a file watcher. -/// -/// - `operation` is the actual operation we want to run and notify every time -/// the watcher detects file changes. -pub async fn watch_recv( - flags: Flags, - print_config: PrintConfig, - mut operation: O, -) -> Result -where - O: FnMut(Flags, WatcherInterface) -> Result, - F: Future>, -{ - let (paths_to_watch_tx, mut paths_to_watch_rx) = - tokio::sync::mpsc::unbounded_channel(); - let (restart_tx, mut restart_rx) = tokio::sync::mpsc::unbounded_channel(); - let (changed_paths_tx, changed_paths_rx) = tokio::sync::broadcast::channel(4); - let (watcher_sender, mut watcher_receiver) = - DebouncedReceiver::new_with_sender(); - - let PrintConfig { job_name, .. } = print_config; - let watcher_interface = WatcherInterface { - 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("HMR"), job_name,); - - 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 - // start to fail, this may need to be increased. - for _ in 0..10 { - tokio::task::yield_now().await; - } - - let mut watcher = new_watcher(watcher_sender.clone())?; - consume_paths_to_watch(&mut watcher, &mut paths_to_watch_rx); - - let receiver_future = async { - loop { - let maybe_paths = paths_to_watch_rx.recv().await; - add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); - } - }; - let operation_future = - error_handler(operation(flags.clone(), watcher_interface.clone())?); - - // TODO(bartlomieju): this is almost identical with `watch_func`, besides - // `received_changed_paths` arm - figure out how to remove it. - select! { - _ = receiver_future => {}, - received_changed_paths = watcher_receiver.recv() => { - // eprintln!("changed paths for watcher {:#?}", received_changed_paths); - if let Some(changed_paths) = received_changed_paths { - changed_paths_tx.send(changed_paths)?; - } - }, - _ = restart_rx.recv() => { - continue; - }, - 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"), - job_name, - if success { - "finished" - } else { - "failed" - } - ); - }, - }; - - let receiver_future = async { - loop { - let maybe_paths = paths_to_watch_rx.recv().await; - add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); - } - }; - select! { - _ = receiver_future => {}, - _received_changed_paths = watcher_receiver.recv() => { - // print_after_restart(); - // changed_paths = received_changed_paths; - continue; - }, - }; - } -} - fn new_watcher( sender: Arc>>, ) -> Result { diff --git a/main.js b/main.js index 577457d9157918..cf452fcca03cd9 100644 --- a/main.js +++ b/main.js @@ -1,7 +1,7 @@ import { getFoo } from "./foo.ts"; async function bar() { - throw new Error("fail"); + throw new Error("fail1"); } let i = 1; From 9ecb1dd24d1dc604bbd565338593a9b22e560df7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 03:07:06 +0200 Subject: [PATCH 37/79] try to debug why short program doesn't watch for paths --- cli/tools/run/hot_reload/mod.rs | 1 + cli/util/file_watcher.rs | 4 ++++ cli/worker.rs | 6 ++++++ 3 files changed, 11 insertions(+) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 04682f2018141f..f08c7bdeda7e00 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -81,6 +81,7 @@ impl HotReloadManager { } } changed_paths = self.changed_paths_rx.recv() => { + eprintln!("changed patchs in hot {:#?}", changed_paths); let changed_paths = changed_paths?; let filtered_paths: Vec = changed_paths.into_iter().filter(|p| p.extension().map_or(false, |ext| { let ext_str = ext.to_str().unwrap(); diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index f847407583ae23..6cbd90647fa43a 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -216,6 +216,7 @@ where let receiver_future = async { loop { let maybe_paths = paths_to_watch_rx.recv().await; + eprintln!("paths to watch paths {:?}", maybe_paths); add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); } }; @@ -235,6 +236,7 @@ where continue; }, received_changed_paths = watcher_receiver.recv() => { + eprintln!("received paths {:?}", received_changed_paths); changed_paths = received_changed_paths.clone(); match restart_mode { @@ -269,6 +271,7 @@ where let receiver_future = async { loop { let maybe_paths = paths_to_watch_rx.recv().await; + eprintln!("paths to watch paths {:?}", maybe_paths); add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); } }; @@ -279,6 +282,7 @@ where select! { _ = receiver_future => {}, received_changed_paths = watcher_receiver.recv() => { + eprintln!("received paths {:?}", received_changed_paths); print_after_restart(); changed_paths = received_changed_paths; continue; diff --git a/cli/worker.rs b/cli/worker.rs index 6636309c956d30..9d17ab14ec3bd8 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -163,10 +163,12 @@ impl CliMainWorker { loop { if let Some(hot_reload_manager) = maybe_hot_reload_manager.as_mut() { + eprintln!("start hmr"); self .worker .with_event_loop_fallible(hot_reload_manager.run().boxed_local()) .await?; + eprintln!("stop hmr"); } else { self .worker @@ -182,6 +184,8 @@ impl CliMainWorker { } } + eprintln!("hot reload finished"); + self.worker.dispatch_unload_event(located_script_name!())?; if let Some(coverage_collector) = maybe_coverage_collector.as_mut() { @@ -197,6 +201,8 @@ impl CliMainWorker { .await?; } + eprintln!("hot reload finished2"); + Ok(self.worker.exit_code()) } From 51c541607485de50b11e2ba40c42aa915d13f38d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 03:12:14 +0200 Subject: [PATCH 38/79] Run_hot_replace --- cli/tools/run/hot_reload/mod.rs | 186 ++++++++++++++++---------------- cli/worker.rs | 5 +- 2 files changed, 98 insertions(+), 93 deletions(-) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index f08c7bdeda7e00..cb5d262b54204a 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -56,98 +56,6 @@ impl HotReloadManager { self.disable_debugger().await } - // TODO(bartlomieju): Shouldn't use `tokio::select!` here, as futures are not cancel safe - pub async fn run(&mut self) -> Result<(), AnyError> { - let mut session_rx = self.session.take_notification_rx(); - loop { - select! { - biased; - // TODO(SyrupThinker): Deferred retry with timeout - Some(notification) = session_rx.next() => { - let notification = serde_json::from_value::(notification)?; - // TODO(bartlomieju): this is not great... and the code is duplicated with the REPL. - if notification.method == "Runtime.exceptionThrown" { - let params = notification.params; - let exception_details = params.get("exceptionDetails").unwrap().as_object().unwrap(); - let text = exception_details.get("text").unwrap().as_str().unwrap(); - let exception = exception_details.get("exception").unwrap().as_object().unwrap(); - let description = exception.get("description").and_then(|d| d.as_str()).unwrap_or("undefined"); - break Err(generic_error(format!("{text} {description}"))); - } else if notification.method == "Debugger.scriptParsed" { - let params = serde_json::from_value::(notification.params)?; - if params.url.starts_with("file://") { - self.script_ids.insert(params.url, params.script_id); - } - } - } - changed_paths = self.changed_paths_rx.recv() => { - eprintln!("changed patchs in hot {:#?}", changed_paths); - let changed_paths = changed_paths?; - let filtered_paths: Vec = changed_paths.into_iter().filter(|p| p.extension().map_or(false, |ext| { - let ext_str = ext.to_str().unwrap(); - matches!(ext_str, "js" | "ts" | "jsx" | "tsx") - })).collect(); - - for path in filtered_paths { - let Some(path_str) = path.to_str() else { - let _ = self.restart_tx.send(()); - continue; - }; - let Ok(module_url) = Url::from_file_path(path_str) else { - let _ = self.restart_tx.send(()); - continue; - }; - - log::info!("{} Reloading changed module {}", colors::intense_blue("HMR"), module_url.as_str()); - - let Some(id) = self.script_ids.get(module_url.as_str()).cloned() else { - let _ = self.restart_tx.send(()); - continue; - }; - - // TODO(bartlomieju): I really don't like `self.emitter` etc here. - // Maybe use `deno_ast` directly? - let media_type = MediaType::from_path(&path); - let source_code = tokio::fs::read_to_string(path).await?; - let source_arc: Arc = Arc::from(source_code.as_str()); - let source_code = { - let parsed_source = self.emitter.parsed_source_cache.get_or_parse_module( - &module_url, - source_arc.clone(), - media_type, - )?; - let mut options = self.emitter.emit_options.clone(); - options.inline_source_map = false; - let transpiled_source = parsed_source.transpile(&options)?; - transpiled_source.text.to_string() - // self.emitter.emit_parsed_source(&module_url, media_type, &source_arc)? - }; - - // eprintln!("transpiled source code {:#?}", source_code); - // TODO(bartlomieju): this loop should do 2 retries at most - loop { - let result = self.set_script_source(&id, source_code.as_str()).await?; - - if matches!(result.status, Status::Ok) { - self.dispatch_hmr_event(module_url.as_str()).await?; - break; - } - - log::info!("{} Failed to reload module {}: {}.", colors::intense_blue("HMR"), module_url, colors::gray(result.status.explain())); - if !result.status.should_retry() { - log::info!("{} Restarting the process...", colors::intense_blue("HMR")); - // TODO(bartlomieju): Print into that sending failed? - let _ = self.restart_tx.send(()); - break; - } - } - } - } - _ = self.session.receive_from_v8_session() => {} - } - } - } - // TODO(bartlomieju): this code is duplicated in `cli/tools/coverage/mod.rs` async fn enable_debugger(&mut self) -> Result<(), AnyError> { self @@ -219,3 +127,97 @@ impl HotReloadManager { Ok(()) } } + +// TODO(bartlomieju): Shouldn't use `tokio::select!` here, as futures are not cancel safe +pub async fn run_hot_reload( + hmr_manager: &mut HotReloadManager, +) -> Result<(), AnyError> { + let mut session_rx = hmr_manager.session.take_notification_rx(); + loop { + select! { + biased; + // TODO(SyrupThinker): Deferred retry with timeout + Some(notification) = session_rx.next() => { + let notification = serde_json::from_value::(notification)?; + // TODO(bartlomieju): this is not great... and the code is duplicated with the REPL. + if notification.method == "Runtime.exceptionThrown" { + let params = notification.params; + let exception_details = params.get("exceptionDetails").unwrap().as_object().unwrap(); + let text = exception_details.get("text").unwrap().as_str().unwrap(); + let exception = exception_details.get("exception").unwrap().as_object().unwrap(); + let description = exception.get("description").and_then(|d| d.as_str()).unwrap_or("undefined"); + break Err(generic_error(format!("{text} {description}"))); + } else if notification.method == "Debugger.scriptParsed" { + let params = serde_json::from_value::(notification.params)?; + if params.url.starts_with("file://") { + hmr_manager.script_ids.insert(params.url, params.script_id); + } + } + } + changed_paths = hmr_manager.changed_paths_rx.recv() => { + eprintln!("changed patchs in hot {:#?}", changed_paths); + let changed_paths = changed_paths?; + let filtered_paths: Vec = changed_paths.into_iter().filter(|p| p.extension().map_or(false, |ext| { + let ext_str = ext.to_str().unwrap(); + matches!(ext_str, "js" | "ts" | "jsx" | "tsx") + })).collect(); + + for path in filtered_paths { + let Some(path_str) = path.to_str() else { + let _ = hmr_manager.restart_tx.send(()); + continue; + }; + let Ok(module_url) = Url::from_file_path(path_str) else { + let _ = hmr_manager.restart_tx.send(()); + continue; + }; + + log::info!("{} Reloading changed module {}", colors::intense_blue("HMR"), module_url.as_str()); + + let Some(id) = hmr_manager.script_ids.get(module_url.as_str()).cloned() else { + let _ = hmr_manager.restart_tx.send(()); + continue; + }; + + // TODO(bartlomieju): I really don't like `hmr_manager.emitter` etc here. + // Maybe use `deno_ast` directly? + let media_type = MediaType::from_path(&path); + let source_code = tokio::fs::read_to_string(path).await?; + let source_arc: Arc = Arc::from(source_code.as_str()); + let source_code = { + let parsed_source = hmr_manager.emitter.parsed_source_cache.get_or_parse_module( + &module_url, + source_arc.clone(), + media_type, + )?; + let mut options = hmr_manager.emitter.emit_options.clone(); + options.inline_source_map = false; + let transpiled_source = parsed_source.transpile(&options)?; + transpiled_source.text.to_string() + // hmr_manager.emitter.emit_parsed_source(&module_url, media_type, &source_arc)? + }; + + // eprintln!("transpiled source code {:#?}", source_code); + // TODO(bartlomieju): this loop should do 2 retries at most + loop { + let result = hmr_manager.set_script_source(&id, source_code.as_str()).await?; + + if matches!(result.status, Status::Ok) { + hmr_manager.dispatch_hmr_event(module_url.as_str()).await?; + break; + } + + log::info!("{} Failed to reload module {}: {}.", colors::intense_blue("HMR"), module_url, colors::gray(result.status.explain())); + if !result.status.should_retry() { + log::info!("{} Restarting the process...", colors::intense_blue("HMR")); + // TODO(bartlomieju): Print into that sending failed? + let _ = hmr_manager.restart_tx.send(()); + break; + } + } + } + } + _ = hmr_manager.session.receive_from_v8_session() => {} + } + } +} diff --git a/cli/worker.rs b/cli/worker.rs index 9d17ab14ec3bd8..04aa59ad6d2258 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -52,6 +52,7 @@ use crate::npm::CliNpmResolver; use crate::ops; use crate::tools; use crate::tools::coverage::CoverageCollector; +use crate::tools::run::hot_reload; use crate::tools::run::hot_reload::HotReloadManager; use crate::util::checksum; use crate::util::file_watcher::WatcherInterface; @@ -166,7 +167,9 @@ impl CliMainWorker { eprintln!("start hmr"); self .worker - .with_event_loop_fallible(hot_reload_manager.run().boxed_local()) + .with_event_loop_fallible( + hot_reload::run_hot_reload(hot_reload_manager).boxed_local(), + ) .await?; eprintln!("stop hmr"); } else { From 3298be8c8b69798ed94e24dafeaf3b2a62241af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 03:21:36 +0200 Subject: [PATCH 39/79] fs watcher changes --- cli/factory.rs | 48 ++++++---- cli/standalone/mod.rs | 3 + cli/tools/bench/mod.rs | 6 +- cli/tools/bundle.rs | 6 +- cli/tools/fmt.rs | 3 +- cli/tools/lint.rs | 4 +- cli/tools/test/mod.rs | 6 +- cli/util/file_watcher.rs | 187 ++++++++++++++++++++++++++++----------- cli/worker.rs | 69 ++++++++++++++- 9 files changed, 248 insertions(+), 84 deletions(-) diff --git a/cli/factory.rs b/cli/factory.rs index e4f9b60fe4fd8d..97821346ce50e0 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -40,6 +40,7 @@ use crate::resolver::CliGraphResolver; use crate::resolver::CliGraphResolverOptions; use crate::standalone::DenoCompileBinaryWriter; use crate::tools::check::TypeChecker; +use crate::util::file_watcher::WatcherInterface; use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; use crate::worker::CliMainWorkerFactory; @@ -59,26 +60,19 @@ use deno_runtime::inspector_server::InspectorServer; use deno_semver::npm::NpmPackageReqReference; use import_map::ImportMap; use log::warn; -use std::cell::RefCell; use std::future::Future; -use std::path::PathBuf; use std::sync::Arc; pub struct CliFactoryBuilder { - maybe_sender: Option>>, + // TODO(bartlomieju): this is a bad name; change it + watcher_interface: Option, } impl CliFactoryBuilder { pub fn new() -> Self { - Self { maybe_sender: None } - } - - pub fn with_watcher( - mut self, - sender: tokio::sync::mpsc::UnboundedSender>, - ) -> Self { - self.maybe_sender = Some(sender); - self + Self { + watcher_interface: None, + } } pub async fn build_from_flags( @@ -88,9 +82,18 @@ impl CliFactoryBuilder { Ok(self.build_from_cli_options(Arc::new(CliOptions::from_flags(flags)?))) } + pub async fn build_from_flags_for_watcher( + mut self, + flags: Flags, + watcher_interface: WatcherInterface, + ) -> Result { + self.watcher_interface = Some(watcher_interface); + self.build_from_flags(flags).await + } + pub fn build_from_cli_options(self, options: Arc) -> CliFactory { CliFactory { - maybe_sender: RefCell::new(self.maybe_sender), + watcher_interface: self.watcher_interface, options, services: Default::default(), } @@ -166,8 +169,7 @@ struct CliFactoryServices { } pub struct CliFactory { - maybe_sender: - RefCell>>>, + watcher_interface: Option, options: Arc, services: CliFactoryServices, } @@ -384,11 +386,14 @@ impl CliFactory { } pub fn maybe_file_watcher_reporter(&self) -> &Option { - let maybe_sender = self.maybe_sender.borrow_mut().take(); + let maybe_file_watcher_reporter = self + .watcher_interface + .as_ref() + .map(|i| FileWatcherReporter::new(i.paths_to_watch_tx.clone())); self .services .maybe_file_watcher_reporter - .get_or_init(|| maybe_sender.map(FileWatcherReporter::new)) + .get_or_init(|| maybe_file_watcher_reporter) } pub fn emit_cache(&self) -> Result<&EmitCache, AnyError> { @@ -595,6 +600,12 @@ impl CliFactory { let npm_resolver = self.npm_resolver().await?; let fs = self.fs(); let cli_node_resolver = self.cli_node_resolver().await?; + let maybe_file_watcher_interface = if self.options.has_hot_reload() { + Some(self.watcher_interface.clone().unwrap()) + } else { + None + }; + Ok(CliMainWorkerFactory::new( StorageKeyResolver::from_options(&self.options), npm_resolver.clone(), @@ -617,6 +628,8 @@ impl CliFactory { )), self.root_cert_store_provider().clone(), self.fs().clone(), + Some(self.emitter()?.clone()), + maybe_file_watcher_interface, self.maybe_inspector_server().clone(), self.maybe_lockfile().clone(), self.feature_checker().clone(), @@ -633,6 +646,7 @@ impl CliFactory { coverage_dir: self.options.coverage_dir(), enable_testing_features: self.options.enable_testing_features(), has_node_modules_dir: self.options.has_node_modules_dir(), + hot_reload: self.options.has_hot_reload(), inspect_brk: self.options.inspect_brk().is_some(), inspect_wait: self.options.inspect_wait().is_some(), is_inspecting: self.options.is_inspecting(), diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index 612ae9eeddd800..99efb7b7d12c81 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -446,6 +446,8 @@ pub async fn run( fs, None, None, + None, + None, feature_checker, CliMainWorkerOptions { argv: metadata.argv, @@ -453,6 +455,7 @@ pub async fn run( coverage_dir: None, enable_testing_features: false, has_node_modules_dir, + hot_reload: false, inspect_brk: false, inspect_wait: false, is_inspecting: false, diff --git a/cli/tools/bench/mod.rs b/cli/tools/bench/mod.rs index 454a9712662af0..6edc65424db35f 100644 --- a/cli/tools/bench/mod.rs +++ b/cli/tools/bench/mod.rs @@ -417,12 +417,12 @@ pub async fn run_benchmarks_with_watch( .map(|w| !w.no_clear_screen) .unwrap_or(true), }, - move |flags, sender, changed_paths| { + move |flags, watcher_interface, changed_paths| { let bench_flags = bench_flags.clone(); + let sender = watcher_interface.paths_to_watch_tx.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(sender.clone()) - .build_from_flags(flags) + .build_from_flags_for_watcher(flags, watcher_interface) .await?; let cli_options = factory.cli_options(); let bench_options = cli_options.resolve_bench_options(bench_flags)?; diff --git a/cli/tools/bundle.rs b/cli/tools/bundle.rs index 827641b1b8ec6c..c7ef84c5b94d40 100644 --- a/cli/tools/bundle.rs +++ b/cli/tools/bundle.rs @@ -35,12 +35,12 @@ pub async fn bundle( job_name: "Bundle".to_string(), clear_screen: !watch_flags.no_clear_screen, }, - move |flags, sender, _changed_paths| { + move |flags, watcher_interface, _changed_paths| { + let sender = watcher_interface.paths_to_watch_tx.clone(); let bundle_flags = bundle_flags.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(sender.clone()) - .build_from_flags(flags) + .build_from_flags_for_watcher(flags, watcher_interface) .await?; let cli_options = factory.cli_options(); let _ = sender.send(cli_options.watch_paths()); diff --git a/cli/tools/fmt.rs b/cli/tools/fmt.rs index 284f20ddaa44ee..d04af99e3b69c5 100644 --- a/cli/tools/fmt.rs +++ b/cli/tools/fmt.rs @@ -68,8 +68,9 @@ pub async fn format(flags: Flags, fmt_flags: FmtFlags) -> Result<(), AnyError> { job_name: "Fmt".to_string(), clear_screen: !watch_flags.no_clear_screen, }, - move |flags, sender, changed_paths| { + move |flags, watcher_interface, changed_paths| { let fmt_flags = fmt_flags.clone(); + let sender = watcher_interface.paths_to_watch_tx.clone(); Ok(async move { let factory = CliFactory::from_flags(flags).await?; let cli_options = factory.cli_options(); diff --git a/cli/tools/lint.rs b/cli/tools/lint.rs index 6a308b5990f4d9..21678cb0140ae8 100644 --- a/cli/tools/lint.rs +++ b/cli/tools/lint.rs @@ -63,7 +63,7 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> { job_name: "Lint".to_string(), clear_screen: !watch_flags.no_clear_screen, }, - move |flags, sender, changed_paths| { + move |flags, watcher_interface, changed_paths| { let lint_flags = lint_flags.clone(); Ok(async move { let factory = CliFactory::from_flags(flags).await?; @@ -77,7 +77,7 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> { Ok(files) } })?; - _ = sender.send(files.clone()); + _ = watcher_interface.paths_to_watch_tx.send(files.clone()); let lint_paths = if let Some(paths) = changed_paths { // lint all files on any changed (https://github.com/denoland/deno/issues/12446) diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index b3aadc1e71c77b..f2b8f98e547564 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -1213,12 +1213,12 @@ pub async fn run_tests_with_watch( .map(|w| !w.no_clear_screen) .unwrap_or(true), }, - move |flags, sender, changed_paths| { + move |flags, watcher_interface, changed_paths| { let test_flags = test_flags.clone(); + let sender = watcher_interface.paths_to_watch_tx.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(sender.clone()) - .build_from_flags(flags) + .build_from_flags_for_watcher(flags, watcher_interface) .await?; let cli_options = factory.cli_options(); let test_options = cli_options.resolve_test_options(test_flags)?; diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index c0eda2d863049f..6cbd90647fa43a 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -23,7 +23,6 @@ use std::time::Duration; use tokio::select; use tokio::sync::mpsc; use tokio::sync::mpsc::UnboundedReceiver; -use tokio::sync::mpsc::UnboundedSender; use tokio::time::sleep; const CLEAR_SCREEN: &str = "\x1B[2J\x1B[1;1H"; @@ -109,26 +108,83 @@ fn create_print_after_restart_fn(clear_screen: bool) -> impl Fn() { } } +// TODO(bartlomieju): this is a poor name; change it +/// And interface to interact with Deno's CLI file watcher. +pub struct WatcherInterface { + /// Send a list of paths that should be watched for changes. + pub paths_to_watch_tx: tokio::sync::mpsc::UnboundedSender>, + + /// Listen for a list of paths that were changed. + pub changed_paths_rx: tokio::sync::broadcast::Receiver>, + + /// Send a message to force a restart. + pub restart_tx: tokio::sync::mpsc::UnboundedSender<()>, +} + +impl Clone for WatcherInterface { + 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(), + } + } +} + /// Creates a file watcher. /// /// - `operation` is the actual operation we want to run every time the watcher detects file /// changes. For example, in the case where we would like to bundle, then `operation` would /// have the logic for it like bundling the code. pub async fn watch_func( + flags: Flags, + print_config: PrintConfig, + operation: O, +) -> Result<(), AnyError> +where + O: + FnMut(Flags, WatcherInterface, Option>) -> Result, + F: Future>, +{ + watch_recv( + flags, + print_config, + WatcherRestartMode::Automatic, + operation, + ) + .await +} + +#[derive(Clone, Copy, Debug)] +pub enum WatcherRestartMode { + /// When a file path changes the process is restarted. + Automatic, + + /// When a file path changes the caller will trigger a restart, using + /// `WatcherInterface.restart_tx`. + Manual, +} + +/// Creates a file watcher. +/// +/// - `operation` is the actual operation we want to run every time the watcher detects file +/// changes. For example, in the case where we would like to bundle, then `operation` would +/// have the logic for it like bundling the code. +pub async fn watch_recv( mut flags: Flags, print_config: PrintConfig, + restart_mode: WatcherRestartMode, mut operation: O, ) -> Result<(), AnyError> where - O: FnMut( - Flags, - UnboundedSender>, - Option>, - ) -> Result, + O: + FnMut(Flags, WatcherInterface, Option>) -> Result, F: Future>, { - let (paths_to_watch_sender, mut paths_to_watch_receiver) = + let (paths_to_watch_tx, mut paths_to_watch_rx) = tokio::sync::mpsc::unbounded_channel(); + let (restart_tx, mut restart_rx) = tokio::sync::mpsc::unbounded_channel(); + let (changed_paths_tx, changed_paths_rx) = tokio::sync::broadcast::channel(4); let (watcher_sender, mut watcher_receiver) = DebouncedReceiver::new_with_sender(); @@ -138,29 +194,13 @@ where } = print_config; let print_after_restart = create_print_after_restart_fn(clear_screen); - + let watcher_interface = WatcherInterface { + 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,); - fn consume_paths_to_watch( - watcher: &mut RecommendedWatcher, - receiver: &mut UnboundedReceiver>, - ) { - loop { - match receiver.try_recv() { - Ok(paths) => { - add_paths_to_watcher(watcher, &paths); - } - Err(e) => match e { - mpsc::error::TryRecvError::Empty => { - break; - } - // there must be at least one receiver alive - _ => unreachable!(), - }, - } - } - } - let mut changed_paths = None; loop { // We may need to give the runtime a tick to settle, as cancellations may need to propagate @@ -171,17 +211,18 @@ where } let mut watcher = new_watcher(watcher_sender.clone())?; - consume_paths_to_watch(&mut watcher, &mut paths_to_watch_receiver); + consume_paths_to_watch(&mut watcher, &mut paths_to_watch_rx); let receiver_future = async { loop { - let maybe_paths = paths_to_watch_receiver.recv().await; + let maybe_paths = paths_to_watch_rx.recv().await; + eprintln!("paths to watch paths {:?}", maybe_paths); add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); } }; let operation_future = error_handler(operation( flags.clone(), - paths_to_watch_sender.clone(), + watcher_interface.clone(), changed_paths.take(), )?); @@ -190,13 +231,29 @@ where select! { _ = receiver_future => {}, - received_changed_paths = watcher_receiver.recv() => { + _ = restart_rx.recv() => { print_after_restart(); - changed_paths = received_changed_paths; continue; }, + received_changed_paths = watcher_receiver.recv() => { + eprintln!("received paths {:?}", received_changed_paths); + changed_paths = received_changed_paths.clone(); + + match restart_mode { + WatcherRestartMode::Automatic => { + continue; + }, + WatcherRestartMode::Manual => { + // TODO(bartlomieju): should we fail on sending changed paths? + // TODO(bartlomieju): change channel to accept Option<> + if let Some(received_changed_paths) = received_changed_paths { + let _ = changed_paths_tx.send(received_changed_paths); + } + } + } + }, success = operation_future => { - consume_paths_to_watch(&mut watcher, &mut paths_to_watch_receiver); + consume_paths_to_watch(&mut watcher, &mut paths_to_watch_rx); // TODO(bartlomieju): print exit code here? info!( "{} {} {}. Restarting on file change...", @@ -213,13 +270,19 @@ where let receiver_future = async { loop { - let maybe_paths = paths_to_watch_receiver.recv().await; + let maybe_paths = paths_to_watch_rx.recv().await; + eprintln!("paths to watch paths {:?}", maybe_paths); add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); } }; + + // If we got this far, it means that the `operation` has finished; let's wait + // and see if there are any new paths to watch received or any of the already + // watched paths has changed. select! { _ = receiver_future => {}, received_changed_paths = watcher_receiver.recv() => { + eprintln!("received paths {:?}", received_changed_paths); print_after_restart(); changed_paths = received_changed_paths; continue; @@ -231,26 +294,28 @@ where fn new_watcher( sender: Arc>>, ) -> Result { - let watcher = Watcher::new( + Ok(Watcher::new( move |res: Result| { - if let Ok(event) = res { - if matches!( - event.kind, - EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_) - ) { - let paths = event - .paths - .iter() - .filter_map(|path| canonicalize_path(path).ok()) - .collect(); - sender.send(paths).unwrap(); - } + let Ok(event) = res else { + return; + }; + + if !matches!( + event.kind, + EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_) + ) { + return; } + + let paths = event + .paths + .iter() + .filter_map(|path| canonicalize_path(path).ok()) + .collect(); + sender.send(paths).unwrap(); }, Default::default(), - )?; - - Ok(watcher) + )?) } fn add_paths_to_watcher(watcher: &mut RecommendedWatcher, paths: &[PathBuf]) { @@ -260,3 +325,23 @@ fn add_paths_to_watcher(watcher: &mut RecommendedWatcher, paths: &[PathBuf]) { } log::debug!("Watching paths: {:?}", paths); } + +fn consume_paths_to_watch( + watcher: &mut RecommendedWatcher, + receiver: &mut UnboundedReceiver>, +) { + loop { + match receiver.try_recv() { + Ok(paths) => { + add_paths_to_watcher(watcher, &paths); + } + Err(e) => match e { + mpsc::error::TryRecvError::Empty => { + break; + } + // there must be at least one receiver alive + _ => unreachable!(), + }, + } + } +} diff --git a/cli/worker.rs b/cli/worker.rs index d8738d49217e53..04aa59ad6d2258 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -46,12 +46,16 @@ use deno_semver::package::PackageReqReference; use crate::args::package_json::PackageJsonDeps; use crate::args::StorageKeyResolver; +use crate::emit::Emitter; use crate::errors; use crate::npm::CliNpmResolver; use crate::ops; use crate::tools; use crate::tools::coverage::CoverageCollector; +use crate::tools::run::hot_reload; +use crate::tools::run::hot_reload::HotReloadManager; use crate::util::checksum; +use crate::util::file_watcher::WatcherInterface; use crate::version; pub trait ModuleLoaderFactory: Send + Sync { @@ -83,6 +87,7 @@ pub struct CliMainWorkerOptions { pub coverage_dir: Option, pub enable_testing_features: bool, pub has_node_modules_dir: bool, + pub hot_reload: bool, pub inspect_brk: bool, pub inspect_wait: bool, pub is_inspecting: bool, @@ -108,6 +113,8 @@ struct SharedWorkerState { module_loader_factory: Box, root_cert_store_provider: Arc, fs: Arc, + emitter: Option>, + maybe_file_watcher_interface: Option, maybe_inspector_server: Option>, maybe_lockfile: Option>>, feature_checker: Arc, @@ -137,6 +144,9 @@ impl CliMainWorker { pub async fn run(&mut self) -> Result { let mut maybe_coverage_collector = self.maybe_setup_coverage_collector().await?; + let mut maybe_hot_reload_manager = + self.maybe_setup_hot_reload_manager().await?; + log::debug!("main_module {}", self.main_module); if self.is_main_cjs { @@ -153,10 +163,22 @@ impl CliMainWorker { self.worker.dispatch_load_event(located_script_name!())?; loop { - self - .worker - .run_event_loop(maybe_coverage_collector.is_none()) - .await?; + if let Some(hot_reload_manager) = maybe_hot_reload_manager.as_mut() { + eprintln!("start hmr"); + self + .worker + .with_event_loop_fallible( + hot_reload::run_hot_reload(hot_reload_manager).boxed_local(), + ) + .await?; + eprintln!("stop hmr"); + } else { + self + .worker + .run_event_loop(maybe_coverage_collector.is_none()) + .await?; + } + if !self .worker .dispatch_beforeunload_event(located_script_name!())? @@ -165,6 +187,8 @@ impl CliMainWorker { } } + eprintln!("hot reload finished"); + self.worker.dispatch_unload_event(located_script_name!())?; if let Some(coverage_collector) = maybe_coverage_collector.as_mut() { @@ -173,6 +197,14 @@ impl CliMainWorker { .with_event_loop(coverage_collector.stop_collecting().boxed_local()) .await?; } + if let Some(hot_reload_manager) = maybe_hot_reload_manager.as_mut() { + self + .worker + .with_event_loop(hot_reload_manager.stop().boxed_local()) + .await?; + } + + eprintln!("hot reload finished2"); Ok(self.worker.exit_code()) } @@ -287,6 +319,31 @@ impl CliMainWorker { } } + pub async fn maybe_setup_hot_reload_manager( + &mut self, + ) -> Result, AnyError> { + if !self.shared.options.hot_reload { + return Ok(None); + } + + let interface = self.shared.maybe_file_watcher_interface.clone().unwrap(); + + // TODO(bartlomieju): this is a code smell, refactor so we don't have + // to pass `emitter` here + let emitter = self.shared.emitter.clone().unwrap(); + + let session = self.worker.create_inspector_session().await; + let mut hot_reload_manager = + HotReloadManager::new(emitter, session, interface); + + self + .worker + .with_event_loop(hot_reload_manager.start().boxed_local()) + .await?; + + Ok(Some(hot_reload_manager)) + } + pub fn execute_script_static( &mut self, name: &'static str, @@ -313,6 +370,8 @@ impl CliMainWorkerFactory { module_loader_factory: Box, root_cert_store_provider: Arc, fs: Arc, + emitter: Option>, + maybe_file_watcher_interface: Option, maybe_inspector_server: Option>, maybe_lockfile: Option>>, feature_checker: Arc, @@ -330,7 +389,9 @@ impl CliMainWorkerFactory { compiled_wasm_module_store: Default::default(), module_loader_factory, root_cert_store_provider, + emitter, fs, + maybe_file_watcher_interface, maybe_inspector_server, maybe_lockfile, feature_checker, From d916d450721676da548ebef8d51d18df8157d1c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 03:55:32 +0200 Subject: [PATCH 40/79] revert unrelated changes --- cli/factory.rs | 8 ----- cli/standalone/mod.rs | 3 -- cli/tools/run.rs | 9 +++--- cli/util/file_watcher.rs | 6 ++-- cli/worker.rs | 67 +++------------------------------------- 5 files changed, 11 insertions(+), 82 deletions(-) diff --git a/cli/factory.rs b/cli/factory.rs index 97821346ce50e0..4f7a2eb18d8f88 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -600,11 +600,6 @@ impl CliFactory { let npm_resolver = self.npm_resolver().await?; let fs = self.fs(); let cli_node_resolver = self.cli_node_resolver().await?; - let maybe_file_watcher_interface = if self.options.has_hot_reload() { - Some(self.watcher_interface.clone().unwrap()) - } else { - None - }; Ok(CliMainWorkerFactory::new( StorageKeyResolver::from_options(&self.options), @@ -628,8 +623,6 @@ impl CliFactory { )), self.root_cert_store_provider().clone(), self.fs().clone(), - Some(self.emitter()?.clone()), - maybe_file_watcher_interface, self.maybe_inspector_server().clone(), self.maybe_lockfile().clone(), self.feature_checker().clone(), @@ -646,7 +639,6 @@ impl CliFactory { coverage_dir: self.options.coverage_dir(), enable_testing_features: self.options.enable_testing_features(), has_node_modules_dir: self.options.has_node_modules_dir(), - hot_reload: self.options.has_hot_reload(), inspect_brk: self.options.inspect_brk().is_some(), inspect_wait: self.options.inspect_wait().is_some(), is_inspecting: self.options.is_inspecting(), diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index 99efb7b7d12c81..612ae9eeddd800 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -446,8 +446,6 @@ pub async fn run( fs, None, None, - None, - None, feature_checker, CliMainWorkerOptions { argv: metadata.argv, @@ -455,7 +453,6 @@ pub async fn run( coverage_dir: None, enable_testing_features: false, has_node_modules_dir, - hot_reload: false, inspect_brk: false, inspect_wait: false, is_inspecting: false, diff --git a/cli/tools/run.rs b/cli/tools/run.rs index 5fb31a4ad7c429..ed99ee91cd7a98 100644 --- a/cli/tools/run.rs +++ b/cli/tools/run.rs @@ -110,18 +110,19 @@ async fn run_with_watch( job_name: "Process".to_string(), clear_screen: !watch_flags.no_clear_screen, }, - move |flags, sender, _changed_paths| { + move |flags, watcher_interface, _changed_paths| { Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(sender.clone()) - .build_from_flags(flags) + .build_from_flags_for_watcher(flags, watcher_interface.clone()) .await?; let cli_options = factory.cli_options(); let main_module = cli_options.resolve_main_module()?; maybe_npm_install(&factory).await?; - let _ = sender.send(cli_options.watch_paths()); + let _ = watcher_interface + .paths_to_watch_tx + .send(cli_options.watch_paths()); let permissions = PermissionsContainer::new(Permissions::from_options( &cli_options.permissions_options(), diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 6cbd90647fa43a..ea68222e188294 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -162,6 +162,8 @@ pub enum WatcherRestartMode { /// When a file path changes the caller will trigger a restart, using /// `WatcherInterface.restart_tx`. + // TODO(bartlomieju): this mode will be used in a follow up PR + #[allow(dead_code)] Manual, } @@ -216,7 +218,6 @@ where let receiver_future = async { loop { let maybe_paths = paths_to_watch_rx.recv().await; - eprintln!("paths to watch paths {:?}", maybe_paths); add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); } }; @@ -236,7 +237,6 @@ where continue; }, received_changed_paths = watcher_receiver.recv() => { - eprintln!("received paths {:?}", received_changed_paths); changed_paths = received_changed_paths.clone(); match restart_mode { @@ -271,7 +271,6 @@ where let receiver_future = async { loop { let maybe_paths = paths_to_watch_rx.recv().await; - eprintln!("paths to watch paths {:?}", maybe_paths); add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); } }; @@ -282,7 +281,6 @@ where select! { _ = receiver_future => {}, received_changed_paths = watcher_receiver.recv() => { - eprintln!("received paths {:?}", received_changed_paths); print_after_restart(); changed_paths = received_changed_paths; continue; diff --git a/cli/worker.rs b/cli/worker.rs index 04aa59ad6d2258..c93d155315d03c 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -46,16 +46,12 @@ use deno_semver::package::PackageReqReference; use crate::args::package_json::PackageJsonDeps; use crate::args::StorageKeyResolver; -use crate::emit::Emitter; use crate::errors; use crate::npm::CliNpmResolver; use crate::ops; use crate::tools; use crate::tools::coverage::CoverageCollector; -use crate::tools::run::hot_reload; -use crate::tools::run::hot_reload::HotReloadManager; use crate::util::checksum; -use crate::util::file_watcher::WatcherInterface; use crate::version; pub trait ModuleLoaderFactory: Send + Sync { @@ -87,7 +83,6 @@ pub struct CliMainWorkerOptions { pub coverage_dir: Option, pub enable_testing_features: bool, pub has_node_modules_dir: bool, - pub hot_reload: bool, pub inspect_brk: bool, pub inspect_wait: bool, pub is_inspecting: bool, @@ -113,8 +108,6 @@ struct SharedWorkerState { module_loader_factory: Box, root_cert_store_provider: Arc, fs: Arc, - emitter: Option>, - maybe_file_watcher_interface: Option, maybe_inspector_server: Option>, maybe_lockfile: Option>>, feature_checker: Arc, @@ -144,8 +137,6 @@ impl CliMainWorker { pub async fn run(&mut self) -> Result { let mut maybe_coverage_collector = self.maybe_setup_coverage_collector().await?; - let mut maybe_hot_reload_manager = - self.maybe_setup_hot_reload_manager().await?; log::debug!("main_module {}", self.main_module); @@ -163,21 +154,10 @@ impl CliMainWorker { self.worker.dispatch_load_event(located_script_name!())?; loop { - if let Some(hot_reload_manager) = maybe_hot_reload_manager.as_mut() { - eprintln!("start hmr"); - self - .worker - .with_event_loop_fallible( - hot_reload::run_hot_reload(hot_reload_manager).boxed_local(), - ) - .await?; - eprintln!("stop hmr"); - } else { - self - .worker - .run_event_loop(maybe_coverage_collector.is_none()) - .await?; - } + self + .worker + .run_event_loop(maybe_coverage_collector.is_none()) + .await?; if !self .worker @@ -187,8 +167,6 @@ impl CliMainWorker { } } - eprintln!("hot reload finished"); - self.worker.dispatch_unload_event(located_script_name!())?; if let Some(coverage_collector) = maybe_coverage_collector.as_mut() { @@ -197,14 +175,6 @@ impl CliMainWorker { .with_event_loop(coverage_collector.stop_collecting().boxed_local()) .await?; } - if let Some(hot_reload_manager) = maybe_hot_reload_manager.as_mut() { - self - .worker - .with_event_loop(hot_reload_manager.stop().boxed_local()) - .await?; - } - - eprintln!("hot reload finished2"); Ok(self.worker.exit_code()) } @@ -319,31 +289,6 @@ impl CliMainWorker { } } - pub async fn maybe_setup_hot_reload_manager( - &mut self, - ) -> Result, AnyError> { - if !self.shared.options.hot_reload { - return Ok(None); - } - - let interface = self.shared.maybe_file_watcher_interface.clone().unwrap(); - - // TODO(bartlomieju): this is a code smell, refactor so we don't have - // to pass `emitter` here - let emitter = self.shared.emitter.clone().unwrap(); - - let session = self.worker.create_inspector_session().await; - let mut hot_reload_manager = - HotReloadManager::new(emitter, session, interface); - - self - .worker - .with_event_loop(hot_reload_manager.start().boxed_local()) - .await?; - - Ok(Some(hot_reload_manager)) - } - pub fn execute_script_static( &mut self, name: &'static str, @@ -370,8 +315,6 @@ impl CliMainWorkerFactory { module_loader_factory: Box, root_cert_store_provider: Arc, fs: Arc, - emitter: Option>, - maybe_file_watcher_interface: Option, maybe_inspector_server: Option>, maybe_lockfile: Option>>, feature_checker: Arc, @@ -389,9 +332,7 @@ impl CliMainWorkerFactory { compiled_wasm_module_store: Default::default(), module_loader_factory, root_cert_store_provider, - emitter, fs, - maybe_file_watcher_interface, maybe_inspector_server, maybe_lockfile, feature_checker, From 104257ace59b31ca1b691a5b954f61c0493259d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 03:56:21 +0200 Subject: [PATCH 41/79] revert unrelated changes --- cli/worker.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/cli/worker.rs b/cli/worker.rs index c93d155315d03c..d8738d49217e53 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -137,7 +137,6 @@ impl CliMainWorker { pub async fn run(&mut self) -> Result { let mut maybe_coverage_collector = self.maybe_setup_coverage_collector().await?; - log::debug!("main_module {}", self.main_module); if self.is_main_cjs { @@ -158,7 +157,6 @@ impl CliMainWorker { .worker .run_event_loop(maybe_coverage_collector.is_none()) .await?; - if !self .worker .dispatch_beforeunload_event(located_script_name!())? From d040ba7f4157b66285a5d05b08bc043f56722332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 13:23:18 +0200 Subject: [PATCH 42/79] print before restart --- cli/util/file_watcher.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index ea68222e188294..bc438c2f95ac5a 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -241,6 +241,7 @@ where match restart_mode { WatcherRestartMode::Automatic => { + print_after_restart(); continue; }, WatcherRestartMode::Manual => { From a1c0512e2b4634fed1a6d84aab5c60ce9863d644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 13:25:00 +0200 Subject: [PATCH 43/79] remove one todo --- cli/util/file_watcher.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index bc438c2f95ac5a..d3ca3f3762ae51 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -115,7 +115,7 @@ pub struct WatcherInterface { pub paths_to_watch_tx: tokio::sync::mpsc::UnboundedSender>, /// Listen for a list of paths that were changed. - pub changed_paths_rx: tokio::sync::broadcast::Receiver>, + pub changed_paths_rx: tokio::sync::broadcast::Receiver>>, /// Send a message to force a restart. pub restart_tx: tokio::sync::mpsc::UnboundedSender<()>, @@ -247,9 +247,7 @@ where WatcherRestartMode::Manual => { // TODO(bartlomieju): should we fail on sending changed paths? // TODO(bartlomieju): change channel to accept Option<> - if let Some(received_changed_paths) = received_changed_paths { - let _ = changed_paths_tx.send(received_changed_paths); - } + let _ = changed_paths_tx.send(received_changed_paths); } } }, From 0700b5f9166934d58e5f0bdd837ab31aafe64d69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 14:04:54 +0200 Subject: [PATCH 44/79] remove stale todo --- cli/util/file_watcher.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index d3ca3f3762ae51..f99e295b91313e 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -246,7 +246,6 @@ where }, WatcherRestartMode::Manual => { // TODO(bartlomieju): should we fail on sending changed paths? - // TODO(bartlomieju): change channel to accept Option<> let _ = changed_paths_tx.send(received_changed_paths); } } From 4ff0cc6d8e3e1ae809e6652e1b475a5c6057cd80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 14:07:57 +0200 Subject: [PATCH 45/79] revert some changes to not overflow the stack on Windows --- cli/factory.rs | 14 +++++--------- cli/tools/bench/mod.rs | 3 ++- cli/tools/bundle.rs | 3 ++- cli/tools/run.rs | 3 ++- cli/tools/test/mod.rs | 3 ++- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cli/factory.rs b/cli/factory.rs index 4f7a2eb18d8f88..a633a04f02648a 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -75,6 +75,11 @@ impl CliFactoryBuilder { } } + pub fn with_watcher(mut self, interface: WatcherInterface) -> Self { + self.watcher_interface = Some(interface); + self + } + pub async fn build_from_flags( self, flags: Flags, @@ -82,15 +87,6 @@ impl CliFactoryBuilder { Ok(self.build_from_cli_options(Arc::new(CliOptions::from_flags(flags)?))) } - pub async fn build_from_flags_for_watcher( - mut self, - flags: Flags, - watcher_interface: WatcherInterface, - ) -> Result { - self.watcher_interface = Some(watcher_interface); - self.build_from_flags(flags).await - } - pub fn build_from_cli_options(self, options: Arc) -> CliFactory { CliFactory { watcher_interface: self.watcher_interface, diff --git a/cli/tools/bench/mod.rs b/cli/tools/bench/mod.rs index 6edc65424db35f..6889004ebe8607 100644 --- a/cli/tools/bench/mod.rs +++ b/cli/tools/bench/mod.rs @@ -422,7 +422,8 @@ pub async fn run_benchmarks_with_watch( let sender = watcher_interface.paths_to_watch_tx.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .build_from_flags_for_watcher(flags, watcher_interface) + .with_watcher(watcher_interface) + .build_from_flags(flags) .await?; let cli_options = factory.cli_options(); let bench_options = cli_options.resolve_bench_options(bench_flags)?; diff --git a/cli/tools/bundle.rs b/cli/tools/bundle.rs index c7ef84c5b94d40..e9429b4b8553f1 100644 --- a/cli/tools/bundle.rs +++ b/cli/tools/bundle.rs @@ -40,7 +40,8 @@ pub async fn bundle( let bundle_flags = bundle_flags.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .build_from_flags_for_watcher(flags, watcher_interface) + .with_watcher(watcher_interface) + .build_from_flags(flags) .await?; let cli_options = factory.cli_options(); let _ = sender.send(cli_options.watch_paths()); diff --git a/cli/tools/run.rs b/cli/tools/run.rs index ed99ee91cd7a98..ca39c91ead766d 100644 --- a/cli/tools/run.rs +++ b/cli/tools/run.rs @@ -113,7 +113,8 @@ async fn run_with_watch( move |flags, watcher_interface, _changed_paths| { Ok(async move { let factory = CliFactoryBuilder::new() - .build_from_flags_for_watcher(flags, watcher_interface.clone()) + .with_watcher(watcher_interface.clone()) + .build_from_flags(flags) .await?; let cli_options = factory.cli_options(); let main_module = cli_options.resolve_main_module()?; diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index f2b8f98e547564..aeb2b61c1582db 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -1218,7 +1218,8 @@ pub async fn run_tests_with_watch( let sender = watcher_interface.paths_to_watch_tx.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .build_from_flags_for_watcher(flags, watcher_interface) + .with_watcher(watcher_interface) + .build_from_flags(flags) .await?; let cli_options = factory.cli_options(); let test_options = cli_options.resolve_test_options(test_flags)?; From 11bf7fbe608896558dcce1a2df00fb8659b7a326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 17:02:47 +0200 Subject: [PATCH 46/79] try to prevent stack overflow on Windows --- cli/util/file_watcher.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index f99e295b91313e..71cbafd68b4a0a 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -7,6 +7,7 @@ use crate::util::fs::canonicalize_path; use deno_core::error::AnyError; use deno_core::error::JsError; use deno_core::futures::Future; +use deno_core::futures::FutureExt; use deno_runtime::fmt_errors::format_js_error; use log::info; use notify::event::Event as NotifyEvent; @@ -146,13 +147,15 @@ where FnMut(Flags, WatcherInterface, Option>) -> Result, F: Future>, { - watch_recv( + let fut = watch_recv( flags, print_config, WatcherRestartMode::Automatic, operation, ) - .await + .boxed_local(); + + fut.await } #[derive(Clone, Copy, Debug)] From c565d52b148013466243546c9609249d0722d31d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 20:34:42 +0200 Subject: [PATCH 47/79] typo --- cli/util/file_watcher.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 71cbafd68b4a0a..23b0179b91831f 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -110,7 +110,7 @@ fn create_print_after_restart_fn(clear_screen: bool) -> impl Fn() { } // TODO(bartlomieju): this is a poor name; change it -/// And interface to interact with Deno's CLI file watcher. +/// An interface to interact with Deno's CLI file watcher. pub struct WatcherInterface { /// Send a list of paths that should be watched for changes. pub paths_to_watch_tx: tokio::sync::mpsc::UnboundedSender>, From 3eca45766627f05fe5c76cdd47f2ef56fb609b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 18 Oct 2023 20:34:49 +0200 Subject: [PATCH 48/79] Revert "revert some changes to not overflow the stack on Windows" This reverts commit 4ff0cc6d8e3e1ae809e6652e1b475a5c6057cd80. --- cli/factory.rs | 14 +++++++++----- cli/tools/bench/mod.rs | 3 +-- cli/tools/bundle.rs | 3 +-- cli/tools/run.rs | 3 +-- cli/tools/test/mod.rs | 3 +-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cli/factory.rs b/cli/factory.rs index a633a04f02648a..4f7a2eb18d8f88 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -75,11 +75,6 @@ impl CliFactoryBuilder { } } - pub fn with_watcher(mut self, interface: WatcherInterface) -> Self { - self.watcher_interface = Some(interface); - self - } - pub async fn build_from_flags( self, flags: Flags, @@ -87,6 +82,15 @@ impl CliFactoryBuilder { Ok(self.build_from_cli_options(Arc::new(CliOptions::from_flags(flags)?))) } + pub async fn build_from_flags_for_watcher( + mut self, + flags: Flags, + watcher_interface: WatcherInterface, + ) -> Result { + self.watcher_interface = Some(watcher_interface); + self.build_from_flags(flags).await + } + pub fn build_from_cli_options(self, options: Arc) -> CliFactory { CliFactory { watcher_interface: self.watcher_interface, diff --git a/cli/tools/bench/mod.rs b/cli/tools/bench/mod.rs index 6889004ebe8607..6edc65424db35f 100644 --- a/cli/tools/bench/mod.rs +++ b/cli/tools/bench/mod.rs @@ -422,8 +422,7 @@ pub async fn run_benchmarks_with_watch( let sender = watcher_interface.paths_to_watch_tx.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(watcher_interface) - .build_from_flags(flags) + .build_from_flags_for_watcher(flags, watcher_interface) .await?; let cli_options = factory.cli_options(); let bench_options = cli_options.resolve_bench_options(bench_flags)?; diff --git a/cli/tools/bundle.rs b/cli/tools/bundle.rs index e9429b4b8553f1..c7ef84c5b94d40 100644 --- a/cli/tools/bundle.rs +++ b/cli/tools/bundle.rs @@ -40,8 +40,7 @@ pub async fn bundle( let bundle_flags = bundle_flags.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(watcher_interface) - .build_from_flags(flags) + .build_from_flags_for_watcher(flags, watcher_interface) .await?; let cli_options = factory.cli_options(); let _ = sender.send(cli_options.watch_paths()); diff --git a/cli/tools/run.rs b/cli/tools/run.rs index ca39c91ead766d..ed99ee91cd7a98 100644 --- a/cli/tools/run.rs +++ b/cli/tools/run.rs @@ -113,8 +113,7 @@ async fn run_with_watch( move |flags, watcher_interface, _changed_paths| { Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(watcher_interface.clone()) - .build_from_flags(flags) + .build_from_flags_for_watcher(flags, watcher_interface.clone()) .await?; let cli_options = factory.cli_options(); let main_module = cli_options.resolve_main_module()?; diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index aeb2b61c1582db..f2b8f98e547564 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -1218,8 +1218,7 @@ pub async fn run_tests_with_watch( let sender = watcher_interface.paths_to_watch_tx.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .with_watcher(watcher_interface) - .build_from_flags(flags) + .build_from_flags_for_watcher(flags, watcher_interface) .await?; let cli_options = factory.cli_options(); let test_options = cli_options.resolve_test_options(test_flags)?; From c065dd7e9d0f4713df2095aa326a8d22f01dfe3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 19 Oct 2023 05:16:49 +0200 Subject: [PATCH 49/79] review --- cli/factory.rs | 19 +++++++++---------- cli/graph_util.rs | 12 ++++++++---- cli/tools/bench/mod.rs | 9 ++++----- cli/tools/bundle.rs | 7 +++---- cli/tools/fmt.rs | 5 ++--- cli/tools/lint.rs | 4 ++-- cli/tools/run.rs | 8 +++----- cli/tools/test/mod.rs | 9 ++++----- cli/util/file_watcher.rs | 38 +++++++++++++++++++++++++------------- 9 files changed, 60 insertions(+), 51 deletions(-) diff --git a/cli/factory.rs b/cli/factory.rs index 4f7a2eb18d8f88..2841482f809a6a 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -40,7 +40,7 @@ use crate::resolver::CliGraphResolver; use crate::resolver::CliGraphResolverOptions; use crate::standalone::DenoCompileBinaryWriter; use crate::tools::check::TypeChecker; -use crate::util::file_watcher::WatcherInterface; +use crate::util::file_watcher::WatcherCommunicator; use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; use crate::worker::CliMainWorkerFactory; @@ -64,14 +64,13 @@ use std::future::Future; use std::sync::Arc; pub struct CliFactoryBuilder { - // TODO(bartlomieju): this is a bad name; change it - watcher_interface: Option, + watcher_communicator: Option, } impl CliFactoryBuilder { pub fn new() -> Self { Self { - watcher_interface: None, + watcher_communicator: None, } } @@ -85,15 +84,15 @@ impl CliFactoryBuilder { pub async fn build_from_flags_for_watcher( mut self, flags: Flags, - watcher_interface: WatcherInterface, + watcher_communicator: WatcherCommunicator, ) -> Result { - self.watcher_interface = Some(watcher_interface); + self.watcher_communicator = Some(watcher_communicator); self.build_from_flags(flags).await } pub fn build_from_cli_options(self, options: Arc) -> CliFactory { CliFactory { - watcher_interface: self.watcher_interface, + watcher_communicator: self.watcher_communicator, options, services: Default::default(), } @@ -169,7 +168,7 @@ struct CliFactoryServices { } pub struct CliFactory { - watcher_interface: Option, + watcher_communicator: Option, options: Arc, services: CliFactoryServices, } @@ -387,9 +386,9 @@ impl CliFactory { pub fn maybe_file_watcher_reporter(&self) -> &Option { let maybe_file_watcher_reporter = self - .watcher_interface + .watcher_communicator .as_ref() - .map(|i| FileWatcherReporter::new(i.paths_to_watch_tx.clone())); + .map(|i| FileWatcherReporter::new(i.clone())); self .services .maybe_file_watcher_reporter diff --git a/cli/graph_util.rs b/cli/graph_util.rs index 17437ca997f228..b90581a145e238 100644 --- a/cli/graph_util.rs +++ b/cli/graph_util.rs @@ -13,6 +13,7 @@ use crate::npm::CliNpmResolver; use crate::resolver::CliGraphResolver; use crate::tools::check; use crate::tools::check::TypeChecker; +use crate::util::file_watcher::WatcherCommunicator; use crate::util::sync::TaskQueue; use crate::util::sync::TaskQueuePermit; @@ -635,14 +636,14 @@ impl<'a> ModuleGraphUpdatePermit<'a> { #[derive(Clone, Debug)] pub struct FileWatcherReporter { - sender: tokio::sync::mpsc::UnboundedSender>, + watcher_communicator: WatcherCommunicator, file_paths: Arc>>, } impl FileWatcherReporter { - pub fn new(sender: tokio::sync::mpsc::UnboundedSender>) -> Self { + pub fn new(watcher_communicator: WatcherCommunicator) -> Self { Self { - sender, + watcher_communicator, file_paths: Default::default(), } } @@ -665,7 +666,10 @@ impl deno_graph::source::Reporter for FileWatcherReporter { } if modules_done == modules_total { - self.sender.send(file_paths.drain(..).collect()).unwrap(); + self + .watcher_communicator + .watch_paths(file_paths.drain(..).collect()) + .unwrap(); } } } diff --git a/cli/tools/bench/mod.rs b/cli/tools/bench/mod.rs index 6edc65424db35f..eb400442e2877e 100644 --- a/cli/tools/bench/mod.rs +++ b/cli/tools/bench/mod.rs @@ -417,19 +417,18 @@ pub async fn run_benchmarks_with_watch( .map(|w| !w.no_clear_screen) .unwrap_or(true), }, - move |flags, watcher_interface, changed_paths| { + move |flags, watcher_communicator, changed_paths| { let bench_flags = bench_flags.clone(); - let sender = watcher_interface.paths_to_watch_tx.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .build_from_flags_for_watcher(flags, watcher_interface) + .build_from_flags_for_watcher(flags, watcher_communicator.clone()) .await?; let cli_options = factory.cli_options(); let bench_options = cli_options.resolve_bench_options(bench_flags)?; - let _ = sender.send(cli_options.watch_paths()); + let _ = watcher_communicator.watch_paths(cli_options.watch_paths()); if let Some(include) = &bench_options.files.include { - let _ = sender.send(include.clone()); + let _ = watcher_communicator.watch_paths(include.clone()); } let graph_kind = cli_options.type_check_mode().as_graph_kind(); diff --git a/cli/tools/bundle.rs b/cli/tools/bundle.rs index c7ef84c5b94d40..cbde8768fd7777 100644 --- a/cli/tools/bundle.rs +++ b/cli/tools/bundle.rs @@ -35,15 +35,14 @@ pub async fn bundle( job_name: "Bundle".to_string(), clear_screen: !watch_flags.no_clear_screen, }, - move |flags, watcher_interface, _changed_paths| { - let sender = watcher_interface.paths_to_watch_tx.clone(); + move |flags, watcher_communicator, _changed_paths| { let bundle_flags = bundle_flags.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .build_from_flags_for_watcher(flags, watcher_interface) + .build_from_flags_for_watcher(flags, watcher_communicator.clone()) .await?; let cli_options = factory.cli_options(); - let _ = sender.send(cli_options.watch_paths()); + let _ = watcher_communicator.watch_paths(cli_options.watch_paths()); bundle_action(factory, &bundle_flags).await?; Ok(()) diff --git a/cli/tools/fmt.rs b/cli/tools/fmt.rs index d04af99e3b69c5..b9525b7b2e43fe 100644 --- a/cli/tools/fmt.rs +++ b/cli/tools/fmt.rs @@ -68,9 +68,8 @@ pub async fn format(flags: Flags, fmt_flags: FmtFlags) -> Result<(), AnyError> { job_name: "Fmt".to_string(), clear_screen: !watch_flags.no_clear_screen, }, - move |flags, watcher_interface, changed_paths| { + move |flags, watcher_communicator, changed_paths| { let fmt_flags = fmt_flags.clone(); - let sender = watcher_interface.paths_to_watch_tx.clone(); Ok(async move { let factory = CliFactory::from_flags(flags).await?; let cli_options = factory.cli_options(); @@ -83,7 +82,7 @@ pub async fn format(flags: Flags, fmt_flags: FmtFlags) -> Result<(), AnyError> { Ok(files) } })?; - _ = sender.send(files.clone()); + _ = watcher_communicator.watch_paths(files.clone()); let refmt_files = if let Some(paths) = changed_paths { if fmt_options.check { // check all files on any changed (https://github.com/denoland/deno/issues/12446) diff --git a/cli/tools/lint.rs b/cli/tools/lint.rs index 21678cb0140ae8..b7f4a3f0d9b911 100644 --- a/cli/tools/lint.rs +++ b/cli/tools/lint.rs @@ -63,7 +63,7 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> { job_name: "Lint".to_string(), clear_screen: !watch_flags.no_clear_screen, }, - move |flags, watcher_interface, changed_paths| { + move |flags, watcher_communicator, changed_paths| { let lint_flags = lint_flags.clone(); Ok(async move { let factory = CliFactory::from_flags(flags).await?; @@ -77,7 +77,7 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> { Ok(files) } })?; - _ = watcher_interface.paths_to_watch_tx.send(files.clone()); + _ = watcher_communicator.watch_paths(files.clone()); let lint_paths = if let Some(paths) = changed_paths { // lint all files on any changed (https://github.com/denoland/deno/issues/12446) diff --git a/cli/tools/run.rs b/cli/tools/run.rs index ed99ee91cd7a98..80e80577e93b31 100644 --- a/cli/tools/run.rs +++ b/cli/tools/run.rs @@ -110,19 +110,17 @@ async fn run_with_watch( job_name: "Process".to_string(), clear_screen: !watch_flags.no_clear_screen, }, - move |flags, watcher_interface, _changed_paths| { + move |flags, watcher_communicator, _changed_paths| { Ok(async move { let factory = CliFactoryBuilder::new() - .build_from_flags_for_watcher(flags, watcher_interface.clone()) + .build_from_flags_for_watcher(flags, watcher_communicator.clone()) .await?; let cli_options = factory.cli_options(); let main_module = cli_options.resolve_main_module()?; maybe_npm_install(&factory).await?; - let _ = watcher_interface - .paths_to_watch_tx - .send(cli_options.watch_paths()); + let _ = watcher_communicator.watch_paths(cli_options.watch_paths()); let permissions = PermissionsContainer::new(Permissions::from_options( &cli_options.permissions_options(), diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index f2b8f98e547564..8e29ba2cbf3c95 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -1213,19 +1213,18 @@ pub async fn run_tests_with_watch( .map(|w| !w.no_clear_screen) .unwrap_or(true), }, - move |flags, watcher_interface, changed_paths| { + move |flags, watcher_communicator, changed_paths| { let test_flags = test_flags.clone(); - let sender = watcher_interface.paths_to_watch_tx.clone(); Ok(async move { let factory = CliFactoryBuilder::new() - .build_from_flags_for_watcher(flags, watcher_interface) + .build_from_flags_for_watcher(flags, watcher_communicator.clone()) .await?; let cli_options = factory.cli_options(); let test_options = cli_options.resolve_test_options(test_flags)?; - let _ = sender.send(cli_options.watch_paths()); + let _ = watcher_communicator.watch_paths(cli_options.watch_paths()); if let Some(include) = &test_options.files.include { - let _ = sender.send(include.clone()); + let _ = watcher_communicator.watch_paths(include.clone()); } let graph_kind = cli_options.type_check_mode().as_graph_kind(); diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 23b0179b91831f..8d6b4e8fb6ffaa 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -109,20 +109,20 @@ fn create_print_after_restart_fn(clear_screen: bool) -> impl Fn() { } } -// TODO(bartlomieju): this is a poor name; change it /// An interface to interact with Deno's CLI file watcher. -pub struct WatcherInterface { +#[derive(Debug)] +pub struct WatcherCommunicator { /// Send a list of paths that should be watched for changes. - pub paths_to_watch_tx: tokio::sync::mpsc::UnboundedSender>, + paths_to_watch_tx: tokio::sync::mpsc::UnboundedSender>, /// Listen for a list of paths that were changed. - pub changed_paths_rx: tokio::sync::broadcast::Receiver>>, + changed_paths_rx: tokio::sync::broadcast::Receiver>>, /// Send a message to force a restart. - pub restart_tx: tokio::sync::mpsc::UnboundedSender<()>, + restart_tx: tokio::sync::mpsc::UnboundedSender<()>, } -impl Clone for WatcherInterface { +impl Clone for WatcherCommunicator { fn clone(&self) -> Self { Self { paths_to_watch_tx: self.paths_to_watch_tx.clone(), @@ -132,6 +132,12 @@ impl Clone for WatcherInterface { } } +impl WatcherCommunicator { + pub fn watch_paths(&self, paths: Vec) -> Result<(), AnyError> { + self.paths_to_watch_tx.send(paths).map_err(AnyError::from) + } +} + /// Creates a file watcher. /// /// - `operation` is the actual operation we want to run every time the watcher detects file @@ -143,8 +149,11 @@ pub async fn watch_func( operation: O, ) -> Result<(), AnyError> where - O: - FnMut(Flags, WatcherInterface, Option>) -> Result, + O: FnMut( + Flags, + WatcherCommunicator, + Option>, + ) -> Result, F: Future>, { let fut = watch_recv( @@ -164,7 +173,7 @@ pub enum WatcherRestartMode { Automatic, /// When a file path changes the caller will trigger a restart, using - /// `WatcherInterface.restart_tx`. + /// `WatcherCommunicator.restart_tx`. // TODO(bartlomieju): this mode will be used in a follow up PR #[allow(dead_code)] Manual, @@ -182,8 +191,11 @@ pub async fn watch_recv( mut operation: O, ) -> Result<(), AnyError> where - O: - FnMut(Flags, WatcherInterface, Option>) -> Result, + O: FnMut( + Flags, + WatcherCommunicator, + Option>, + ) -> Result, F: Future>, { let (paths_to_watch_tx, mut paths_to_watch_rx) = @@ -199,7 +211,7 @@ where } = print_config; let print_after_restart = create_print_after_restart_fn(clear_screen); - let watcher_interface = WatcherInterface { + let watcher_communicator = WatcherCommunicator { paths_to_watch_tx: paths_to_watch_tx.clone(), changed_paths_rx: changed_paths_rx.resubscribe(), restart_tx: restart_tx.clone(), @@ -226,7 +238,7 @@ where }; let operation_future = error_handler(operation( flags.clone(), - watcher_interface.clone(), + watcher_communicator.clone(), changed_paths.take(), )?); From 0f4e31c0ec66efe4fc4754aa10340ddd0ae1c1bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 19 Oct 2023 05:34:22 +0200 Subject: [PATCH 50/79] updates after merge --- cli/factory.rs | 6 +++--- cli/tools/bench/mod.rs | 1 - cli/tools/fmt.rs | 3 +-- cli/tools/run/hot_reload/mod.rs | 26 ++++++++++++---------- cli/tools/run/mod.rs | 38 ++++++++++++++++----------------- cli/tools/test/mod.rs | 1 - cli/util/file_watcher.rs | 11 ++++++++++ cli/worker.rs | 11 +++++----- 8 files changed, 55 insertions(+), 42 deletions(-) diff --git a/cli/factory.rs b/cli/factory.rs index 7ddd80115dd735..5ffc2cf8a9ba40 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -599,8 +599,8 @@ impl CliFactory { let npm_resolver = self.npm_resolver().await?; let fs = self.fs(); let cli_node_resolver = self.cli_node_resolver().await?; - let maybe_file_watcher_interface = if self.options.has_hot_reload() { - Some(self.watcher_interface.clone().unwrap()) + let maybe_file_watcher_communicator = if self.options.has_hot_reload() { + Some(self.watcher_communicator.clone().unwrap()) } else { None }; @@ -628,7 +628,7 @@ impl CliFactory { self.root_cert_store_provider().clone(), self.fs().clone(), Some(self.emitter()?.clone()), - maybe_file_watcher_interface, + maybe_file_watcher_communicator, self.maybe_inspector_server().clone(), self.maybe_lockfile().clone(), self.feature_checker().clone(), diff --git a/cli/tools/bench/mod.rs b/cli/tools/bench/mod.rs index ada1ff8e27993a..eb400442e2877e 100644 --- a/cli/tools/bench/mod.rs +++ b/cli/tools/bench/mod.rs @@ -419,7 +419,6 @@ pub async fn run_benchmarks_with_watch( }, move |flags, watcher_communicator, changed_paths| { let bench_flags = bench_flags.clone(); - let sender = watcher_interface.paths_to_watch_tx.clone(); Ok(async move { let factory = CliFactoryBuilder::new() .build_from_flags_for_watcher(flags, watcher_communicator.clone()) diff --git a/cli/tools/fmt.rs b/cli/tools/fmt.rs index 968b78b4ea1b6e..d5a6b40f5035eb 100644 --- a/cli/tools/fmt.rs +++ b/cli/tools/fmt.rs @@ -70,7 +70,6 @@ pub async fn format(flags: Flags, fmt_flags: FmtFlags) -> Result<(), AnyError> { }, move |flags, watcher_communicator, changed_paths| { let fmt_flags = fmt_flags.clone(); - let sender = watcher_interface.paths_to_watch_tx.clone(); Ok(async move { let factory = CliFactory::from_flags(flags).await?; let cli_options = factory.cli_options(); @@ -83,7 +82,7 @@ pub async fn format(flags: Flags, fmt_flags: FmtFlags) -> Result<(), AnyError> { Ok(files) } })?; - _ = watcher_communicator.watch_paths(files.clone()); + let _ = watcher_communicator.watch_paths(files.clone()); let refmt_files = if let Some(paths) = changed_paths { if fmt_options.check { // check all files on any changed (https://github.com/denoland/deno/issues/12446) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index cb5d262b54204a..0bf145b39925e3 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -1,7 +1,7 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use crate::emit::Emitter; -use crate::util::file_watcher::WatcherInterface; +use crate::util::file_watcher::WatcherCommunicator; use deno_ast::MediaType; use deno_core::error::generic_error; use deno_core::error::AnyError; @@ -25,8 +25,7 @@ use json_types::Status; pub struct HotReloadManager { session: LocalInspectorSession, - changed_paths_rx: tokio::sync::broadcast::Receiver>, - restart_tx: tokio::sync::mpsc::UnboundedSender<()>, + watcher_communicator: WatcherCommunicator, script_ids: HashMap, emitter: Arc, } @@ -35,13 +34,12 @@ impl HotReloadManager { pub fn new( emitter: Arc, session: LocalInspectorSession, - interface: WatcherInterface, + watcher_communicator: WatcherCommunicator, ) -> Self { Self { session, emitter, - changed_paths_rx: interface.changed_paths_rx, - restart_tx: interface.restart_tx, + watcher_communicator, script_ids: HashMap::new(), } } @@ -154,9 +152,15 @@ pub async fn run_hot_reload( } } } - changed_paths = hmr_manager.changed_paths_rx.recv() => { + changed_paths = hmr_manager.watcher_communicator.watch_for_changed_paths() => { eprintln!("changed patchs in hot {:#?}", changed_paths); let changed_paths = changed_paths?; + + let Some(changed_paths) = changed_paths else { + let _ = hmr_manager.watcher_communicator.force_restart(); + continue; + }; + let filtered_paths: Vec = changed_paths.into_iter().filter(|p| p.extension().map_or(false, |ext| { let ext_str = ext.to_str().unwrap(); matches!(ext_str, "js" | "ts" | "jsx" | "tsx") @@ -164,18 +168,18 @@ pub async fn run_hot_reload( for path in filtered_paths { let Some(path_str) = path.to_str() else { - let _ = hmr_manager.restart_tx.send(()); + let _ = hmr_manager.watcher_communicator.force_restart(); continue; }; let Ok(module_url) = Url::from_file_path(path_str) else { - let _ = hmr_manager.restart_tx.send(()); + let _ = hmr_manager.watcher_communicator.force_restart(); continue; }; log::info!("{} Reloading changed module {}", colors::intense_blue("HMR"), module_url.as_str()); let Some(id) = hmr_manager.script_ids.get(module_url.as_str()).cloned() else { - let _ = hmr_manager.restart_tx.send(()); + let _ = hmr_manager.watcher_communicator.force_restart(); continue; }; @@ -211,7 +215,7 @@ pub async fn run_hot_reload( if !result.status.should_retry() { log::info!("{} Restarting the process...", colors::intense_blue("HMR")); // TODO(bartlomieju): Print into that sending failed? - let _ = hmr_manager.restart_tx.send(()); + let _ = hmr_manager.watcher_communicator.force_restart(); break; } } diff --git a/cli/tools/run/mod.rs b/cli/tools/run/mod.rs index d7cf1b50048e3d..260773769916e2 100644 --- a/cli/tools/run/mod.rs +++ b/cli/tools/run/mod.rs @@ -107,24 +107,25 @@ async fn run_with_watch( flags: Flags, watch_flags: WatchFlagsWithPaths, ) -> Result { - util::file_watcher::watch_func( - flags, - util::file_watcher::PrintConfig { - job_name: "Process".to_string(), - clear_screen: !watch_flags.no_clear_screen, - }, - WatcherRestartMode::Manual, - move |flags, watcher_communicator, _changed_paths| { - Ok(async move { - let factory = CliFactoryBuilder::new() - .build_from_flags_for_watcher(flags, watcher_communicator.clone()) - .await?; - let cli_options = factory.cli_options(); - let main_module = cli_options.resolve_main_module()?; + if watch_flags.hot_reload { + util::file_watcher::watch_recv( + flags, + util::file_watcher::PrintConfig { + job_name: "Process".to_string(), + clear_screen: !watch_flags.no_clear_screen, + }, + WatcherRestartMode::Manual, + move |flags, watcher_communicator, _changed_paths| { + Ok(async move { + let factory = CliFactoryBuilder::new() + .build_from_flags_for_watcher(flags, watcher_communicator.clone()) + .await?; + let cli_options = factory.cli_options(); + let main_module = cli_options.resolve_main_module()?; maybe_npm_install(&factory).await?; - let _ = watcher_communicator.watch_paths(cli_options.watch_paths()); + let _ = watcher_communicator.watch_paths(cli_options.watch_paths()); let permissions = PermissionsContainer::new( Permissions::from_options(&cli_options.permissions_options())?, @@ -153,18 +154,17 @@ async fn run_with_watch( job_name: "Process".to_string(), clear_screen: !watch_flags.no_clear_screen, }, - move |flags, watcher_interface, _changed_paths| { - let sender = watcher_interface.paths_to_watch_tx.clone(); + move |flags, watcher_communicator, _changed_paths| { Ok(async move { let factory = CliFactoryBuilder::new() - .build_from_flags_for_watcher(flags, watcher_interface) + .build_from_flags_for_watcher(flags, watcher_communicator.clone()) .await?; let cli_options = factory.cli_options(); let main_module = cli_options.resolve_main_module()?; maybe_npm_install(&factory).await?; - let _ = sender.send(cli_options.watch_paths()); + let _ = watcher_communicator.watch_paths(cli_options.watch_paths()); let permissions = PermissionsContainer::new( Permissions::from_options(&cli_options.permissions_options())?, diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index cd77501f9202ca..8e29ba2cbf3c95 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -1215,7 +1215,6 @@ pub async fn run_tests_with_watch( }, move |flags, watcher_communicator, changed_paths| { let test_flags = test_flags.clone(); - let sender = watcher_interface.paths_to_watch_tx.clone(); Ok(async move { let factory = CliFactoryBuilder::new() .build_from_flags_for_watcher(flags, watcher_communicator.clone()) diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 4308c0a50cdfe2..26431acbecb1ce 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -136,6 +136,17 @@ impl WatcherCommunicator { pub fn watch_paths(&self, paths: Vec) -> Result<(), AnyError> { self.paths_to_watch_tx.send(paths).map_err(AnyError::from) } + + pub fn force_restart(&self) -> Result<(), AnyError> { + self.restart_tx.send(()).map_err(AnyError::from) + } + + pub async fn watch_for_changed_paths( + &self, + ) -> Result>, AnyError> { + let mut rx = self.changed_paths_rx.resubscribe(); + rx.recv().await.map_err(AnyError::from) + } } /// Creates a file watcher. diff --git a/cli/worker.rs b/cli/worker.rs index 04aa59ad6d2258..34f25f6bffcad6 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -55,7 +55,7 @@ use crate::tools::coverage::CoverageCollector; use crate::tools::run::hot_reload; use crate::tools::run::hot_reload::HotReloadManager; use crate::util::checksum; -use crate::util::file_watcher::WatcherInterface; +use crate::util::file_watcher::WatcherCommunicator; use crate::version; pub trait ModuleLoaderFactory: Send + Sync { @@ -114,7 +114,7 @@ struct SharedWorkerState { root_cert_store_provider: Arc, fs: Arc, emitter: Option>, - maybe_file_watcher_interface: Option, + maybe_file_watcher_communicator: Option, maybe_inspector_server: Option>, maybe_lockfile: Option>>, feature_checker: Arc, @@ -326,7 +326,8 @@ impl CliMainWorker { return Ok(None); } - let interface = self.shared.maybe_file_watcher_interface.clone().unwrap(); + let interface = + self.shared.maybe_file_watcher_communicator.clone().unwrap(); // TODO(bartlomieju): this is a code smell, refactor so we don't have // to pass `emitter` here @@ -371,7 +372,7 @@ impl CliMainWorkerFactory { root_cert_store_provider: Arc, fs: Arc, emitter: Option>, - maybe_file_watcher_interface: Option, + maybe_file_watcher_communicator: Option, maybe_inspector_server: Option>, maybe_lockfile: Option>>, feature_checker: Arc, @@ -391,7 +392,7 @@ impl CliMainWorkerFactory { root_cert_store_provider, emitter, fs, - maybe_file_watcher_interface, + maybe_file_watcher_communicator, maybe_inspector_server, maybe_lockfile, feature_checker, From 87adbb62c4eb66582e3244f5c1f72ea42a47eede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 20 Oct 2023 04:31:09 +0200 Subject: [PATCH 51/79] remove with_event_loop_fallible --- cli/worker.rs | 2 +- runtime/worker.rs | 19 ------------------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/cli/worker.rs b/cli/worker.rs index 34f25f6bffcad6..a6285a8da02f02 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -167,7 +167,7 @@ impl CliMainWorker { eprintln!("start hmr"); self .worker - .with_event_loop_fallible( + .with_event_loop( hot_reload::run_hot_reload(hot_reload_manager).boxed_local(), ) .await?; diff --git a/runtime/worker.rs b/runtime/worker.rs index 07e2cb7a29684e..e8d9ca6bcccab5 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -523,25 +523,6 @@ impl MainWorker { } } - pub async fn with_event_loop_fallible<'a>( - &mut self, - mut fut: Pin> + 'a>>, - ) -> Result<(), AnyError> { - loop { - tokio::select! { - biased; - result = &mut fut => { - return result; - } - r = self.run_event_loop(false) => { - if r.is_err() { - return r; - } - } - }; - } - } - /// Return exit code set by the executed code (either in main worker /// or one of child web workers). pub fn exit_code(&self) -> i32 { From 8ee89a47b2a5f4be4fb6331f7ea0d50b7a8dcfb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 20 Oct 2023 04:36:06 +0200 Subject: [PATCH 52/79] unify run subcommand --- cli/tools/run/mod.rs | 116 ++++++++++++++------------------------- cli/util/file_watcher.rs | 2 +- 2 files changed, 43 insertions(+), 75 deletions(-) diff --git a/cli/tools/run/mod.rs b/cli/tools/run/mod.rs index 260773769916e2..6f987ee0d643b3 100644 --- a/cli/tools/run/mod.rs +++ b/cli/tools/run/mod.rs @@ -107,83 +107,51 @@ async fn run_with_watch( flags: Flags, watch_flags: WatchFlagsWithPaths, ) -> Result { - if watch_flags.hot_reload { - util::file_watcher::watch_recv( - flags, - util::file_watcher::PrintConfig { - job_name: "Process".to_string(), - clear_screen: !watch_flags.no_clear_screen, - }, - WatcherRestartMode::Manual, - move |flags, watcher_communicator, _changed_paths| { - Ok(async move { - let factory = CliFactoryBuilder::new() - .build_from_flags_for_watcher(flags, watcher_communicator.clone()) - .await?; - let cli_options = factory.cli_options(); - let main_module = cli_options.resolve_main_module()?; - - maybe_npm_install(&factory).await?; - - let _ = watcher_communicator.watch_paths(cli_options.watch_paths()); - - let permissions = PermissionsContainer::new( - Permissions::from_options(&cli_options.permissions_options())?, - ); - let mut worker = factory - .create_cli_main_worker_factory() - .await? - .create_main_worker(main_module, permissions) - .await?; - - let r = worker.run().await; - // eprintln!("worker run result {:#?}", r); - r?; - - Ok(()) - }) - }, - ) - .await?; - - Ok(0) - } else { - util::file_watcher::watch_func( - flags, - util::file_watcher::PrintConfig { - job_name: "Process".to_string(), - clear_screen: !watch_flags.no_clear_screen, - }, - move |flags, watcher_communicator, _changed_paths| { - Ok(async move { - let factory = CliFactoryBuilder::new() - .build_from_flags_for_watcher(flags, watcher_communicator.clone()) - .await?; - let cli_options = factory.cli_options(); - let main_module = cli_options.resolve_main_module()?; - - maybe_npm_install(&factory).await?; - - let _ = watcher_communicator.watch_paths(cli_options.watch_paths()); - - let permissions = PermissionsContainer::new( - Permissions::from_options(&cli_options.permissions_options())?, - ); - let worker = factory - .create_cli_main_worker_factory() - .await? - .create_main_worker(main_module, permissions) - .await?; + util::file_watcher::watch_recv( + flags, + util::file_watcher::PrintConfig { + job_name: "Process".to_string(), + clear_screen: !watch_flags.no_clear_screen, + }, + if watch_flags.hot_reload { + WatcherRestartMode::Manual + } else { + WatcherRestartMode::Automatic + }, + move |flags, watcher_communicator, _changed_paths| { + Ok(async move { + let factory = CliFactoryBuilder::new() + .build_from_flags_for_watcher(flags, watcher_communicator.clone()) + .await?; + let cli_options = factory.cli_options(); + let main_module = cli_options.resolve_main_module()?; + + maybe_npm_install(&factory).await?; + + let _ = watcher_communicator.watch_paths(cli_options.watch_paths()); + + let permissions = PermissionsContainer::new(Permissions::from_options( + &cli_options.permissions_options(), + )?); + let mut worker = factory + .create_cli_main_worker_factory() + .await? + .create_main_worker(main_module, permissions) + .await?; + + if watch_flags.hot_reload { + worker.run().await?; + } else { worker.run_for_watcher().await?; + } - Ok(()) - }) - }, - ) - .await?; + Ok(()) + }) + }, + ) + .await?; - Ok(0) - } + Ok(0) } pub async fn eval_command( diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 26431acbecb1ce..5c5058cee287ac 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -303,7 +303,7 @@ where select! { _ = receiver_future => {}, received_changed_paths = watcher_receiver.recv() => { - eprintln!("received paths {:?}", received_changed_paths); + // eprintln!("received paths {:?}", received_changed_paths); print_after_restart(); changed_paths = received_changed_paths; continue; From 02b06753d7f929a01101bffe9e2ca965244c16b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 20 Oct 2023 04:39:50 +0200 Subject: [PATCH 53/79] PrintConfig::new --- cli/tools/bench/mod.rs | 8 ++++---- cli/tools/bundle.rs | 8 ++++---- cli/tools/fmt.rs | 5 +---- cli/tools/lint.rs | 5 +---- cli/tools/run/mod.rs | 8 ++++---- cli/tools/test/mod.rs | 8 ++++---- cli/util/file_watcher.rs | 13 +++++++++++-- 7 files changed, 29 insertions(+), 26 deletions(-) diff --git a/cli/tools/bench/mod.rs b/cli/tools/bench/mod.rs index eb400442e2877e..70551a767216f6 100644 --- a/cli/tools/bench/mod.rs +++ b/cli/tools/bench/mod.rs @@ -409,14 +409,14 @@ pub async fn run_benchmarks_with_watch( ) -> Result<(), AnyError> { file_watcher::watch_func( flags, - file_watcher::PrintConfig { - job_name: "Bench".to_string(), - clear_screen: bench_flags + file_watcher::PrintConfig::new( + "Bench", + bench_flags .watch .as_ref() .map(|w| !w.no_clear_screen) .unwrap_or(true), - }, + ), move |flags, watcher_communicator, changed_paths| { let bench_flags = bench_flags.clone(); Ok(async move { diff --git a/cli/tools/bundle.rs b/cli/tools/bundle.rs index cbde8768fd7777..111fce5415ea40 100644 --- a/cli/tools/bundle.rs +++ b/cli/tools/bundle.rs @@ -31,10 +31,10 @@ pub async fn bundle( if let Some(watch_flags) = &bundle_flags.watch { util::file_watcher::watch_func( flags, - util::file_watcher::PrintConfig { - job_name: "Bundle".to_string(), - clear_screen: !watch_flags.no_clear_screen, - }, + util::file_watcher::PrintConfig::new( + "Bundle", + !watch_flags.no_clear_screen, + ), move |flags, watcher_communicator, _changed_paths| { let bundle_flags = bundle_flags.clone(); Ok(async move { diff --git a/cli/tools/fmt.rs b/cli/tools/fmt.rs index d5a6b40f5035eb..f28ec99cf0a347 100644 --- a/cli/tools/fmt.rs +++ b/cli/tools/fmt.rs @@ -64,10 +64,7 @@ pub async fn format(flags: Flags, fmt_flags: FmtFlags) -> Result<(), AnyError> { if let Some(watch_flags) = &fmt_flags.watch { file_watcher::watch_func( flags, - file_watcher::PrintConfig { - job_name: "Fmt".to_string(), - clear_screen: !watch_flags.no_clear_screen, - }, + file_watcher::PrintConfig::new("Fmt", !watch_flags.no_clear_screen), move |flags, watcher_communicator, changed_paths| { let fmt_flags = fmt_flags.clone(); Ok(async move { diff --git a/cli/tools/lint.rs b/cli/tools/lint.rs index b7f4a3f0d9b911..5b9387eb1378a6 100644 --- a/cli/tools/lint.rs +++ b/cli/tools/lint.rs @@ -59,10 +59,7 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> { } file_watcher::watch_func( flags, - file_watcher::PrintConfig { - job_name: "Lint".to_string(), - clear_screen: !watch_flags.no_clear_screen, - }, + file_watcher::PrintConfig::new("Lint", !watch_flags.no_clear_screen), move |flags, watcher_communicator, changed_paths| { let lint_flags = lint_flags.clone(); Ok(async move { diff --git a/cli/tools/run/mod.rs b/cli/tools/run/mod.rs index 6f987ee0d643b3..89b5821dfe2e7b 100644 --- a/cli/tools/run/mod.rs +++ b/cli/tools/run/mod.rs @@ -109,10 +109,10 @@ async fn run_with_watch( ) -> Result { util::file_watcher::watch_recv( flags, - util::file_watcher::PrintConfig { - job_name: "Process".to_string(), - clear_screen: !watch_flags.no_clear_screen, - }, + util::file_watcher::PrintConfig::new( + "Process", + !watch_flags.no_clear_screen, + ), if watch_flags.hot_reload { WatcherRestartMode::Manual } else { diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index 8e29ba2cbf3c95..5e34e345f067e5 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -1205,14 +1205,14 @@ pub async fn run_tests_with_watch( file_watcher::watch_func( flags, - file_watcher::PrintConfig { - job_name: "Test".to_string(), - clear_screen: test_flags + file_watcher::PrintConfig::new( + "Test", + test_flags .watch .as_ref() .map(|w| !w.no_clear_screen) .unwrap_or(true), - }, + ), move |flags, watcher_communicator, changed_paths| { let test_flags = test_flags.clone(); Ok(async move { diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 5c5058cee287ac..51bbaa45841e5c 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -92,9 +92,18 @@ where pub struct PrintConfig { /// printing watcher status to terminal. - pub job_name: String, + job_name: &'static str, /// determine whether to clear the terminal screen; applicable to TTY environments only. - pub clear_screen: bool, + clear_screen: bool, +} + +impl PrintConfig { + pub fn new(job_name: &'static str, clear_screen: bool) -> Self { + Self { + job_name, + clear_screen, + } + } } fn create_print_after_restart_fn(clear_screen: bool) -> impl Fn() { From 9bacd17f60bceb195c946790c5ec0b34b0771ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 20 Oct 2023 04:42:38 +0200 Subject: [PATCH 54/79] make banner configurable --- cli/tools/run/mod.rs | 7 ++++++- cli/util/file_watcher.rs | 31 +++++++++++++++++++++++++------ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/cli/tools/run/mod.rs b/cli/tools/run/mod.rs index 89b5821dfe2e7b..e2034b5e851b5f 100644 --- a/cli/tools/run/mod.rs +++ b/cli/tools/run/mod.rs @@ -109,7 +109,12 @@ async fn run_with_watch( ) -> Result { util::file_watcher::watch_recv( flags, - util::file_watcher::PrintConfig::new( + util::file_watcher::PrintConfig::new_with_banner( + if watch_flags.hot_reload { + "HMR" + } else { + "Watcher" + }, "Process", !watch_flags.no_clear_screen, ), diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 51bbaa45841e5c..0ac0a6e83f91d5 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -91,29 +91,47 @@ where } pub struct PrintConfig { - /// printing watcher status to terminal. + /// Defaults to 'Watcher' + banner: &'static str, + /// Printing watcher status to terminal. job_name: &'static str, - /// determine whether to clear the terminal screen; applicable to TTY environments only. + /// Determine whether to clear the terminal screen; applicable to TTY environments only. clear_screen: bool, } impl PrintConfig { 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(clear_screen: bool) -> impl Fn() { +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), ); } } @@ -224,17 +242,18 @@ 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 print_after_restart = create_print_after_restart_fn(banner, clear_screen); let watcher_communicator = 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,); + info!("{} {} started.", colors::intense_blue(banner), job_name); let mut changed_paths = None; loop { From 7c8a5503006af497da3149a990d822954fb3ec44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 20 Oct 2023 04:44:42 +0200 Subject: [PATCH 55/79] change flag to --unstable-hmr --- cli/args/flags.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/args/flags.rs b/cli/args/flags.rs index b81ab0eddbcdd8..47cd5d2c0c9c13 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -2702,7 +2702,7 @@ fn seed_arg() -> Arg { fn hmr_arg(takes_files: bool) -> Arg { let arg = Arg::new("hmr") - .long("hmr") + .long("unstable-hmr") .help("UNSTABLE: Watch for file changes and hot replace modules") .conflicts_with("watch"); @@ -4016,7 +4016,7 @@ mod tests { let r = flags_from_vec(svec![ "deno", "run", - "--hmr", + "--unstable-hmr", "--no-clear-screen", "script.ts" ]); @@ -4039,7 +4039,7 @@ mod tests { let r = flags_from_vec(svec![ "deno", "run", - "--hmr=foo.txt", + "--unstable-hmr=foo.txt", "--no-clear-screen", "script.ts" ]); From 62d1c9eaca95522d5b974e5a9bad64cdf0f96cd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 22 Oct 2023 10:49:58 +0200 Subject: [PATCH 56/79] try to debug why hmr stops --- cli/tools/run/hot_reload/mod.rs | 7 +++++++ cli/tools/run/mod.rs | 6 +----- cli/util/file_watcher.rs | 19 ++++++++++++++++++- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 0bf145b39925e3..ab616b68d1bc28 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -2,6 +2,7 @@ use crate::emit::Emitter; use crate::util::file_watcher::WatcherCommunicator; +use crate::util::file_watcher::WatcherRestartMode; use deno_ast::MediaType; use deno_core::error::generic_error; use deno_core::error::AnyError; @@ -51,6 +52,9 @@ impl HotReloadManager { // TODO(bartlomieju): this code is duplicated in `cli/tools/coverage/mod.rs` pub async fn stop(&mut self) -> Result<(), AnyError> { + self + .watcher_communicator + .change_restart_mode(WatcherRestartMode::Automatic); self.disable_debugger().await } @@ -130,6 +134,9 @@ impl HotReloadManager { pub async fn run_hot_reload( hmr_manager: &mut HotReloadManager, ) -> Result<(), AnyError> { + hmr_manager + .watcher_communicator + .change_restart_mode(WatcherRestartMode::Manual); let mut session_rx = hmr_manager.session.take_notification_rx(); loop { select! { diff --git a/cli/tools/run/mod.rs b/cli/tools/run/mod.rs index e2034b5e851b5f..8dba3a2aee5fc0 100644 --- a/cli/tools/run/mod.rs +++ b/cli/tools/run/mod.rs @@ -118,11 +118,7 @@ async fn run_with_watch( "Process", !watch_flags.no_clear_screen, ), - if watch_flags.hot_reload { - WatcherRestartMode::Manual - } else { - WatcherRestartMode::Automatic - }, + WatcherRestartMode::Automatic, move |flags, watcher_communicator, _changed_paths| { Ok(async move { let factory = CliFactoryBuilder::new() diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 0ac0a6e83f91d5..5a5aa1cad4c289 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; @@ -147,6 +148,8 @@ pub struct WatcherCommunicator { /// Send a message to force a restart. restart_tx: tokio::sync::mpsc::UnboundedSender<()>, + + restart_mode: Arc>, } impl Clone for WatcherCommunicator { @@ -155,6 +158,7 @@ impl Clone for WatcherCommunicator { 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: self.restart_mode.clone(), } } } @@ -174,6 +178,10 @@ impl WatcherCommunicator { 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; + } } /// Creates a file watcher. @@ -247,11 +255,13 @@ where clear_screen, } = print_config; + let restart_mode = Arc::new(Mutex::new(restart_mode)); let print_after_restart = create_print_after_restart_fn(banner, clear_screen); let watcher_communicator = WatcherCommunicator { paths_to_watch_tx: paths_to_watch_tx.clone(), changed_paths_rx: changed_paths_rx.resubscribe(), restart_tx: restart_tx.clone(), + restart_mode: restart_mode.clone(), }; info!("{} {} started.", colors::intense_blue(banner), job_name); @@ -265,11 +275,13 @@ where } let mut watcher = new_watcher(watcher_sender.clone())?; + eprintln!("consume paths1"); consume_paths_to_watch(&mut watcher, &mut paths_to_watch_rx); let receiver_future = async { loop { let maybe_paths = paths_to_watch_rx.recv().await; + eprintln!("add paths1"); add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); } }; @@ -289,9 +301,10 @@ where continue; }, received_changed_paths = watcher_receiver.recv() => { + eprintln!("received changed paths in file watcher"); changed_paths = received_changed_paths.clone(); - match restart_mode { + match *restart_mode.lock() { WatcherRestartMode::Automatic => { print_after_restart(); continue; @@ -303,6 +316,7 @@ where } }, success = operation_future => { + eprintln!("consume paths2"); consume_paths_to_watch(&mut watcher, &mut paths_to_watch_rx); // TODO(bartlomieju): print exit code here? info!( @@ -321,6 +335,7 @@ where let receiver_future = async { loop { let maybe_paths = paths_to_watch_rx.recv().await; + eprintln!("add paths2"); add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); } }; @@ -368,6 +383,7 @@ fn new_watcher( } fn add_paths_to_watcher(watcher: &mut RecommendedWatcher, paths: &[PathBuf]) { + eprintln!("add patchs to watcher {:#?}", paths); // Ignore any error e.g. `PathNotFound` for path in paths { let _ = watcher.watch(path, RecursiveMode::Recursive); @@ -382,6 +398,7 @@ fn consume_paths_to_watch( loop { match receiver.try_recv() { Ok(paths) => { + eprintln!("add paths3"); add_paths_to_watcher(watcher, &paths); } Err(e) => match e { From 3458a03d187bfe9bb5abe766e87ade7e085cf639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 22 Oct 2023 13:02:47 +0200 Subject: [PATCH 57/79] short lived scripts work, long lived scripts now broken :( --- cli/tools/run/hot_reload/mod.rs | 6 ++++++ cli/util/file_watcher.rs | 5 ++++- cli/worker.rs | 17 +++++++++++------ main2.js | 2 +- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index ab616b68d1bc28..c1eb02e13b0c14 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -155,6 +155,12 @@ pub async fn run_hot_reload( } else if notification.method == "Debugger.scriptParsed" { let params = serde_json::from_value::(notification.params)?; if params.url.starts_with("file://") { + // if let Ok(path_url) = Url::parse(¶ms.url) { + // eprintln!("path url {:#?}", path_url.as_str()); + // let _ = hmr_manager.watcher_communicator.watch_paths(vec![path_url.to_file_path().unwrap()]); + // eprintln!("started watching path"); + // tokio::task::yield_now().await; + // } hmr_manager.script_ids.insert(params.url, params.script_id); } } diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 5a5aa1cad4c289..816c0184078efb 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -279,6 +279,7 @@ where consume_paths_to_watch(&mut watcher, &mut paths_to_watch_rx); let receiver_future = async { + eprintln!("receiver future"); loop { let maybe_paths = paths_to_watch_rx.recv().await; eprintln!("add paths1"); @@ -294,6 +295,7 @@ where // don't reload dependencies after the first run flags.reload = false; + eprintln!("before select"); select! { _ = receiver_future => {}, _ = restart_rx.recv() => { @@ -331,8 +333,9 @@ where ); }, }; - + eprintln!("after select"); let receiver_future = async { + eprintln!("receiver future2"); loop { let maybe_paths = paths_to_watch_rx.recv().await; eprintln!("add paths2"); diff --git a/cli/worker.rs b/cli/worker.rs index a6285a8da02f02..ad353cc93f1117 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -43,6 +43,7 @@ use deno_runtime::BootstrapOptions; use deno_runtime::WorkerLogLevel; use deno_semver::npm::NpmPackageReqReference; use deno_semver::package::PackageReqReference; +use tokio::select; use crate::args::package_json::PackageJsonDeps; use crate::args::StorageKeyResolver; @@ -165,12 +166,16 @@ impl CliMainWorker { loop { if let Some(hot_reload_manager) = maybe_hot_reload_manager.as_mut() { eprintln!("start hmr"); - self - .worker - .with_event_loop( - hot_reload::run_hot_reload(hot_reload_manager).boxed_local(), - ) - .await?; + let hmr_future = + hot_reload::run_hot_reload(hot_reload_manager).boxed_local(); + let event_loop_future = self.worker.run_event_loop(false).boxed_local(); + + select! { + _ = hmr_future => {}, + result = event_loop_future => { + result?; + } + } eprintln!("stop hmr"); } else { self diff --git a/main2.js b/main2.js index 7a412448e9269c..9bc4afb3f92fc9 100644 --- a/main2.js +++ b/main2.js @@ -1 +1 @@ -console.log("hello world123"); +console.log("hello world1231"); From 53c5fa3be984095bfc991c18657bf69c55acdf7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 22 Oct 2023 14:44:39 +0200 Subject: [PATCH 58/79] working for short lived programs --- cli/tools/run/hot_reload/mod.rs | 2 +- cli/util/file_watcher.rs | 46 +++++++++++++++++++-------------- cli/worker.rs | 8 +++++- foo.ts | 2 +- main.js | 4 +-- server.js | 4 +-- 6 files changed, 39 insertions(+), 27 deletions(-) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index c1eb02e13b0c14..50726f5a8a4b1a 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -26,7 +26,7 @@ use json_types::Status; pub struct HotReloadManager { session: LocalInspectorSession, - watcher_communicator: WatcherCommunicator, + pub watcher_communicator: WatcherCommunicator, script_ids: HashMap, emitter: Arc, } diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 816c0184078efb..f1c65c1d31377e 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -265,7 +265,30 @@ where }; info!("{} {} started.", colors::intense_blue(banner), job_name); - let mut changed_paths = None; + let changed_paths = Arc::new(Mutex::new(None)); + let changed_paths_ = changed_paths.clone(); + + tokio::task::spawn(async move { + let print_after_restart = + create_print_after_restart_fn(banner, clear_screen); + loop { + let received_changed_paths = watcher_receiver.recv().await; + eprintln!("received changed paths in file watcher"); + *changed_paths_.lock() = received_changed_paths.clone(); + match *restart_mode.lock() { + WatcherRestartMode::Automatic => { + print_after_restart(); + let _ = restart_tx.send(()); + } + WatcherRestartMode::Manual => { + eprintln!("manual dispatching event"); + // TODO(bartlomieju): should we fail on sending changed paths? + let _ = changed_paths_tx.send(received_changed_paths); + } + } + } + }); + 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 @@ -289,7 +312,7 @@ where let operation_future = error_handler(operation( flags.clone(), watcher_communicator.clone(), - changed_paths.take(), + changed_paths.lock().take(), )?); // don't reload dependencies after the first run @@ -302,21 +325,6 @@ where print_after_restart(); continue; }, - received_changed_paths = watcher_receiver.recv() => { - eprintln!("received changed paths in file watcher"); - changed_paths = received_changed_paths.clone(); - - match *restart_mode.lock() { - 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 => { eprintln!("consume paths2"); consume_paths_to_watch(&mut watcher, &mut paths_to_watch_rx); @@ -348,10 +356,8 @@ where // watched paths has changed. select! { _ = receiver_future => {}, - received_changed_paths = watcher_receiver.recv() => { - // eprintln!("received paths {:?}", received_changed_paths); + _ = restart_rx.recv() => { print_after_restart(); - changed_paths = received_changed_paths; continue; }, }; diff --git a/cli/worker.rs b/cli/worker.rs index ad353cc93f1117..d426b269c21f2f 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -57,6 +57,7 @@ use crate::tools::run::hot_reload; use crate::tools::run::hot_reload::HotReloadManager; use crate::util::checksum; use crate::util::file_watcher::WatcherCommunicator; +use crate::util::file_watcher::WatcherRestartMode; use crate::version; pub trait ModuleLoaderFactory: Send + Sync { @@ -171,8 +172,13 @@ impl CliMainWorker { let event_loop_future = self.worker.run_event_loop(false).boxed_local(); select! { - _ = hmr_future => {}, + result = hmr_future => { + eprintln!("hmr future ended"); + hot_reload_manager.watcher_communicator.change_restart_mode(WatcherRestartMode::Automatic); + result?; + }, result = event_loop_future => { + eprintln!("event loop future ended"); result?; } } diff --git a/foo.ts b/foo.ts index 075789b0eae9e9..c8b917afd3ec4d 100644 --- a/foo.ts +++ b/foo.ts @@ -1,3 +1,3 @@ export function getFoo() { - return "foo12asdfasf434"; // change this line + return "foo1234"; // change this line } diff --git a/main.js b/main.js index cf452fcca03cd9..09755f5c348c13 100644 --- a/main.js +++ b/main.js @@ -1,12 +1,12 @@ import { getFoo } from "./foo.ts"; async function bar() { - throw new Error("fail1"); + throw new Error("fail12123"); } let i = 1; setInterval(async () => { - if (i === 5) { + if (i % 5 === 0) { // unhandled promise rejection is not shown await bar(); } diff --git a/server.js b/server.js index 45c35f2e462576..b105f37d123281 100644 --- a/server.js +++ b/server.js @@ -4,8 +4,8 @@ function bar() { } function handler(req) { - // console.log("req111123", req); - return new Response("hello122334"); + console.log("req111123123", req); + return new Response("hello4"); } Deno.serve(handler); From d99ab1f405f237367862492b18d3bbddea155ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 22 Oct 2023 14:46:30 +0200 Subject: [PATCH 59/79] remove debug logs --- cli/tools/run/hot_reload/mod.rs | 7 ------- cli/util/file_watcher.rs | 12 ------------ cli/worker.rs | 8 -------- 3 files changed, 27 deletions(-) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 50726f5a8a4b1a..19a4e58658a310 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -155,18 +155,11 @@ pub async fn run_hot_reload( } else if notification.method == "Debugger.scriptParsed" { let params = serde_json::from_value::(notification.params)?; if params.url.starts_with("file://") { - // if let Ok(path_url) = Url::parse(¶ms.url) { - // eprintln!("path url {:#?}", path_url.as_str()); - // let _ = hmr_manager.watcher_communicator.watch_paths(vec![path_url.to_file_path().unwrap()]); - // eprintln!("started watching path"); - // tokio::task::yield_now().await; - // } hmr_manager.script_ids.insert(params.url, params.script_id); } } } changed_paths = hmr_manager.watcher_communicator.watch_for_changed_paths() => { - eprintln!("changed patchs in hot {:#?}", changed_paths); let changed_paths = changed_paths?; let Some(changed_paths) = changed_paths else { diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index f1c65c1d31377e..882f052b1db817 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -273,7 +273,6 @@ where create_print_after_restart_fn(banner, clear_screen); loop { let received_changed_paths = watcher_receiver.recv().await; - eprintln!("received changed paths in file watcher"); *changed_paths_.lock() = received_changed_paths.clone(); match *restart_mode.lock() { WatcherRestartMode::Automatic => { @@ -281,7 +280,6 @@ where let _ = restart_tx.send(()); } WatcherRestartMode::Manual => { - eprintln!("manual dispatching event"); // TODO(bartlomieju): should we fail on sending changed paths? let _ = changed_paths_tx.send(received_changed_paths); } @@ -298,14 +296,11 @@ where } let mut watcher = new_watcher(watcher_sender.clone())?; - eprintln!("consume paths1"); consume_paths_to_watch(&mut watcher, &mut paths_to_watch_rx); let receiver_future = async { - eprintln!("receiver future"); loop { let maybe_paths = paths_to_watch_rx.recv().await; - eprintln!("add paths1"); add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); } }; @@ -318,7 +313,6 @@ where // don't reload dependencies after the first run flags.reload = false; - eprintln!("before select"); select! { _ = receiver_future => {}, _ = restart_rx.recv() => { @@ -326,7 +320,6 @@ where continue; }, success = operation_future => { - eprintln!("consume paths2"); consume_paths_to_watch(&mut watcher, &mut paths_to_watch_rx); // TODO(bartlomieju): print exit code here? info!( @@ -341,12 +334,9 @@ where ); }, }; - eprintln!("after select"); let receiver_future = async { - eprintln!("receiver future2"); loop { let maybe_paths = paths_to_watch_rx.recv().await; - eprintln!("add paths2"); add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); } }; @@ -392,7 +382,6 @@ fn new_watcher( } fn add_paths_to_watcher(watcher: &mut RecommendedWatcher, paths: &[PathBuf]) { - eprintln!("add patchs to watcher {:#?}", paths); // Ignore any error e.g. `PathNotFound` for path in paths { let _ = watcher.watch(path, RecursiveMode::Recursive); @@ -407,7 +396,6 @@ fn consume_paths_to_watch( loop { match receiver.try_recv() { Ok(paths) => { - eprintln!("add paths3"); add_paths_to_watcher(watcher, &paths); } Err(e) => match e { diff --git a/cli/worker.rs b/cli/worker.rs index d426b269c21f2f..38c89db05d5dfa 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -166,23 +166,19 @@ impl CliMainWorker { loop { if let Some(hot_reload_manager) = maybe_hot_reload_manager.as_mut() { - eprintln!("start hmr"); let hmr_future = hot_reload::run_hot_reload(hot_reload_manager).boxed_local(); let event_loop_future = self.worker.run_event_loop(false).boxed_local(); select! { result = hmr_future => { - eprintln!("hmr future ended"); hot_reload_manager.watcher_communicator.change_restart_mode(WatcherRestartMode::Automatic); result?; }, result = event_loop_future => { - eprintln!("event loop future ended"); result?; } } - eprintln!("stop hmr"); } else { self .worker @@ -198,8 +194,6 @@ impl CliMainWorker { } } - eprintln!("hot reload finished"); - self.worker.dispatch_unload_event(located_script_name!())?; if let Some(coverage_collector) = maybe_coverage_collector.as_mut() { @@ -215,8 +209,6 @@ impl CliMainWorker { .await?; } - eprintln!("hot reload finished2"); - Ok(self.worker.exit_code()) } From 2f423dd510bd24e357efd4843ec48bf38ec6a325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 22 Oct 2023 14:48:55 +0200 Subject: [PATCH 60/79] fix logs --- cli/util/file_watcher.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 882f052b1db817..1871280c257177 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -269,14 +269,11 @@ where let changed_paths_ = changed_paths.clone(); tokio::task::spawn(async move { - let print_after_restart = - create_print_after_restart_fn(banner, clear_screen); loop { let received_changed_paths = watcher_receiver.recv().await; *changed_paths_.lock() = received_changed_paths.clone(); match *restart_mode.lock() { WatcherRestartMode::Automatic => { - print_after_restart(); let _ = restart_tx.send(()); } WatcherRestartMode::Manual => { From b695d5168883ba6891f37fbaa9e8b14664bc53da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 22 Oct 2023 15:09:29 +0200 Subject: [PATCH 61/79] fix on returned error --- cli/util/file_watcher.rs | 2 +- cli/worker.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 1871280c257177..e51e07fbf9c8c7 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -321,7 +321,7 @@ where // TODO(bartlomieju): print exit code here? info!( "{} {} {}. Restarting on file change...", - colors::intense_blue("Watcher"), + colors::intense_blue(banner), job_name, if success { "finished" diff --git a/cli/worker.rs b/cli/worker.rs index 38c89db05d5dfa..3db870e214abd4 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -176,6 +176,7 @@ impl CliMainWorker { result?; }, result = event_loop_future => { + hot_reload_manager.watcher_communicator.change_restart_mode(WatcherRestartMode::Automatic); result?; } } From 76de135211c517b770c502c9c946fa97c0f106a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 22 Oct 2023 15:27:12 +0200 Subject: [PATCH 62/79] lint --- cli/args/flags.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cli/args/flags.rs b/cli/args/flags.rs index d0a86068dff940..37000cc35ed650 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -3867,20 +3867,20 @@ fn watch_arg_parse_with_paths( matches: &mut ArgMatches, ) -> Option { if let Some(paths) = matches.remove_many::("watch") { - Some(WatchFlagsWithPaths { + return Some(WatchFlagsWithPaths { paths: paths.collect(), hot_reload: false, no_clear_screen: matches.get_flag("no-clear-screen"), - }) - } else if let Some(paths) = matches.remove_many::("hmr") { - Some(WatchFlagsWithPaths { + }); + } + + matches + .remove_many::("hmr") + .map(|paths| WatchFlagsWithPaths { paths: paths.collect(), hot_reload: true, no_clear_screen: matches.get_flag("no-clear-screen"), }) - } else { - None - } } // TODO(ry) move this to utility module and add test. From 35e519ec8c0699678dfc863ecd404998bba59611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 22 Oct 2023 17:48:45 +0200 Subject: [PATCH 63/79] store banner in communicator --- cli/tools/run/hot_reload/mod.rs | 6 +++--- cli/util/file_watcher.rs | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 19a4e58658a310..28b628797c75e0 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -182,7 +182,7 @@ pub async fn run_hot_reload( continue; }; - log::info!("{} Reloading changed module {}", colors::intense_blue("HMR"), module_url.as_str()); + hmr_manager.watcher_communicator.print(format!("Reloading changed module {}", module_url.as_str())); let Some(id) = hmr_manager.script_ids.get(module_url.as_str()).cloned() else { let _ = hmr_manager.watcher_communicator.force_restart(); @@ -217,9 +217,9 @@ pub async fn run_hot_reload( break; } - log::info!("{} Failed to reload module {}: {}.", colors::intense_blue("HMR"), module_url, colors::gray(result.status.explain())); + hmr_manager.watcher_communicator.print(format!("Failed to reload module {}: {}.", module_url, colors::gray(result.status.explain()))); if !result.status.should_retry() { - log::info!("{} Restarting the process...", colors::intense_blue("HMR")); + hmr_manager.watcher_communicator.print("Restarting the process...".to_string()); // TODO(bartlomieju): Print into that sending failed? let _ = hmr_manager.watcher_communicator.force_restart(); break; diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index e51e07fbf9c8c7..9d7a8e198ca552 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -150,6 +150,8 @@ pub struct WatcherCommunicator { restart_tx: tokio::sync::mpsc::UnboundedSender<()>, restart_mode: Arc>, + + banner: String, } impl Clone for WatcherCommunicator { @@ -159,6 +161,7 @@ impl Clone for WatcherCommunicator { changed_paths_rx: self.changed_paths_rx.resubscribe(), restart_tx: self.restart_tx.clone(), restart_mode: self.restart_mode.clone(), + banner: self.banner.clone(), } } } @@ -182,6 +185,10 @@ impl WatcherCommunicator { 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. @@ -262,6 +269,7 @@ where changed_paths_rx: changed_paths_rx.resubscribe(), restart_tx: restart_tx.clone(), restart_mode: restart_mode.clone(), + banner: colors::intense_blue(banner).to_string(), }; info!("{} {} started.", colors::intense_blue(banner), job_name); From 741ace30a55ec3ff0521b290829acb9b51a955c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 22 Oct 2023 22:30:56 +0200 Subject: [PATCH 64/79] Address TODOs --- cli/tools/run/hot_reload/mod.rs | 18 +++++++++--------- cli/worker.rs | 18 ++++++++++++------ 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 28b628797c75e0..d30796616b8ed8 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -130,7 +130,6 @@ impl HotReloadManager { } } -// TODO(bartlomieju): Shouldn't use `tokio::select!` here, as futures are not cancel safe pub async fn run_hot_reload( hmr_manager: &mut HotReloadManager, ) -> Result<(), AnyError> { @@ -141,7 +140,6 @@ pub async fn run_hot_reload( loop { select! { biased; - // TODO(SyrupThinker): Deferred retry with timeout Some(notification) = session_rx.next() => { let notification = serde_json::from_value::(notification)?; // TODO(bartlomieju): this is not great... and the code is duplicated with the REPL. @@ -207,8 +205,7 @@ pub async fn run_hot_reload( // hmr_manager.emitter.emit_parsed_source(&module_url, media_type, &source_arc)? }; - // eprintln!("transpiled source code {:#?}", source_code); - // TODO(bartlomieju): this loop should do 2 retries at most + let mut tries = 1; loop { let result = hmr_manager.set_script_source(&id, source_code.as_str()).await?; @@ -218,12 +215,15 @@ pub async fn run_hot_reload( } hmr_manager.watcher_communicator.print(format!("Failed to reload module {}: {}.", module_url, colors::gray(result.status.explain()))); - if !result.status.should_retry() { - hmr_manager.watcher_communicator.print("Restarting the process...".to_string()); - // TODO(bartlomieju): Print into that sending failed? - let _ = hmr_manager.watcher_communicator.force_restart(); - break; + if result.status.should_retry() && tries <= 2 { + tries += 1; + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + continue; } + + hmr_manager.watcher_communicator.print("Restarting the process...".to_string()); + let _ = hmr_manager.watcher_communicator.force_restart(); + break; } } } diff --git a/cli/worker.rs b/cli/worker.rs index 3db870e214abd4..c30201b7e7e6f5 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -170,16 +170,22 @@ impl CliMainWorker { hot_reload::run_hot_reload(hot_reload_manager).boxed_local(); let event_loop_future = self.worker.run_event_loop(false).boxed_local(); + let result; + select! { - result = hmr_future => { - hot_reload_manager.watcher_communicator.change_restart_mode(WatcherRestartMode::Automatic); - result?; + hmr_result = hmr_future => { + result = hmr_result; }, - result = event_loop_future => { - hot_reload_manager.watcher_communicator.change_restart_mode(WatcherRestartMode::Automatic); - result?; + event_loop_result = event_loop_future => { + result = event_loop_result; } } + if let Err(e) = result { + hot_reload_manager + .watcher_communicator + .change_restart_mode(WatcherRestartMode::Automatic); + return Err(e); + } } else { self .worker From 859416a0fa14d54bff08594b8cb741e51995f3f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 22 Oct 2023 22:37:10 +0200 Subject: [PATCH 65/79] wip --- foo.jsx | 7 +++++++ main.js | 24 +++++++----------------- server.ts | 6 +----- 3 files changed, 15 insertions(+), 22 deletions(-) create mode 100644 foo.jsx diff --git a/foo.jsx b/foo.jsx new file mode 100644 index 00000000000000..2b5f8e74253166 --- /dev/null +++ b/foo.jsx @@ -0,0 +1,7 @@ +export function foo() { + setTimeout(() => { + // Uncomment this: + // throw new Error("fail"); + }); + return `

asd1

`; +} diff --git a/main.js b/main.js index 09755f5c348c13..49179b759e7eb4 100644 --- a/main.js +++ b/main.js @@ -1,19 +1,9 @@ -import { getFoo } from "./foo.ts"; +// main.ts +import { foo } from "./foo.jsx"; -async function bar() { - throw new Error("fail12123"); -} +let i = 0; +setInterval(() => { + console.log(i++, foo()); +}, 1000); -let i = 1; -setInterval(async () => { - if (i % 5 === 0) { - // unhandled promise rejection is not shown - await bar(); - } - console.log(i++, getFoo()); -}, 100); - -// addEventListener("unhandledrejection", (e) => { -// console.log("unhandledrejection", e.reason); -// e.preventDefault(); -// }); +// foo.jsx diff --git a/server.ts b/server.ts index 0678d675793528..fc05bf80457729 100644 --- a/server.ts +++ b/server.ts @@ -4,12 +4,8 @@ function bar() { } function handler(req) { - // console.log("req123", req); + console.log("req123", req); return new Response("Hello world!1234"); } Deno.serve(handler); - -addEventListener("hmr", (ev) => { - console.log("event", ev.detail); -}); From e72c02bd2d1cd3553bdf8f3f685715724751bdc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 25 Oct 2023 22:57:57 +0200 Subject: [PATCH 66/79] cleanup emitter handling --- cli/emit.rs | 26 ++++++++++++++++++++++++-- cli/tools/run/hot_reload/mod.rs | 21 +++------------------ 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/cli/emit.rs b/cli/emit.rs index f2f78c0d3d537e..b9d0d0a93a02ee 100644 --- a/cli/emit.rs +++ b/cli/emit.rs @@ -14,8 +14,8 @@ use std::sync::Arc; pub struct Emitter { emit_cache: EmitCache, - pub parsed_source_cache: Arc, - pub emit_options: deno_ast::EmitOptions, + parsed_source_cache: Arc, + emit_options: deno_ast::EmitOptions, // cached hash of the emit options emit_options_hash: u64, } @@ -101,6 +101,28 @@ impl Emitter { } } + /// Expects a file URL, panics otherwise. + pub async fn load_and_emit_for_hmr( + &self, + specifier: &ModuleSpecifier, + ) -> Result { + let media_type = MediaType::from_specifier(specifier); + let source_code = tokio::fs::read_to_string( + ModuleSpecifier::to_file_path(specifier).unwrap(), + ) + .await?; + let source_arc: Arc = Arc::from(source_code.as_str()); + let parsed_source = self.parsed_source_cache.get_or_parse_module( + specifier, + source_arc.clone(), + media_type, + )?; + let mut options = self.emit_options.clone(); + options.inline_source_map = false; + let transpiled_source = parsed_source.transpile(&options)?; + Ok(transpiled_source.text.to_string()) + } + /// A hashing function that takes the source code and uses the global emit /// options then generates a string hash which can be stored to /// determine if the cached emit is valid or not. diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index d30796616b8ed8..87466858eee7ff 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -3,7 +3,6 @@ use crate::emit::Emitter; use crate::util::file_watcher::WatcherCommunicator; use crate::util::file_watcher::WatcherRestartMode; -use deno_ast::MediaType; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::futures::StreamExt; @@ -187,23 +186,9 @@ pub async fn run_hot_reload( continue; }; - // TODO(bartlomieju): I really don't like `hmr_manager.emitter` etc here. - // Maybe use `deno_ast` directly? - let media_type = MediaType::from_path(&path); - let source_code = tokio::fs::read_to_string(path).await?; - let source_arc: Arc = Arc::from(source_code.as_str()); - let source_code = { - let parsed_source = hmr_manager.emitter.parsed_source_cache.get_or_parse_module( - &module_url, - source_arc.clone(), - media_type, - )?; - let mut options = hmr_manager.emitter.emit_options.clone(); - options.inline_source_map = false; - let transpiled_source = parsed_source.transpile(&options)?; - transpiled_source.text.to_string() - // hmr_manager.emitter.emit_parsed_source(&module_url, media_type, &source_arc)? - }; + let source_code = hmr_manager.emitter.load_and_emit_for_hmr( + &module_url + ).await?; let mut tries = 1; loop { From 95306486b662e3b0e169e7e9271ef60cd1ce61bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Oct 2023 02:15:57 +0200 Subject: [PATCH 67/79] start working on tests --- cli/tests/testdata/hmr/jsx/foo.jsx | 3 +++ main.js => cli/tests/testdata/hmr/jsx/main.js | 5 +---- cli/tests/testdata/hmr/server/mod.ts | 13 +++++++++++++ .../tests/testdata/hmr/uncaught_error/foo.jsx | 3 +-- cli/tests/testdata/hmr/uncaught_error/main.js | 6 ++++++ cli/tests/testdata/hmr/unhandled_rejection/main.js | 13 +++++++++++++ foo.ts | 3 --- hmr.js | 6 ------ main2.js | 1 - server.js | 11 ----------- server.ts | 11 ----------- 11 files changed, 37 insertions(+), 38 deletions(-) create mode 100644 cli/tests/testdata/hmr/jsx/foo.jsx rename main.js => cli/tests/testdata/hmr/jsx/main.js (73%) create mode 100644 cli/tests/testdata/hmr/server/mod.ts rename foo.jsx => cli/tests/testdata/hmr/uncaught_error/foo.jsx (58%) create mode 100644 cli/tests/testdata/hmr/uncaught_error/main.js create mode 100644 cli/tests/testdata/hmr/unhandled_rejection/main.js delete mode 100644 foo.ts delete mode 100644 hmr.js delete mode 100644 main2.js delete mode 100644 server.js delete mode 100644 server.ts diff --git a/cli/tests/testdata/hmr/jsx/foo.jsx b/cli/tests/testdata/hmr/jsx/foo.jsx new file mode 100644 index 00000000000000..8c6508eb3376a8 --- /dev/null +++ b/cli/tests/testdata/hmr/jsx/foo.jsx @@ -0,0 +1,3 @@ +export function foo() { + return `

Hello

`; +} diff --git a/main.js b/cli/tests/testdata/hmr/jsx/main.js similarity index 73% rename from main.js rename to cli/tests/testdata/hmr/jsx/main.js index 49179b759e7eb4..5ed940ab3e2e8d 100644 --- a/main.js +++ b/cli/tests/testdata/hmr/jsx/main.js @@ -1,9 +1,6 @@ -// main.ts import { foo } from "./foo.jsx"; let i = 0; setInterval(() => { console.log(i++, foo()); -}, 1000); - -// foo.jsx +}, 100); diff --git a/cli/tests/testdata/hmr/server/mod.ts b/cli/tests/testdata/hmr/server/mod.ts new file mode 100644 index 00000000000000..24d441788d1730 --- /dev/null +++ b/cli/tests/testdata/hmr/server/mod.ts @@ -0,0 +1,13 @@ +globalThis.state = { i: 0 }; + +function bar() { + globalThis.state.i = 0; + console.log("got request", globalThis.state.i); +} + +function handler(_req) { + bar(); + return new Response("Hello world!"); +} + +Deno.serve(handler); diff --git a/foo.jsx b/cli/tests/testdata/hmr/uncaught_error/foo.jsx similarity index 58% rename from foo.jsx rename to cli/tests/testdata/hmr/uncaught_error/foo.jsx index 2b5f8e74253166..65feecf113ebff 100644 --- a/foo.jsx +++ b/cli/tests/testdata/hmr/uncaught_error/foo.jsx @@ -1,7 +1,6 @@ export function foo() { setTimeout(() => { - // Uncomment this: - // throw new Error("fail"); + throw new Error("fail"); }); return `

asd1

`; } diff --git a/cli/tests/testdata/hmr/uncaught_error/main.js b/cli/tests/testdata/hmr/uncaught_error/main.js new file mode 100644 index 00000000000000..5ed940ab3e2e8d --- /dev/null +++ b/cli/tests/testdata/hmr/uncaught_error/main.js @@ -0,0 +1,6 @@ +import { foo } from "./foo.jsx"; + +let i = 0; +setInterval(() => { + console.log(i++, foo()); +}, 100); diff --git a/cli/tests/testdata/hmr/unhandled_rejection/main.js b/cli/tests/testdata/hmr/unhandled_rejection/main.js new file mode 100644 index 00000000000000..a348e96f35ac0d --- /dev/null +++ b/cli/tests/testdata/hmr/unhandled_rejection/main.js @@ -0,0 +1,13 @@ +import { foo } from "./foo.jsx"; + +async function rejection() { + throw new Error("boom!"); +} + +let i = 0; +setInterval(() => { + if (i == 3) { + rejection(); + } + console.log(i++, foo()); +}, 100); diff --git a/foo.ts b/foo.ts deleted file mode 100644 index c8b917afd3ec4d..00000000000000 --- a/foo.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function getFoo() { - return "foo1234"; // change this line -} diff --git a/hmr.js b/hmr.js deleted file mode 100644 index e1a6b18497fc1f..00000000000000 --- a/hmr.js +++ /dev/null @@ -1,6 +0,0 @@ -console.log("Hello there 123!"); - -Deno.serve((req) => { - console.log("request", req); - return new Response("hello there 12"); -}); diff --git a/main2.js b/main2.js deleted file mode 100644 index 9bc4afb3f92fc9..00000000000000 --- a/main2.js +++ /dev/null @@ -1 +0,0 @@ -console.log("hello world1231"); diff --git a/server.js b/server.js deleted file mode 100644 index b105f37d123281..00000000000000 --- a/server.js +++ /dev/null @@ -1,11 +0,0 @@ -globalThis.state = { i: 0 }; - -function bar() { -} - -function handler(req) { - console.log("req111123123", req); - return new Response("hello4"); -} - -Deno.serve(handler); diff --git a/server.ts b/server.ts deleted file mode 100644 index fc05bf80457729..00000000000000 --- a/server.ts +++ /dev/null @@ -1,11 +0,0 @@ -globalThis.state = { i: 0 }; - -function bar() { -} - -function handler(req) { - console.log("req123", req); - return new Response("Hello world!1234"); -} - -Deno.serve(handler); From 2ebf0cb4cb467bfb75267fe037168b96d46460ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Oct 2023 02:17:31 +0200 Subject: [PATCH 68/79] lint --- cli/tests/testdata/hmr/unhandled_rejection/main.js | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/tests/testdata/hmr/unhandled_rejection/main.js b/cli/tests/testdata/hmr/unhandled_rejection/main.js index a348e96f35ac0d..39a926de0fd221 100644 --- a/cli/tests/testdata/hmr/unhandled_rejection/main.js +++ b/cli/tests/testdata/hmr/unhandled_rejection/main.js @@ -1,5 +1,6 @@ import { foo } from "./foo.jsx"; +// deno-lint-ignore require-await async function rejection() { throw new Error("boom!"); } From dcda421cb5eb4d6467173d54019547c9821110ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Oct 2023 23:49:59 +0200 Subject: [PATCH 69/79] restart on external path change --- cli/tools/run/hot_reload/mod.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 87466858eee7ff..0bae6f5432176b 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -169,6 +169,14 @@ pub async fn run_hot_reload( matches!(ext_str, "js" | "ts" | "jsx" | "tsx") })).collect(); + // If after filtering there are no paths it means it's either a file + // we can't HMR or an external file that was passed explicitly to + // `--unstable-hmr=` path. + if filtered_paths.is_empty() { + let _ = hmr_manager.watcher_communicator.force_restart(); + continue; + } + for path in filtered_paths { let Some(path_str) = path.to_str() else { let _ = hmr_manager.watcher_communicator.force_restart(); @@ -179,13 +187,13 @@ pub async fn run_hot_reload( continue; }; - hmr_manager.watcher_communicator.print(format!("Reloading changed module {}", module_url.as_str())); - let Some(id) = hmr_manager.script_ids.get(module_url.as_str()).cloned() else { let _ = hmr_manager.watcher_communicator.force_restart(); continue; }; + hmr_manager.watcher_communicator.print(format!("Reloading changed module {}", module_url.as_str())); + let source_code = hmr_manager.emitter.load_and_emit_for_hmr( &module_url ).await?; From 7881e4ee9357c059aff330e8d5d3d44bdb1da53c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 27 Oct 2023 23:57:22 +0200 Subject: [PATCH 70/79] revert to automatic mode on forced restart --- cli/tools/run/hot_reload/mod.rs | 1 - cli/util/file_watcher.rs | 3 +++ cli/worker.rs | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 0bae6f5432176b..0cbc81886d08b5 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -214,7 +214,6 @@ pub async fn run_hot_reload( continue; } - hmr_manager.watcher_communicator.print("Restarting the process...".to_string()); let _ = hmr_manager.watcher_communicator.force_restart(); break; } diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 9d7a8e198ca552..9aafe901fc26bc 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -172,6 +172,9 @@ impl WatcherCommunicator { } 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) } diff --git a/cli/worker.rs b/cli/worker.rs index c30201b7e7e6f5..a11490d9a56e5a 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -171,7 +171,6 @@ impl CliMainWorker { let event_loop_future = self.worker.run_event_loop(false).boxed_local(); let result; - select! { hmr_result = hmr_future => { result = hmr_result; From af535fba09c6b513c8687fdacd7ae857d9260d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 30 Oct 2023 01:42:55 +0100 Subject: [PATCH 71/79] review part one --- cli/emit.rs | 10 ++++------ cli/worker.rs | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/cli/emit.rs b/cli/emit.rs index b9d0d0a93a02ee..c84776ae4d76ef 100644 --- a/cli/emit.rs +++ b/cli/emit.rs @@ -112,15 +112,13 @@ impl Emitter { ) .await?; let source_arc: Arc = Arc::from(source_code.as_str()); - let parsed_source = self.parsed_source_cache.get_or_parse_module( - specifier, - source_arc.clone(), - media_type, - )?; + let parsed_source = self + .parsed_source_cache + .get_or_parse_module(specifier, source_arc, media_type)?; let mut options = self.emit_options.clone(); options.inline_source_map = false; let transpiled_source = parsed_source.transpile(&options)?; - Ok(transpiled_source.text.to_string()) + Ok(transpiled_source.text) } /// A hashing function that takes the source code and uses the global emit diff --git a/cli/worker.rs b/cli/worker.rs index a11490d9a56e5a..05e9f7a4461d68 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -335,7 +335,7 @@ impl CliMainWorker { return Ok(None); } - let interface = + let watcher_communicator = self.shared.maybe_file_watcher_communicator.clone().unwrap(); // TODO(bartlomieju): this is a code smell, refactor so we don't have @@ -344,7 +344,7 @@ impl CliMainWorker { let session = self.worker.create_inspector_session().await; let mut hot_reload_manager = - HotReloadManager::new(emitter, session, interface); + HotReloadManager::new(emitter, session, watcher_communicator); self .worker From 55f75936692734ca52521ba710a490b09131e557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 30 Oct 2023 01:46:13 +0100 Subject: [PATCH 72/79] review part two --- cli/tools/run/hot_reload/mod.rs | 2 +- cli/util/file_watcher.rs | 6 ++++-- cli/worker.rs | 9 ++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index 0cbc81886d08b5..bb77131162f5dd 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -25,7 +25,7 @@ use json_types::Status; pub struct HotReloadManager { session: LocalInspectorSession, - pub watcher_communicator: WatcherCommunicator, + watcher_communicator: WatcherCommunicator, script_ids: HashMap, emitter: Arc, } diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 9aafe901fc26bc..562ffd80c477a7 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -92,7 +92,6 @@ where } pub struct PrintConfig { - /// Defaults to 'Watcher' banner: &'static str, /// Printing watcher status to terminal. job_name: &'static str, @@ -101,6 +100,9 @@ pub struct PrintConfig { } 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", @@ -279,7 +281,7 @@ where let changed_paths = Arc::new(Mutex::new(None)); let changed_paths_ = changed_paths.clone(); - tokio::task::spawn(async move { + deno_core::unsync::spawn(async move { loop { let received_changed_paths = watcher_receiver.recv().await; *changed_paths_.lock() = received_changed_paths.clone(); diff --git a/cli/worker.rs b/cli/worker.rs index 05e9f7a4461d68..9a7a72a83d0205 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -166,6 +166,9 @@ impl CliMainWorker { loop { if let Some(hot_reload_manager) = maybe_hot_reload_manager.as_mut() { + let watcher_communicator = + self.shared.maybe_file_watcher_communicator.clone().unwrap(); + let hmr_future = hot_reload::run_hot_reload(hot_reload_manager).boxed_local(); let event_loop_future = self.worker.run_event_loop(false).boxed_local(); @@ -180,8 +183,7 @@ impl CliMainWorker { } } if let Err(e) = result { - hot_reload_manager - .watcher_communicator + watcher_communicator .change_restart_mode(WatcherRestartMode::Automatic); return Err(e); } @@ -337,9 +339,6 @@ impl CliMainWorker { let watcher_communicator = self.shared.maybe_file_watcher_communicator.clone().unwrap(); - - // TODO(bartlomieju): this is a code smell, refactor so we don't have - // to pass `emitter` here let emitter = self.shared.emitter.clone().unwrap(); let session = self.worker.create_inspector_session().await; From 757e885e6d4272e4e22463d6124fa18cc7ef53e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 30 Oct 2023 02:03:12 +0100 Subject: [PATCH 73/79] review part three --- cli/tools/run/hot_reload/mod.rs | 21 ++++++++++++++++++--- cli/worker.rs | 6 +++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hot_reload/mod.rs index bb77131162f5dd..95437f80a95413 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hot_reload/mod.rs @@ -23,14 +23,29 @@ use json_types::ScriptParsed; use json_types::SetScriptSourceReturnObject; use json_types::Status; -pub struct HotReloadManager { +/// This structure is responsible for providing Hot Module Replacement +/// functionality. +/// +/// It communicates with V8 inspector over a local session and waits for +/// notifications about changed files from the `FileWatcher`. +/// +/// Upon receiving such notification, the runner decides if the changed +/// path should be handled the `FileWatcher` itself (as if we were running +/// in `--watch` mode), or if the path is eligible to be hot replaced in the +/// current program. +/// +/// Even if the runner decides that a path will be hot-replaced, the V8 isolate +/// can refuse to perform hot replacement, eg. a top-level variable/function +/// of an ES module cannot be hot-replaced. In such situation the runner will +/// force a full restart of a program by notifying the `FileWatcher`. +pub struct HmrRunner { session: LocalInspectorSession, watcher_communicator: WatcherCommunicator, script_ids: HashMap, emitter: Arc, } -impl HotReloadManager { +impl HmrRunner { pub fn new( emitter: Arc, session: LocalInspectorSession, @@ -130,7 +145,7 @@ impl HotReloadManager { } pub async fn run_hot_reload( - hmr_manager: &mut HotReloadManager, + hmr_manager: &mut HmrRunner, ) -> Result<(), AnyError> { hmr_manager .watcher_communicator diff --git a/cli/worker.rs b/cli/worker.rs index 9a7a72a83d0205..98a052dd013eb2 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -54,7 +54,7 @@ use crate::ops; use crate::tools; use crate::tools::coverage::CoverageCollector; use crate::tools::run::hot_reload; -use crate::tools::run::hot_reload::HotReloadManager; +use crate::tools::run::hot_reload::HmrRunner; use crate::util::checksum; use crate::util::file_watcher::WatcherCommunicator; use crate::util::file_watcher::WatcherRestartMode; @@ -332,7 +332,7 @@ impl CliMainWorker { pub async fn maybe_setup_hot_reload_manager( &mut self, - ) -> Result, AnyError> { + ) -> Result, AnyError> { if !self.shared.options.hot_reload { return Ok(None); } @@ -343,7 +343,7 @@ impl CliMainWorker { let session = self.worker.create_inspector_session().await; let mut hot_reload_manager = - HotReloadManager::new(emitter, session, watcher_communicator); + HmrRunner::new(emitter, session, watcher_communicator); self .worker From 4ac67957cf222bab9704f92da848b2779dbf476c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 30 Oct 2023 02:21:29 +0100 Subject: [PATCH 74/79] renames: hot_reload -> hmr --- cli/args/flags.rs | 30 +++++++++---------- cli/args/mod.rs | 6 ++-- cli/factory.rs | 4 +-- cli/standalone/mod.rs | 2 +- .../run/{hot_reload => hmr}/json_types.rs | 0 cli/tools/run/{hot_reload => hmr}/mod.rs | 2 +- cli/tools/run/mod.rs | 10 ++----- cli/worker.rs | 28 ++++++++--------- 8 files changed, 38 insertions(+), 44 deletions(-) rename cli/tools/run/{hot_reload => hmr}/json_types.rs (100%) rename cli/tools/run/{hot_reload => hmr}/mod.rs (99%) diff --git a/cli/args/flags.rs b/cli/args/flags.rs index 778093793a6142..cd545f4ff692f2 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -212,13 +212,13 @@ impl RunFlags { #[derive(Clone, Default, Debug, Eq, PartialEq)] pub struct WatchFlags { - pub hot_reload: bool, + pub hmr: bool, pub no_clear_screen: bool, } #[derive(Clone, Default, Debug, Eq, PartialEq)] pub struct WatchFlagsWithPaths { - pub hot_reload: bool, + pub hmr: bool, pub paths: Vec, pub no_clear_screen: bool, } @@ -3865,7 +3865,7 @@ fn reload_arg_validate(urlstr: &str) -> Result { fn watch_arg_parse(matches: &mut ArgMatches) -> Option { if matches.get_flag("watch") { Some(WatchFlags { - hot_reload: false, + hmr: false, no_clear_screen: matches.get_flag("no-clear-screen"), }) } else { @@ -3879,7 +3879,7 @@ fn watch_arg_parse_with_paths( if let Some(paths) = matches.remove_many::("watch") { return Some(WatchFlagsWithPaths { paths: paths.collect(), - hot_reload: false, + hmr: false, no_clear_screen: matches.get_flag("no-clear-screen"), }); } @@ -3888,7 +3888,7 @@ fn watch_arg_parse_with_paths( .remove_many::("hmr") .map(|paths| WatchFlagsWithPaths { paths: paths.collect(), - hot_reload: true, + hmr: true, no_clear_screen: matches.get_flag("no-clear-screen"), }) } @@ -4006,7 +4006,7 @@ mod tests { subcommand: DenoSubcommand::Run(RunFlags { script: "script.ts".to_string(), watch: Some(WatchFlagsWithPaths { - hot_reload: false, + hmr: false, paths: vec![], no_clear_screen: false, }), @@ -4029,7 +4029,7 @@ mod tests { subcommand: DenoSubcommand::Run(RunFlags { script: "script.ts".to_string(), watch: Some(WatchFlagsWithPaths { - hot_reload: false, + hmr: false, paths: vec![], no_clear_screen: true, }), @@ -4052,7 +4052,7 @@ mod tests { subcommand: DenoSubcommand::Run(RunFlags { script: "script.ts".to_string(), watch: Some(WatchFlagsWithPaths { - hot_reload: true, + hmr: true, paths: vec![], no_clear_screen: true, }), @@ -4075,7 +4075,7 @@ mod tests { subcommand: DenoSubcommand::Run(RunFlags { script: "script.ts".to_string(), watch: Some(WatchFlagsWithPaths { - hot_reload: true, + hmr: true, paths: vec![PathBuf::from("foo.txt")], no_clear_screen: true, }), @@ -4100,7 +4100,7 @@ mod tests { subcommand: DenoSubcommand::Run(RunFlags { script: "script.ts".to_string(), watch: Some(WatchFlagsWithPaths { - hot_reload: false, + hmr: false, paths: vec![PathBuf::from("file1"), PathBuf::from("file2")], no_clear_screen: false, }), @@ -4127,7 +4127,7 @@ mod tests { subcommand: DenoSubcommand::Run(RunFlags { script: "script.ts".to_string(), watch: Some(WatchFlagsWithPaths { - hot_reload: false, + hmr: false, paths: vec![], no_clear_screen: true, }) @@ -4474,7 +4474,7 @@ mod tests { prose_wrap: None, no_semicolons: None, watch: Some(WatchFlags { - hot_reload: false, + hmr: false, no_clear_screen: true, }) }), @@ -4716,7 +4716,7 @@ mod tests { json: false, compact: false, watch: Some(WatchFlags { - hot_reload: false, + hmr: false, no_clear_screen: true, }) }), @@ -5943,7 +5943,7 @@ mod tests { source_file: "source.ts".to_string(), out_file: None, watch: Some(WatchFlags { - hot_reload: false, + hmr: false, no_clear_screen: true, }), }), @@ -7175,7 +7175,7 @@ mod tests { trace_ops: false, coverage_dir: None, watch: Some(WatchFlags { - hot_reload: false, + hmr: false, no_clear_screen: true, }), reporter: Default::default(), diff --git a/cli/args/mod.rs b/cli/args/mod.rs index 434dc422fff4b4..96f4e9a74a1375 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -1130,13 +1130,13 @@ impl CliOptions { &self.flags.ext } - pub fn has_hot_reload(&self) -> bool { + pub fn has_hmr(&self) -> bool { if let DenoSubcommand::Run(RunFlags { - watch: Some(WatchFlagsWithPaths { hot_reload, .. }), + watch: Some(WatchFlagsWithPaths { hmr, .. }), .. }) = &self.flags.subcommand { - *hot_reload + *hmr } else { false } diff --git a/cli/factory.rs b/cli/factory.rs index 1a753c9c0436e2..566d8dc471e2b4 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -620,7 +620,7 @@ impl CliFactory { let npm_resolver = self.npm_resolver().await?; let fs = self.fs(); let cli_node_resolver = self.cli_node_resolver().await?; - let maybe_file_watcher_communicator = if self.options.has_hot_reload() { + let maybe_file_watcher_communicator = if self.options.has_hmr() { Some(self.watcher_communicator.clone().unwrap()) } else { None @@ -666,7 +666,7 @@ impl CliFactory { coverage_dir: self.options.coverage_dir(), enable_testing_features: self.options.enable_testing_features(), has_node_modules_dir: self.options.has_node_modules_dir(), - hot_reload: self.options.has_hot_reload(), + hmr: self.options.has_hmr(), inspect_brk: self.options.inspect_brk().is_some(), inspect_wait: self.options.inspect_wait().is_some(), is_inspecting: self.options.is_inspecting(), diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index 99efb7b7d12c81..803655b9a0c3e1 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -455,7 +455,7 @@ pub async fn run( coverage_dir: None, enable_testing_features: false, has_node_modules_dir, - hot_reload: false, + hmr: false, inspect_brk: false, inspect_wait: false, is_inspecting: false, diff --git a/cli/tools/run/hot_reload/json_types.rs b/cli/tools/run/hmr/json_types.rs similarity index 100% rename from cli/tools/run/hot_reload/json_types.rs rename to cli/tools/run/hmr/json_types.rs diff --git a/cli/tools/run/hot_reload/mod.rs b/cli/tools/run/hmr/mod.rs similarity index 99% rename from cli/tools/run/hot_reload/mod.rs rename to cli/tools/run/hmr/mod.rs index 95437f80a95413..eaacab4523a69b 100644 --- a/cli/tools/run/hot_reload/mod.rs +++ b/cli/tools/run/hmr/mod.rs @@ -144,7 +144,7 @@ impl HmrRunner { } } -pub async fn run_hot_reload( +pub async fn run_hot_module_replacement( hmr_manager: &mut HmrRunner, ) -> Result<(), AnyError> { hmr_manager diff --git a/cli/tools/run/mod.rs b/cli/tools/run/mod.rs index 8dba3a2aee5fc0..119129b1bab559 100644 --- a/cli/tools/run/mod.rs +++ b/cli/tools/run/mod.rs @@ -17,7 +17,7 @@ use crate::file_fetcher::File; use crate::util; use crate::util::file_watcher::WatcherRestartMode; -pub mod hot_reload; +pub mod hmr; pub async fn run_script( flags: Flags, @@ -110,11 +110,7 @@ async fn run_with_watch( util::file_watcher::watch_recv( flags, util::file_watcher::PrintConfig::new_with_banner( - if watch_flags.hot_reload { - "HMR" - } else { - "Watcher" - }, + if watch_flags.hmr { "HMR" } else { "Watcher" }, "Process", !watch_flags.no_clear_screen, ), @@ -140,7 +136,7 @@ async fn run_with_watch( .create_main_worker(main_module, permissions) .await?; - if watch_flags.hot_reload { + if watch_flags.hmr { worker.run().await?; } else { worker.run_for_watcher().await?; diff --git a/cli/worker.rs b/cli/worker.rs index 98a052dd013eb2..8b3d6a3dc3c3d6 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -53,8 +53,8 @@ use crate::npm::CliNpmResolver; use crate::ops; use crate::tools; use crate::tools::coverage::CoverageCollector; -use crate::tools::run::hot_reload; -use crate::tools::run::hot_reload::HmrRunner; +use crate::tools::run::hmr; +use crate::tools::run::hmr::HmrRunner; use crate::util::checksum; use crate::util::file_watcher::WatcherCommunicator; use crate::util::file_watcher::WatcherRestartMode; @@ -89,7 +89,7 @@ pub struct CliMainWorkerOptions { pub coverage_dir: Option, pub enable_testing_features: bool, pub has_node_modules_dir: bool, - pub hot_reload: bool, + pub hmr: bool, pub inspect_brk: bool, pub inspect_wait: bool, pub is_inspecting: bool, @@ -146,8 +146,7 @@ impl CliMainWorker { pub async fn run(&mut self) -> Result { let mut maybe_coverage_collector = self.maybe_setup_coverage_collector().await?; - let mut maybe_hot_reload_manager = - self.maybe_setup_hot_reload_manager().await?; + let mut maybe_hmr_runner = self.maybe_setup_hmr_runner().await?; log::debug!("main_module {}", self.main_module); @@ -165,12 +164,12 @@ impl CliMainWorker { self.worker.dispatch_load_event(located_script_name!())?; loop { - if let Some(hot_reload_manager) = maybe_hot_reload_manager.as_mut() { + if let Some(hmr_runner) = maybe_hmr_runner.as_mut() { let watcher_communicator = self.shared.maybe_file_watcher_communicator.clone().unwrap(); let hmr_future = - hot_reload::run_hot_reload(hot_reload_manager).boxed_local(); + hmr::run_hot_module_replacement(hmr_runner).boxed_local(); let event_loop_future = self.worker.run_event_loop(false).boxed_local(); let result; @@ -210,10 +209,10 @@ impl CliMainWorker { .with_event_loop(coverage_collector.stop_collecting().boxed_local()) .await?; } - if let Some(hot_reload_manager) = maybe_hot_reload_manager.as_mut() { + if let Some(hmr_runner) = maybe_hmr_runner.as_mut() { self .worker - .with_event_loop(hot_reload_manager.stop().boxed_local()) + .with_event_loop(hmr_runner.stop().boxed_local()) .await?; } @@ -330,10 +329,10 @@ impl CliMainWorker { } } - pub async fn maybe_setup_hot_reload_manager( + pub async fn maybe_setup_hmr_runner( &mut self, ) -> Result, AnyError> { - if !self.shared.options.hot_reload { + if !self.shared.options.hmr { return Ok(None); } @@ -342,15 +341,14 @@ impl CliMainWorker { let emitter = self.shared.emitter.clone().unwrap(); let session = self.worker.create_inspector_session().await; - let mut hot_reload_manager = - HmrRunner::new(emitter, session, watcher_communicator); + let mut hmr_runner = HmrRunner::new(emitter, session, watcher_communicator); self .worker - .with_event_loop(hot_reload_manager.start().boxed_local()) + .with_event_loop(hmr_runner.start().boxed_local()) .await?; - Ok(Some(hot_reload_manager)) + Ok(Some(hmr_runner)) } pub fn execute_script_static( From e12b33241cb6fed5cd7ac7e5484647ebff68d5ce Mon Sep 17 00:00:00 2001 From: David Sherret Date: Mon, 30 Oct 2023 11:13:02 -0400 Subject: [PATCH 75/79] Arcify --- cli/factory.rs | 6 +++--- cli/graph_util.rs | 4 ++-- cli/tools/run/hmr/mod.rs | 4 ++-- cli/util/file_watcher.rs | 37 ++++++++++++++----------------------- cli/worker.rs | 4 ++-- 5 files changed, 23 insertions(+), 32 deletions(-) diff --git a/cli/factory.rs b/cli/factory.rs index 566d8dc471e2b4..389c4dbe07e407 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -66,7 +66,7 @@ use std::future::Future; use std::sync::Arc; pub struct CliFactoryBuilder { - watcher_communicator: Option, + watcher_communicator: Option>, } impl CliFactoryBuilder { @@ -86,7 +86,7 @@ impl CliFactoryBuilder { pub async fn build_from_flags_for_watcher( mut self, flags: Flags, - watcher_communicator: WatcherCommunicator, + watcher_communicator: Arc, ) -> Result { self.watcher_communicator = Some(watcher_communicator); self.build_from_flags(flags).await @@ -171,7 +171,7 @@ struct CliFactoryServices { } pub struct CliFactory { - watcher_communicator: Option, + watcher_communicator: Option>, options: Arc, services: CliFactoryServices, } diff --git a/cli/graph_util.rs b/cli/graph_util.rs index 2f5fd40fd8dfbd..f2713a9db2e201 100644 --- a/cli/graph_util.rs +++ b/cli/graph_util.rs @@ -681,12 +681,12 @@ impl<'a> ModuleGraphUpdatePermit<'a> { #[derive(Clone, Debug)] pub struct FileWatcherReporter { - watcher_communicator: WatcherCommunicator, + watcher_communicator: Arc, file_paths: Arc>>, } impl FileWatcherReporter { - pub fn new(watcher_communicator: WatcherCommunicator) -> Self { + pub fn new(watcher_communicator: Arc) -> Self { Self { watcher_communicator, file_paths: Default::default(), diff --git a/cli/tools/run/hmr/mod.rs b/cli/tools/run/hmr/mod.rs index eaacab4523a69b..0ab9bf6b133b95 100644 --- a/cli/tools/run/hmr/mod.rs +++ b/cli/tools/run/hmr/mod.rs @@ -40,7 +40,7 @@ use json_types::Status; /// force a full restart of a program by notifying the `FileWatcher`. pub struct HmrRunner { session: LocalInspectorSession, - watcher_communicator: WatcherCommunicator, + watcher_communicator: Arc, script_ids: HashMap, emitter: Arc, } @@ -49,7 +49,7 @@ impl HmrRunner { pub fn new( emitter: Arc, session: LocalInspectorSession, - watcher_communicator: WatcherCommunicator, + watcher_communicator: Arc, ) -> Self { Self { session, diff --git a/cli/util/file_watcher.rs b/cli/util/file_watcher.rs index 562ffd80c477a7..5a316139ccc0ef 100644 --- a/cli/util/file_watcher.rs +++ b/cli/util/file_watcher.rs @@ -17,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; @@ -151,23 +153,11 @@ pub struct WatcherCommunicator { /// Send a message to force a restart. restart_tx: tokio::sync::mpsc::UnboundedSender<()>, - restart_mode: Arc>, + restart_mode: Mutex, banner: String, } -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: self.restart_mode.clone(), - banner: self.banner.clone(), - } - } -} - impl WatcherCommunicator { pub fn watch_paths(&self, paths: Vec) -> Result<(), AnyError> { self.paths_to_watch_tx.send(paths).map_err(AnyError::from) @@ -209,7 +199,7 @@ pub async fn watch_func( where O: FnMut( Flags, - WatcherCommunicator, + Arc, Option>, ) -> Result, F: Future>, @@ -249,7 +239,7 @@ pub async fn watch_recv( where O: FnMut( Flags, - WatcherCommunicator, + Arc, Option>, ) -> Result, F: Future>, @@ -267,25 +257,26 @@ where clear_screen, } = print_config; - let restart_mode = Arc::new(Mutex::new(restart_mode)); let print_after_restart = create_print_after_restart_fn(banner, clear_screen); - let watcher_communicator = WatcherCommunicator { + 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(), - restart_mode: restart_mode.clone(), + restart_mode: Mutex::new(restart_mode), banner: colors::intense_blue(banner).to_string(), - }; + }); info!("{} {} started.", colors::intense_blue(banner), job_name); - let changed_paths = Arc::new(Mutex::new(None)); + 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_.lock() = received_changed_paths.clone(); - match *restart_mode.lock() { + *changed_paths_.borrow_mut() = received_changed_paths.clone(); + + match *watcher_.restart_mode.lock() { WatcherRestartMode::Automatic => { let _ = restart_tx.send(()); } @@ -317,7 +308,7 @@ where let operation_future = error_handler(operation( flags.clone(), watcher_communicator.clone(), - changed_paths.lock().take(), + changed_paths.borrow_mut().take(), )?); // don't reload dependencies after the first run diff --git a/cli/worker.rs b/cli/worker.rs index 8b3d6a3dc3c3d6..8eefdfe3963f18 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -116,7 +116,7 @@ struct SharedWorkerState { root_cert_store_provider: Arc, fs: Arc, emitter: Option>, - maybe_file_watcher_communicator: Option, + maybe_file_watcher_communicator: Option>, maybe_inspector_server: Option>, maybe_lockfile: Option>>, feature_checker: Arc, @@ -378,7 +378,7 @@ impl CliMainWorkerFactory { root_cert_store_provider: Arc, fs: Arc, emitter: Option>, - maybe_file_watcher_communicator: Option, + maybe_file_watcher_communicator: Option>, maybe_inspector_server: Option>, maybe_lockfile: Option>>, feature_checker: Arc, From f5412b7196a6b01b62f15d31988a52f769c970e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 30 Oct 2023 23:23:20 +0100 Subject: [PATCH 76/79] review --- cli/tools/run/hmr/mod.rs | 150 +++++++++++++++++++-------------------- cli/worker.rs | 4 +- 2 files changed, 75 insertions(+), 79 deletions(-) diff --git a/cli/tools/run/hmr/mod.rs b/cli/tools/run/hmr/mod.rs index 0ab9bf6b133b95..6ea04502f764a6 100644 --- a/cli/tools/run/hmr/mod.rs +++ b/cli/tools/run/hmr/mod.rs @@ -142,99 +142,97 @@ impl HmrRunner { Ok(()) } -} -pub async fn run_hot_module_replacement( - hmr_manager: &mut HmrRunner, -) -> Result<(), AnyError> { - hmr_manager - .watcher_communicator - .change_restart_mode(WatcherRestartMode::Manual); - let mut session_rx = hmr_manager.session.take_notification_rx(); - loop { - select! { - biased; - Some(notification) = session_rx.next() => { - let notification = serde_json::from_value::(notification)?; - // TODO(bartlomieju): this is not great... and the code is duplicated with the REPL. - if notification.method == "Runtime.exceptionThrown" { - let params = notification.params; - let exception_details = params.get("exceptionDetails").unwrap().as_object().unwrap(); - let text = exception_details.get("text").unwrap().as_str().unwrap(); - let exception = exception_details.get("exception").unwrap().as_object().unwrap(); - let description = exception.get("description").and_then(|d| d.as_str()).unwrap_or("undefined"); - break Err(generic_error(format!("{text} {description}"))); - } else if notification.method == "Debugger.scriptParsed" { - let params = serde_json::from_value::(notification.params)?; - if params.url.starts_with("file://") { - hmr_manager.script_ids.insert(params.url, params.script_id); + pub async fn run(&mut self) -> Result<(), AnyError> { + self + .watcher_communicator + .change_restart_mode(WatcherRestartMode::Manual); + let mut session_rx = self.session.take_notification_rx(); + loop { + select! { + biased; + Some(notification) = session_rx.next() => { + let notification = serde_json::from_value::(notification)?; + // TODO(bartlomieju): this is not great... and the code is duplicated with the REPL. + if notification.method == "Runtime.exceptionThrown" { + let params = notification.params; + let exception_details = params.get("exceptionDetails").unwrap().as_object().unwrap(); + let text = exception_details.get("text").unwrap().as_str().unwrap(); + let exception = exception_details.get("exception").unwrap().as_object().unwrap(); + let description = exception.get("description").and_then(|d| d.as_str()).unwrap_or("undefined"); + break Err(generic_error(format!("{text} {description}"))); + } else if notification.method == "Debugger.scriptParsed" { + let params = serde_json::from_value::(notification.params)?; + if params.url.starts_with("file://") { + self.script_ids.insert(params.url, params.script_id); + } } } - } - changed_paths = hmr_manager.watcher_communicator.watch_for_changed_paths() => { - let changed_paths = changed_paths?; - - let Some(changed_paths) = changed_paths else { - let _ = hmr_manager.watcher_communicator.force_restart(); - continue; - }; - - let filtered_paths: Vec = changed_paths.into_iter().filter(|p| p.extension().map_or(false, |ext| { - let ext_str = ext.to_str().unwrap(); - matches!(ext_str, "js" | "ts" | "jsx" | "tsx") - })).collect(); - - // If after filtering there are no paths it means it's either a file - // we can't HMR or an external file that was passed explicitly to - // `--unstable-hmr=` path. - if filtered_paths.is_empty() { - let _ = hmr_manager.watcher_communicator.force_restart(); - continue; - } + changed_paths = self.watcher_communicator.watch_for_changed_paths() => { + let changed_paths = changed_paths?; - for path in filtered_paths { - let Some(path_str) = path.to_str() else { - let _ = hmr_manager.watcher_communicator.force_restart(); - continue; - }; - let Ok(module_url) = Url::from_file_path(path_str) else { - let _ = hmr_manager.watcher_communicator.force_restart(); + let Some(changed_paths) = changed_paths else { + let _ = self.watcher_communicator.force_restart(); continue; }; - let Some(id) = hmr_manager.script_ids.get(module_url.as_str()).cloned() else { - let _ = hmr_manager.watcher_communicator.force_restart(); + let filtered_paths: Vec = changed_paths.into_iter().filter(|p| p.extension().map_or(false, |ext| { + let ext_str = ext.to_str().unwrap(); + matches!(ext_str, "js" | "ts" | "jsx" | "tsx") + })).collect(); + + // If after filtering there are no paths it means it's either a file + // we can't HMR or an external file that was passed explicitly to + // `--unstable-hmr=` path. + if filtered_paths.is_empty() { + let _ = self.watcher_communicator.force_restart(); continue; - }; + } - hmr_manager.watcher_communicator.print(format!("Reloading changed module {}", module_url.as_str())); + for path in filtered_paths { + let Some(path_str) = path.to_str() else { + let _ = self.watcher_communicator.force_restart(); + continue; + }; + let Ok(module_url) = Url::from_file_path(path_str) else { + let _ = self.watcher_communicator.force_restart(); + continue; + }; - let source_code = hmr_manager.emitter.load_and_emit_for_hmr( - &module_url - ).await?; + let Some(id) = self.script_ids.get(module_url.as_str()).cloned() else { + let _ = self.watcher_communicator.force_restart(); + continue; + }; - let mut tries = 1; - loop { - let result = hmr_manager.set_script_source(&id, source_code.as_str()).await?; + self.watcher_communicator.print(format!("Reloading changed module {}", module_url.as_str())); - if matches!(result.status, Status::Ok) { - hmr_manager.dispatch_hmr_event(module_url.as_str()).await?; - break; - } + let source_code = self.emitter.load_and_emit_for_hmr( + &module_url + ).await?; - hmr_manager.watcher_communicator.print(format!("Failed to reload module {}: {}.", module_url, colors::gray(result.status.explain()))); - if result.status.should_retry() && tries <= 2 { - tries += 1; - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - continue; - } + let mut tries = 1; + loop { + let result = self.set_script_source(&id, source_code.as_str()).await?; + + if matches!(result.status, Status::Ok) { + self.dispatch_hmr_event(module_url.as_str()).await?; + break; + } + + self.watcher_communicator.print(format!("Failed to reload module {}: {}.", module_url, colors::gray(result.status.explain()))); + if result.status.should_retry() && tries <= 2 { + tries += 1; + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + continue; + } - let _ = hmr_manager.watcher_communicator.force_restart(); - break; + let _ = self.watcher_communicator.force_restart(); + break; + } } } + _ = self.session.receive_from_v8_session() => {} } - _ = hmr_manager.session.receive_from_v8_session() => {} } } } diff --git a/cli/worker.rs b/cli/worker.rs index 8eefdfe3963f18..58bd96642ef35a 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -53,7 +53,6 @@ use crate::npm::CliNpmResolver; use crate::ops; use crate::tools; use crate::tools::coverage::CoverageCollector; -use crate::tools::run::hmr; use crate::tools::run::hmr::HmrRunner; use crate::util::checksum; use crate::util::file_watcher::WatcherCommunicator; @@ -168,8 +167,7 @@ impl CliMainWorker { let watcher_communicator = self.shared.maybe_file_watcher_communicator.clone().unwrap(); - let hmr_future = - hmr::run_hot_module_replacement(hmr_runner).boxed_local(); + let hmr_future = hmr_runner.run().boxed_local(); let event_loop_future = self.worker.run_event_loop(false).boxed_local(); let result; From b8f4e5ef182bdfcc2dcf88bcdcfe0fbe660cc321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 30 Oct 2023 23:51:00 +0100 Subject: [PATCH 77/79] handle Arc --- cli/emit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/emit.rs b/cli/emit.rs index c84776ae4d76ef..8e51c4eddeaddc 100644 --- a/cli/emit.rs +++ b/cli/emit.rs @@ -111,7 +111,7 @@ impl Emitter { ModuleSpecifier::to_file_path(specifier).unwrap(), ) .await?; - let source_arc: Arc = Arc::from(source_code.as_str()); + let source_arc: Arc = source_code.into(); let parsed_source = self .parsed_source_cache .get_or_parse_module(specifier, source_arc, media_type)?; From a3a0fdbda0d50932bd24419d2b1a321640b3bc1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 31 Oct 2023 00:41:02 +0100 Subject: [PATCH 78/79] add integration tests --- cli/tests/integration/watcher_tests.rs | 254 ++++++++++++++++++ cli/tests/testdata/hmr/jsx/foo.jsx | 3 - cli/tests/testdata/hmr/jsx/main.js | 6 - cli/tests/testdata/hmr/server/mod.ts | 13 - cli/tests/testdata/hmr/uncaught_error/foo.jsx | 6 - cli/tests/testdata/hmr/uncaught_error/main.js | 6 - .../testdata/hmr/unhandled_rejection/main.js | 14 - cli/tools/run/hmr/mod.rs | 10 +- 8 files changed, 261 insertions(+), 51 deletions(-) delete mode 100644 cli/tests/testdata/hmr/jsx/foo.jsx delete mode 100644 cli/tests/testdata/hmr/jsx/main.js delete mode 100644 cli/tests/testdata/hmr/server/mod.ts delete mode 100644 cli/tests/testdata/hmr/uncaught_error/foo.jsx delete mode 100644 cli/tests/testdata/hmr/uncaught_error/main.js delete mode 100644 cli/tests/testdata/hmr/unhandled_rejection/main.js diff --git a/cli/tests/integration/watcher_tests.rs b/cli/tests/integration/watcher_tests.rs index 1ee8a45e083c10..0defaa69eaa3b9 100644 --- a/cli/tests/integration/watcher_tests.rs +++ b/cli/tests/integration/watcher_tests.rs @@ -1645,3 +1645,257 @@ async fn run_watch_inspect() { check_alive_then_kill(child); } + +#[tokio::test] +async fn run_hmr_server() { + let t = TempDir::new(); + let file_to_watch = t.path().join("file_to_watch.js"); + file_to_watch.write( + r#" +globalThis.state = { i: 0 }; + +function bar() { + globalThis.state.i = 0; + console.log("got request", globalThis.state.i); +} + +function handler(_req) { + bar(); + return new Response("Hello world!"); +} + +Deno.serve({ port: 11111 }, handler); +console.log("Listening...") + "#, + ); + + let mut child = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("--unstable-hmr") + .arg("--allow-net") + .arg("-L") + .arg("debug") + .arg(&file_to_watch) + .env("NO_COLOR", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); + wait_contains("Process started", &mut stderr_lines).await; + wait_contains("No package.json file found", &mut stderr_lines).await; + + wait_for_watcher("file_to_watch.js", &mut stderr_lines).await; + wait_contains("Listening...", &mut stdout_lines).await; + + file_to_watch.write( + r#" +globalThis.state = { i: 0 }; + +function bar() { + globalThis.state.i = 0; + console.log("got request1", globalThis.state.i); +} + +function handler(_req) { + bar(); + return new Response("Hello world!"); +} + +Deno.serve({ port: 11111 }, handler); +console.log("Listening...") + "#, + ); + + wait_contains("Failed to reload module", &mut stderr_lines).await; + wait_contains("File change detected", &mut stderr_lines).await; + + check_alive_then_kill(child); +} + +#[tokio::test] +async fn run_hmr_jsx() { + let t = TempDir::new(); + let file_to_watch = t.path().join("file_to_watch.js"); + file_to_watch.write( + r#" +import { foo } from "./foo.jsx"; + +let i = 0; +setInterval(() => { + console.log(i++, foo()); +}, 100); +"#, + ); + let file_to_watch2 = t.path().join("foo.jsx"); + file_to_watch2.write( + r#" +export function foo() { + return `

Hello

`; +} +"#, + ); + + let mut child = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("--unstable-hmr") + .arg("-L") + .arg("debug") + .arg(&file_to_watch) + .env("NO_COLOR", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); + wait_contains("Process started", &mut stderr_lines).await; + wait_contains("No package.json file found", &mut stderr_lines).await; + + wait_for_watcher("file_to_watch.js", &mut stderr_lines).await; + wait_contains("5

Hello

", &mut stdout_lines).await; + + file_to_watch2.write( + r#" +export function foo() { + return `

Hello world

`; +} + "#, + ); + + wait_contains("Replaced changed module", &mut stderr_lines).await; + wait_contains("

Hello world

", &mut stdout_lines).await; + + check_alive_then_kill(child); +} + +#[tokio::test] +async fn run_hmr_uncaught_error() { + let t = TempDir::new(); + let file_to_watch = t.path().join("file_to_watch.js"); + file_to_watch.write( + r#" +import { foo } from "./foo.jsx"; + +let i = 0; +setInterval(() => { + console.log(i++, foo()); +}, 100); +"#, + ); + let file_to_watch2 = t.path().join("foo.jsx"); + file_to_watch2.write( + r#" +export function foo() { + setTimeout(() => { + throw new Error("fail"); + }); + return `

asd1

`; +} +"#, + ); + + let mut child = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("--unstable-hmr") + .arg("-L") + .arg("debug") + .arg(&file_to_watch) + .env("NO_COLOR", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); + wait_contains("Process started", &mut stderr_lines).await; + wait_contains("No package.json file found", &mut stderr_lines).await; + + wait_for_watcher("file_to_watch.js", &mut stderr_lines).await; + wait_contains("

asd1

", &mut stdout_lines).await; + wait_contains("fail", &mut stderr_lines).await; + + file_to_watch2.write( + r#" +export function foo() { + return `

asd2

`; +} + "#, + ); + + wait_contains("Process failed", &mut stderr_lines).await; + wait_contains("File change detected", &mut stderr_lines).await; + wait_contains("

asd2

", &mut stdout_lines).await; + + check_alive_then_kill(child); +} + +#[tokio::test] +async fn run_hmr_unhandled_rejection() { + let t = TempDir::new(); + let file_to_watch = t.path().join("file_to_watch.js"); + file_to_watch.write( + r#" +import { foo } from "./foo.jsx"; + +// deno-lint-ignore require-await +async function rejection() { + throw new Error("boom!"); +} + +let i = 0; +setInterval(() => { + if (i == 3) { + rejection(); + } + console.log(i++, foo()); +}, 100); +"#, + ); + let file_to_watch2 = t.path().join("foo.jsx"); + file_to_watch2.write( + r#" +export function foo() { + return `

asd1

`; +} +"#, + ); + + let mut child = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("--unstable-hmr") + .arg("-L") + .arg("debug") + .arg(&file_to_watch) + .env("NO_COLOR", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); + wait_contains("Process started", &mut stderr_lines).await; + wait_contains("No package.json file found", &mut stderr_lines).await; + + wait_for_watcher("file_to_watch.js", &mut stderr_lines).await; + wait_contains("2

asd1

", &mut stdout_lines).await; + wait_contains("boom", &mut stderr_lines).await; + + file_to_watch.write( + r#" +import { foo } from "./foo.jsx"; + +let i = 0; +setInterval(() => { + console.log(i++, foo()); +}, 100); + "#, + ); + + wait_contains("Process failed", &mut stderr_lines).await; + wait_contains("File change detected", &mut stderr_lines).await; + wait_contains("

asd1

", &mut stdout_lines).await; + + check_alive_then_kill(child); +} diff --git a/cli/tests/testdata/hmr/jsx/foo.jsx b/cli/tests/testdata/hmr/jsx/foo.jsx deleted file mode 100644 index 8c6508eb3376a8..00000000000000 --- a/cli/tests/testdata/hmr/jsx/foo.jsx +++ /dev/null @@ -1,3 +0,0 @@ -export function foo() { - return `

Hello

`; -} diff --git a/cli/tests/testdata/hmr/jsx/main.js b/cli/tests/testdata/hmr/jsx/main.js deleted file mode 100644 index 5ed940ab3e2e8d..00000000000000 --- a/cli/tests/testdata/hmr/jsx/main.js +++ /dev/null @@ -1,6 +0,0 @@ -import { foo } from "./foo.jsx"; - -let i = 0; -setInterval(() => { - console.log(i++, foo()); -}, 100); diff --git a/cli/tests/testdata/hmr/server/mod.ts b/cli/tests/testdata/hmr/server/mod.ts deleted file mode 100644 index 24d441788d1730..00000000000000 --- a/cli/tests/testdata/hmr/server/mod.ts +++ /dev/null @@ -1,13 +0,0 @@ -globalThis.state = { i: 0 }; - -function bar() { - globalThis.state.i = 0; - console.log("got request", globalThis.state.i); -} - -function handler(_req) { - bar(); - return new Response("Hello world!"); -} - -Deno.serve(handler); diff --git a/cli/tests/testdata/hmr/uncaught_error/foo.jsx b/cli/tests/testdata/hmr/uncaught_error/foo.jsx deleted file mode 100644 index 65feecf113ebff..00000000000000 --- a/cli/tests/testdata/hmr/uncaught_error/foo.jsx +++ /dev/null @@ -1,6 +0,0 @@ -export function foo() { - setTimeout(() => { - throw new Error("fail"); - }); - return `

asd1

`; -} diff --git a/cli/tests/testdata/hmr/uncaught_error/main.js b/cli/tests/testdata/hmr/uncaught_error/main.js deleted file mode 100644 index 5ed940ab3e2e8d..00000000000000 --- a/cli/tests/testdata/hmr/uncaught_error/main.js +++ /dev/null @@ -1,6 +0,0 @@ -import { foo } from "./foo.jsx"; - -let i = 0; -setInterval(() => { - console.log(i++, foo()); -}, 100); diff --git a/cli/tests/testdata/hmr/unhandled_rejection/main.js b/cli/tests/testdata/hmr/unhandled_rejection/main.js deleted file mode 100644 index 39a926de0fd221..00000000000000 --- a/cli/tests/testdata/hmr/unhandled_rejection/main.js +++ /dev/null @@ -1,14 +0,0 @@ -import { foo } from "./foo.jsx"; - -// deno-lint-ignore require-await -async function rejection() { - throw new Error("boom!"); -} - -let i = 0; -setInterval(() => { - if (i == 3) { - rejection(); - } - console.log(i++, foo()); -}, 100); diff --git a/cli/tools/run/hmr/mod.rs b/cli/tools/run/hmr/mod.rs index 6ea04502f764a6..9e0fbe3418e81d 100644 --- a/cli/tools/run/hmr/mod.rs +++ b/cli/tools/run/hmr/mod.rs @@ -164,7 +164,12 @@ impl HmrRunner { } else if notification.method == "Debugger.scriptParsed" { let params = serde_json::from_value::(notification.params)?; if params.url.starts_with("file://") { - self.script_ids.insert(params.url, params.script_id); + let file_url = Url::parse(¶ms.url).unwrap(); + let file_path = file_url.to_file_path().unwrap(); + if let Some(canonicalized_file_path) = file_path.canonicalize().ok() { + let canonicalized_file_url = Url::from_file_path(canonicalized_file_path).unwrap(); + self.script_ids.insert(canonicalized_file_url.to_string(), params.script_id); + } } } } @@ -204,8 +209,6 @@ impl HmrRunner { continue; }; - self.watcher_communicator.print(format!("Reloading changed module {}", module_url.as_str())); - let source_code = self.emitter.load_and_emit_for_hmr( &module_url ).await?; @@ -216,6 +219,7 @@ impl HmrRunner { if matches!(result.status, Status::Ok) { self.dispatch_hmr_event(module_url.as_str()).await?; + self.watcher_communicator.print(format!("Replaced changed module {}", module_url.as_str())); break; } From ca8677e06bc5fb8a9afb1e3f3cd6c691904f3f8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 31 Oct 2023 00:49:16 +0100 Subject: [PATCH 79/79] lint --- cli/tools/run/hmr/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/tools/run/hmr/mod.rs b/cli/tools/run/hmr/mod.rs index 9e0fbe3418e81d..1a577230702ccf 100644 --- a/cli/tools/run/hmr/mod.rs +++ b/cli/tools/run/hmr/mod.rs @@ -166,7 +166,7 @@ impl HmrRunner { if params.url.starts_with("file://") { let file_url = Url::parse(¶ms.url).unwrap(); let file_path = file_url.to_file_path().unwrap(); - if let Some(canonicalized_file_path) = file_path.canonicalize().ok() { + if let Ok(canonicalized_file_path) = file_path.canonicalize() { let canonicalized_file_url = Url::from_file_path(canonicalized_file_path).unwrap(); self.script_ids.insert(canonicalized_file_url.to_string(), params.script_id); }