Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: deno run --unstable-hmr #20876

Merged
merged 88 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
2698f13
Prototype module hot-reloading
SyrupThinker Aug 20, 2023
fbfe7b9
Merge branch 'main' into hot_reload
bartlomieju Oct 10, 2023
139d09c
update patch in Cargo.toml
bartlomieju Oct 10, 2023
e763c48
add hmr.js
bartlomieju Oct 10, 2023
8f4c91f
Merge branch 'main' into hot_reload
bartlomieju Oct 14, 2023
cfe431f
fix after merge, use deno_core from git
bartlomieju Oct 14, 2023
86d2a69
rename to --hot
bartlomieju Oct 14, 2023
43047d5
add todos
bartlomieju Oct 15, 2023
769c573
transpilation todo
bartlomieju Oct 15, 2023
44ada3d
restart process on blocked hmr
bartlomieju Oct 16, 2023
b93c090
simplify cli
bartlomieju Oct 16, 2023
8b07090
copyright, todo
bartlomieju Oct 16, 2023
0b58bac
transpile
bartlomieju Oct 16, 2023
f467c8e
fix
bartlomieju Oct 16, 2023
dd10397
fix multiple updates
bartlomieju Oct 16, 2023
1680d24
fix transpilation by disabling source maps
bartlomieju Oct 16, 2023
1df1d6e
dispatch 'hmr' event on module replace
bartlomieju Oct 16, 2023
91b7162
ignore exit code
bartlomieju Oct 17, 2023
a7e9f43
Add temporary WatcherInterface
bartlomieju Oct 17, 2023
b56c1f5
handle errors
bartlomieju Oct 17, 2023
4df3cae
try to debug rejections
bartlomieju Oct 17, 2023
c9f7bd0
watcher refactor
bartlomieju Oct 17, 2023
4afa6ab
flags test
bartlomieju Oct 17, 2023
417e455
flatten a function
bartlomieju Oct 17, 2023
13022ef
make restart watcher required
bartlomieju Oct 17, 2023
62e4942
simplify passing channels
bartlomieju Oct 17, 2023
3079c91
debug
bartlomieju Oct 17, 2023
524ba86
handle unhandled rejections
bartlomieju Oct 17, 2023
0082222
nicer error
bartlomieju Oct 17, 2023
56ccc3e
more unification!
bartlomieju Oct 17, 2023
62eac1c
channel creation hoised out of the loop
bartlomieju Oct 17, 2023
b832c7f
remove optional field
bartlomieju Oct 17, 2023
85011e8
clone WatcherInterface
bartlomieju Oct 17, 2023
3d822c2
remove HotReloadInterface
bartlomieju Oct 17, 2023
8c5340d
renames
bartlomieju Oct 17, 2023
3ee3121
add a todo
bartlomieju Oct 17, 2023
1390c71
Add a copt
bartlomieju Oct 18, 2023
4e0081c
unify into a single function!
bartlomieju Oct 18, 2023
9ecb1dd
try to debug why short program doesn't watch for paths
bartlomieju Oct 18, 2023
51c5416
Run_hot_replace
bartlomieju Oct 18, 2023
3298be8
fs watcher changes
bartlomieju Oct 18, 2023
d916d45
revert unrelated changes
bartlomieju Oct 18, 2023
104257a
revert unrelated changes
bartlomieju Oct 18, 2023
d040ba7
print before restart
bartlomieju Oct 18, 2023
a1c0512
remove one todo
bartlomieju Oct 18, 2023
0700b5f
remove stale todo
bartlomieju Oct 18, 2023
4ff0cc6
revert some changes to not overflow the stack on Windows
bartlomieju Oct 18, 2023
11bf7fb
try to prevent stack overflow on Windows
bartlomieju Oct 18, 2023
c565d52
typo
bartlomieju Oct 18, 2023
3eca457
Revert "revert some changes to not overflow the stack on Windows"
bartlomieju Oct 18, 2023
c065dd7
review
bartlomieju Oct 19, 2023
129056b
Merge branch 'fs_watcher_refactors' into hot_reload
bartlomieju Oct 19, 2023
0f4e31c
updates after merge
bartlomieju Oct 19, 2023
9ac9d72
Merge branch 'main' into hot_reload
bartlomieju Oct 19, 2023
87adbb6
remove with_event_loop_fallible
bartlomieju Oct 20, 2023
8ee89a4
unify run subcommand
bartlomieju Oct 20, 2023
02b0675
PrintConfig::new
bartlomieju Oct 20, 2023
9bacd17
make banner configurable
bartlomieju Oct 20, 2023
7c8a550
change flag to --unstable-hmr
bartlomieju Oct 20, 2023
23edc5f
Merge branch 'main' into hot_reload
bartlomieju Oct 22, 2023
62d1c9e
try to debug why hmr stops
bartlomieju Oct 22, 2023
3458a03
short lived scripts work, long lived scripts now broken :(
bartlomieju Oct 22, 2023
53c5fa3
working for short lived programs
bartlomieju Oct 22, 2023
d99ab1f
remove debug logs
bartlomieju Oct 22, 2023
2f423dd
fix logs
bartlomieju Oct 22, 2023
b695d51
fix on returned error
bartlomieju Oct 22, 2023
76de135
lint
bartlomieju Oct 22, 2023
35e519e
store banner in communicator
bartlomieju Oct 22, 2023
741ace3
Address TODOs
bartlomieju Oct 22, 2023
859416a
wip
bartlomieju Oct 22, 2023
9cbc5b6
Merge branch 'main' into hot_reload
bartlomieju Oct 25, 2023
e72c02b
cleanup emitter handling
bartlomieju Oct 25, 2023
c02df02
Merge branch 'main' into hot_reload
bartlomieju Oct 27, 2023
9530648
start working on tests
bartlomieju Oct 27, 2023
2ebf0cb
lint
bartlomieju Oct 27, 2023
c5a01fc
Merge branch 'main' into hot_reload
bartlomieju Oct 27, 2023
dcda421
restart on external path change
bartlomieju Oct 27, 2023
7881e4e
revert to automatic mode on forced restart
bartlomieju Oct 27, 2023
44ca2d3
Merge branch 'main' into hot_reload
bartlomieju Oct 30, 2023
af535fb
review part one
bartlomieju Oct 30, 2023
55f7593
review part two
bartlomieju Oct 30, 2023
757e885
review part three
bartlomieju Oct 30, 2023
4ac6795
renames: hot_reload -> hmr
bartlomieju Oct 30, 2023
e12b332
Arcify
dsherret Oct 30, 2023
f5412b7
review
bartlomieju Oct 30, 2023
b8f4e5e
handle Arc
bartlomieju Oct 30, 2023
a3a0fdb
add integration tests
bartlomieju Oct 30, 2023
ca8677e
lint
bartlomieju Oct 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 131 additions & 27 deletions cli/args/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,13 @@ impl RunFlags {

#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct WatchFlags {
pub hmr: bool,
pub no_clear_screen: bool,
}

#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct WatchFlagsWithPaths {
pub hmr: bool,
pub paths: Vec<PathBuf>,
pub no_clear_screen: bool,
}
Expand Down Expand Up @@ -1854,6 +1856,7 @@ fn run_subcommand() -> Command {
runtime_args(Command::new("run"), true, true)
.arg(check_arg(false))
.arg(watch_arg(true))
.arg(hmr_arg(true))
.arg(no_clear_screen_arg())
.arg(executable_ext_arg())
.arg(
Expand Down Expand Up @@ -2722,6 +2725,33 @@ fn seed_arg() -> Arg {
.value_parser(value_parser!(u64))
}

fn hmr_arg(takes_files: bool) -> Arg {
let arg = Arg::new("hmr")
.long("unstable-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 {
let arg = Arg::new("watch")
.long("watch")
Expand Down Expand Up @@ -3835,6 +3865,7 @@ fn reload_arg_validate(urlstr: &str) -> Result<String, String> {
fn watch_arg_parse(matches: &mut ArgMatches) -> Option<WatchFlags> {
if matches.get_flag("watch") {
Some(WatchFlags {
hmr: false,
no_clear_screen: matches.get_flag("no-clear-screen"),
})
} else {
Expand All @@ -3845,10 +3876,19 @@ fn watch_arg_parse(matches: &mut ArgMatches) -> Option<WatchFlags> {
fn watch_arg_parse_with_paths(
matches: &mut ArgMatches,
) -> Option<WatchFlagsWithPaths> {
if let Some(paths) = matches.remove_many::<PathBuf>("watch") {
return Some(WatchFlagsWithPaths {
paths: paths.collect(),
hmr: false,
no_clear_screen: matches.get_flag("no-clear-screen"),
});
}

matches
.remove_many::<PathBuf>("watch")
.map(|f| WatchFlagsWithPaths {
paths: f.collect(),
.remove_many::<PathBuf>("hmr")
.map(|paths| WatchFlagsWithPaths {
paths: paths.collect(),
hmr: true,
no_clear_screen: matches.get_flag("no-clear-screen"),
})
}
Expand Down Expand Up @@ -3966,13 +4006,87 @@ mod tests {
subcommand: DenoSubcommand::Run(RunFlags {
script: "script.ts".to_string(),
watch: Some(WatchFlagsWithPaths {
hmr: false,
paths: vec![],
no_clear_screen: false,
}),
}),
..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 {
hmr: false,
paths: vec![],
no_clear_screen: true,
}),
}),
..Flags::default()
}
);

let r = flags_from_vec(svec![
"deno",
"run",
"--unstable-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 {
hmr: true,
paths: vec![],
no_clear_screen: true,
}),
}),
..Flags::default()
}
);

let r = flags_from_vec(svec![
"deno",
"run",
"--unstable-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 {
hmr: 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]
Expand All @@ -3986,6 +4100,7 @@ mod tests {
subcommand: DenoSubcommand::Run(RunFlags {
script: "script.ts".to_string(),
watch: Some(WatchFlagsWithPaths {
hmr: false,
paths: vec![PathBuf::from("file1"), PathBuf::from("file2")],
no_clear_screen: false,
}),
Expand All @@ -4012,6 +4127,7 @@ mod tests {
subcommand: DenoSubcommand::Run(RunFlags {
script: "script.ts".to_string(),
watch: Some(WatchFlagsWithPaths {
hmr: false,
paths: vec![],
no_clear_screen: true,
})
Expand Down Expand Up @@ -4333,9 +4449,7 @@ mod tests {
single_quote: None,
prose_wrap: None,
no_semicolons: None,
watch: Some(WatchFlags {
no_clear_screen: false,
})
watch: Some(Default::default()),
}),
ext: Some("ts".to_string()),
..Flags::default()
Expand All @@ -4360,6 +4474,7 @@ mod tests {
prose_wrap: None,
no_semicolons: None,
watch: Some(WatchFlags {
hmr: false,
no_clear_screen: true,
})
}),
Expand Down Expand Up @@ -4391,9 +4506,7 @@ mod tests {
single_quote: None,
prose_wrap: None,
no_semicolons: None,
watch: Some(WatchFlags {
no_clear_screen: false,
})
watch: Some(Default::default()),
}),
ext: Some("ts".to_string()),
..Flags::default()
Expand Down Expand Up @@ -4447,9 +4560,7 @@ mod tests {
single_quote: None,
prose_wrap: None,
no_semicolons: None,
watch: Some(WatchFlags {
no_clear_screen: false,
})
watch: Some(Default::default()),
}),
config_flag: ConfigFlag::Path("deno.jsonc".to_string()),
ext: Some("ts".to_string()),
Expand Down Expand Up @@ -4573,9 +4684,7 @@ mod tests {
maybe_rules_exclude: None,
json: false,
compact: false,
watch: Some(WatchFlags {
no_clear_screen: false,
})
watch: Some(Default::default()),
}),
..Flags::default()
}
Expand Down Expand Up @@ -4607,6 +4716,7 @@ mod tests {
json: false,
compact: false,
watch: Some(WatchFlags {
hmr: false,
no_clear_screen: true,
})
}),
Expand Down Expand Up @@ -5809,9 +5919,7 @@ mod tests {
subcommand: DenoSubcommand::Bundle(BundleFlags {
source_file: "source.ts".to_string(),
out_file: None,
watch: Some(WatchFlags {
no_clear_screen: false,
}),
watch: Some(Default::default()),
}),
type_check_mode: TypeCheckMode::Local,
..Flags::default()
Expand All @@ -5835,6 +5943,7 @@ mod tests {
source_file: "source.ts".to_string(),
out_file: None,
watch: Some(WatchFlags {
hmr: false,
no_clear_screen: true,
}),
}),
Expand Down Expand Up @@ -7003,9 +7112,7 @@ mod tests {
concurrent_jobs: None,
trace_ops: false,
coverage_dir: None,
watch: Some(WatchFlags {
no_clear_screen: false,
}),
watch: Some(Default::default()),
reporter: Default::default(),
junit_path: None,
}),
Expand Down Expand Up @@ -7035,9 +7142,7 @@ mod tests {
concurrent_jobs: None,
trace_ops: false,
coverage_dir: None,
watch: Some(WatchFlags {
no_clear_screen: false,
}),
watch: Some(Default::default()),
reporter: Default::default(),
junit_path: None,
}),
Expand Down Expand Up @@ -7070,6 +7175,7 @@ mod tests {
trace_ops: false,
coverage_dir: None,
watch: Some(WatchFlags {
hmr: false,
no_clear_screen: true,
}),
reporter: Default::default(),
Expand Down Expand Up @@ -7779,9 +7885,7 @@ mod tests {
include: vec![],
ignore: vec![],
},
watch: Some(WatchFlags {
no_clear_screen: false,
}),
watch: Some(Default::default()),
}),
no_prompt: true,
type_check_mode: TypeCheckMode::Local,
Expand Down
12 changes: 12 additions & 0 deletions cli/args/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,18 @@ impl CliOptions {
&self.flags.ext
}

pub fn has_hmr(&self) -> bool {
if let DenoSubcommand::Run(RunFlags {
watch: Some(WatchFlagsWithPaths { hmr, .. }),
..
}) = &self.flags.subcommand
{
*hmr
} else {
false
}
}

/// If the --inspect or --inspect-brk flags are used.
pub fn is_inspecting(&self) -> bool {
self.flags.inspect.is_some()
Expand Down
20 changes: 20 additions & 0 deletions cli/emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,26 @@ impl Emitter {
}
}

/// Expects a file URL, panics otherwise.
pub async fn load_and_emit_for_hmr(
&self,
specifier: &ModuleSpecifier,
) -> Result<String, AnyError> {
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<str> = source_code.into();
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)
}

/// 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.
Expand Down
Loading