mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
fix(cli): dispatch unload event on watch drop (#11696)
This commit is contained in:
parent
85a56e7144
commit
a3fd4bb998
2 changed files with 141 additions and 14 deletions
80
cli/main.rs
80
cli/main.rs
|
@ -892,29 +892,81 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<(), AnyError> {
|
|||
})
|
||||
};
|
||||
|
||||
/// The FileWatcherModuleExecutor provides module execution with safe dispatching of life-cycle events by tracking the
|
||||
/// state of any pending events and emitting accordingly on drop in the case of a future
|
||||
/// cancellation.
|
||||
struct FileWatcherModuleExecutor {
|
||||
worker: MainWorker,
|
||||
pending_unload: bool,
|
||||
}
|
||||
|
||||
impl FileWatcherModuleExecutor {
|
||||
pub fn new(worker: MainWorker) -> FileWatcherModuleExecutor {
|
||||
FileWatcherModuleExecutor {
|
||||
worker,
|
||||
pending_unload: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute the given main module emitting load and unload events before and after execution
|
||||
/// respectively.
|
||||
pub async fn execute(
|
||||
&mut self,
|
||||
main_module: &ModuleSpecifier,
|
||||
) -> Result<(), AnyError> {
|
||||
self.worker.execute_module(main_module).await?;
|
||||
self.worker.execute_script(
|
||||
&located_script_name!(),
|
||||
"window.dispatchEvent(new Event('load'))",
|
||||
)?;
|
||||
self.pending_unload = true;
|
||||
|
||||
let result = self.worker.run_event_loop(false).await;
|
||||
self.pending_unload = false;
|
||||
|
||||
if let Err(err) = result {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
self.worker.execute_script(
|
||||
&located_script_name!(),
|
||||
"window.dispatchEvent(new Event('unload'))",
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FileWatcherModuleExecutor {
|
||||
fn drop(&mut self) {
|
||||
if self.pending_unload {
|
||||
self
|
||||
.worker
|
||||
.execute_script(
|
||||
&located_script_name!(),
|
||||
"window.dispatchEvent(new Event('unload'))",
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let operation =
|
||||
|(program_state, main_module): (Arc<ProgramState>, ModuleSpecifier)| {
|
||||
let flags = flags.clone();
|
||||
let permissions = Permissions::from_options(&flags.into());
|
||||
async move {
|
||||
let main_module = main_module.clone();
|
||||
let mut worker = create_main_worker(
|
||||
// We make use an module executor guard to ensure that unload is always fired when an
|
||||
// operation is called.
|
||||
let mut executor = FileWatcherModuleExecutor::new(create_main_worker(
|
||||
&program_state,
|
||||
main_module.clone(),
|
||||
permissions,
|
||||
None,
|
||||
);
|
||||
debug!("main_module {}", main_module);
|
||||
worker.execute_module(&main_module).await?;
|
||||
worker.execute_script(
|
||||
&located_script_name!(),
|
||||
"window.dispatchEvent(new Event('load'))",
|
||||
)?;
|
||||
worker.run_event_loop(false).await?;
|
||||
worker.execute_script(
|
||||
&located_script_name!(),
|
||||
"window.dispatchEvent(new Event('unload'))",
|
||||
)?;
|
||||
));
|
||||
|
||||
executor.execute(&main_module).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
|
|
|
@ -322,6 +322,81 @@ fn run_watch() {
|
|||
drop(t);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_watch_load_unload_events() {
|
||||
let t = TempDir::new().expect("tempdir fail");
|
||||
let file_to_watch = t.path().join("file_to_watch.js");
|
||||
std::fs::write(
|
||||
&file_to_watch,
|
||||
r#"
|
||||
setInterval(() => {}, 0);
|
||||
window.addEventListener("load", () => {
|
||||
console.log("load");
|
||||
});
|
||||
|
||||
window.addEventListener("unload", () => {
|
||||
console.log("unload");
|
||||
});
|
||||
"#,
|
||||
)
|
||||
.expect("error writing file");
|
||||
|
||||
let mut child = util::deno_cmd()
|
||||
.current_dir(util::testdata_path())
|
||||
.arg("run")
|
||||
.arg("--watch")
|
||||
.arg("--unstable")
|
||||
.arg(&file_to_watch)
|
||||
.env("NO_COLOR", "1")
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.expect("failed to spawn script");
|
||||
|
||||
let stdout = child.stdout.as_mut().unwrap();
|
||||
let mut stdout_lines =
|
||||
std::io::BufReader::new(stdout).lines().map(|r| r.unwrap());
|
||||
let stderr = child.stderr.as_mut().unwrap();
|
||||
let mut stderr_lines =
|
||||
std::io::BufReader::new(stderr).lines().map(|r| r.unwrap());
|
||||
|
||||
// Wait for the first load event to fire
|
||||
assert!(stdout_lines.next().unwrap().contains("load"));
|
||||
|
||||
// Change content of the file, this time without an interval to keep it alive.
|
||||
std::fs::write(
|
||||
&file_to_watch,
|
||||
r#"
|
||||
window.addEventListener("load", () => {
|
||||
console.log("load");
|
||||
});
|
||||
|
||||
window.addEventListener("unload", () => {
|
||||
console.log("unload");
|
||||
});
|
||||
"#,
|
||||
)
|
||||
.expect("error writing file");
|
||||
|
||||
// Events from the file watcher is "debounced", so we need to wait for the next execution to start
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
|
||||
// Wait for the restart
|
||||
assert!(stderr_lines.next().unwrap().contains("Restarting"));
|
||||
|
||||
// Confirm that the unload event was dispatched from the first run
|
||||
assert!(stdout_lines.next().unwrap().contains("unload"));
|
||||
|
||||
// Followed by the load event of the second run
|
||||
assert!(stdout_lines.next().unwrap().contains("load"));
|
||||
|
||||
// Which is then unloaded as there is nothing keeping it alive.
|
||||
assert!(stdout_lines.next().unwrap().contains("unload"));
|
||||
|
||||
child.kill().unwrap();
|
||||
drop(t);
|
||||
}
|
||||
|
||||
/// Confirm that the watcher continues to work even if module resolution fails at the *first* attempt
|
||||
#[test]
|
||||
fn run_watch_not_exit() {
|
||||
|
|
Loading…
Reference in a new issue