diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2023-10-31 01:25:58 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-31 01:25:58 +0100 |
commit | 1713df13524ad20c6bd0413bcf4aa57adc3f9735 (patch) | |
tree | a558239a7f483cab10b83305b333d5ef9657bd3e /cli/tools/run/mod.rs | |
parent | 48c5c3a3fb2f43716528db8915b36e55c411d94f (diff) |
feat: deno run --unstable-hmr (#20876)
This commit adds `--unstable-hmr` flag, that enabled Hot Module Replacement.
This flag works like `--watch` and accepts the same arguments. If
HMR is not possible the process will be restarted instead.
Currently HMR is only supported in `deno run` subcommand.
Upon HMR a `CustomEvent("hmr")` will be dispatched that contains
information which file was changed in its `details` property.
---------
Co-authored-by: Valentin Anger <syrupthinker@gryphno.de>
Co-authored-by: David Sherret <dsherret@gmail.com>
Diffstat (limited to 'cli/tools/run/mod.rs')
-rw-r--r-- | cli/tools/run/mod.rs | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/cli/tools/run/mod.rs b/cli/tools/run/mod.rs new file mode 100644 index 000000000..119129b1b --- /dev/null +++ b/cli/tools/run/mod.rs @@ -0,0 +1,205 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use std::io::Read; + +use deno_ast::MediaType; +use deno_core::error::AnyError; +use deno_runtime::permissions::Permissions; +use deno_runtime::permissions::PermissionsContainer; + +use crate::args::EvalFlags; +use crate::args::Flags; +use crate::args::RunFlags; +use crate::args::WatchFlagsWithPaths; +use crate::factory::CliFactory; +use crate::factory::CliFactoryBuilder; +use crate::file_fetcher::File; +use crate::util; +use crate::util::file_watcher::WatcherRestartMode; + +pub mod hmr; + +pub async fn run_script( + flags: Flags, + run_flags: RunFlags, +) -> Result<i32, AnyError> { + if !flags.has_permission() && flags.has_permission_in_argv() { + log::warn!( + "{}", + crate::colors::yellow( + r#"Permission flags have likely been incorrectly set after the script argument. +To grant permissions, set them before the script argument. For example: + deno run --allow-read=. main.js"# + ) + ); + } + + if let Some(watch_flags) = run_flags.watch { + return run_with_watch(flags, watch_flags).await; + } + + // TODO(bartlomieju): actually I think it will also fail if there's an import + // map specified and bare specifier is used on the command line + let factory = CliFactory::from_flags(flags).await?; + let deno_dir = factory.deno_dir()?; + let http_client = factory.http_client(); + let cli_options = factory.cli_options(); + + // Run a background task that checks for available upgrades. If an earlier + // run of this background task found a new version of Deno. + super::upgrade::check_for_upgrades( + http_client.clone(), + deno_dir.upgrade_check_file_path(), + ); + + let main_module = cli_options.resolve_main_module()?; + + maybe_npm_install(&factory).await?; + + let permissions = PermissionsContainer::new(Permissions::from_options( + &cli_options.permissions_options(), + )?); + let worker_factory = factory.create_cli_main_worker_factory().await?; + let mut worker = worker_factory + .create_main_worker(main_module, permissions) + .await?; + + let exit_code = worker.run().await?; + Ok(exit_code) +} + +pub async fn run_from_stdin(flags: Flags) -> Result<i32, AnyError> { + let factory = CliFactory::from_flags(flags).await?; + let cli_options = factory.cli_options(); + let main_module = cli_options.resolve_main_module()?; + + maybe_npm_install(&factory).await?; + + let file_fetcher = factory.file_fetcher()?; + let worker_factory = factory.create_cli_main_worker_factory().await?; + let permissions = PermissionsContainer::new(Permissions::from_options( + &cli_options.permissions_options(), + )?); + let mut source = Vec::new(); + std::io::stdin().read_to_end(&mut source)?; + // Create a dummy source file. + let source_file = File { + maybe_types: None, + media_type: MediaType::TypeScript, + source: String::from_utf8(source)?.into(), + specifier: main_module.clone(), + maybe_headers: None, + }; + // Save our fake file into file fetcher cache + // to allow module access by TS compiler + file_fetcher.insert_cached(source_file); + + let mut worker = worker_factory + .create_main_worker(main_module, permissions) + .await?; + let exit_code = worker.run().await?; + Ok(exit_code) +} + +// TODO(bartlomieju): this function is not handling `exit_code` set by the runtime +// code properly. +async fn run_with_watch( + flags: Flags, + watch_flags: WatchFlagsWithPaths, +) -> Result<i32, AnyError> { + util::file_watcher::watch_recv( + flags, + util::file_watcher::PrintConfig::new_with_banner( + if watch_flags.hmr { "HMR" } else { "Watcher" }, + "Process", + !watch_flags.no_clear_screen, + ), + 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.hmr { + worker.run().await?; + } else { + worker.run_for_watcher().await?; + } + + Ok(()) + }) + }, + ) + .await?; + + Ok(0) +} + +pub async fn eval_command( + flags: Flags, + eval_flags: EvalFlags, +) -> Result<i32, AnyError> { + let factory = CliFactory::from_flags(flags).await?; + let cli_options = factory.cli_options(); + let file_fetcher = factory.file_fetcher()?; + let main_module = cli_options.resolve_main_module()?; + + maybe_npm_install(&factory).await?; + + // Create a dummy source file. + let source_code = if eval_flags.print { + format!("console.log({})", eval_flags.code) + } else { + eval_flags.code + } + .into_bytes(); + + let file = File { + maybe_types: None, + media_type: MediaType::Unknown, + source: String::from_utf8(source_code)?.into(), + specifier: main_module.clone(), + maybe_headers: None, + }; + + // Save our fake file into file fetcher cache + // to allow module access by TS compiler. + file_fetcher.insert_cached(file); + + let permissions = PermissionsContainer::new(Permissions::from_options( + &cli_options.permissions_options(), + )?); + let worker_factory = factory.create_cli_main_worker_factory().await?; + let mut worker = worker_factory + .create_main_worker(main_module, permissions) + .await?; + let exit_code = worker.run().await?; + Ok(exit_code) +} + +async fn maybe_npm_install(factory: &CliFactory) -> Result<(), AnyError> { + // ensure an "npm install" is done if the user has explicitly + // opted into using a managed node_modules directory + if factory.cli_options().node_modules_dir_enablement() == Some(true) { + if let Some(npm_resolver) = factory.npm_resolver().await?.as_managed() { + npm_resolver.ensure_top_level_package_json_install().await?; + } + } + Ok(()) +} |