From eff2a27bd02f8987e904907ae6ebb6cb9c07944b Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) \"Kassimo\" Qian" Date: Sat, 19 Oct 2019 14:19:19 -0700 Subject: [PATCH] feat: Allow "deno eval" to run code as module (#3148) --- cli/lib.rs | 51 +++++++++++---------- cli/ops/workers.rs | 2 +- cli/worker.rs | 25 ++++++---- core/modules.rs | 112 +++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 149 insertions(+), 41 deletions(-) diff --git a/cli/lib.rs b/cli/lib.rs index 3c093cda..5e416c6a 100644 --- a/cli/lib.rs +++ b/cli/lib.rs @@ -241,7 +241,7 @@ fn info_command(flags: DenoFlags, argv: Vec) { debug!("main_module {}", main_module); worker - .execute_mod_async(&main_module, true) + .execute_mod_async(&main_module, None, true) .map_err(print_err_and_exit) .and_then(move |()| print_file_info(worker, &main_module)) .and_then(|worker| { @@ -263,36 +263,41 @@ fn fetch_command(flags: DenoFlags, argv: Vec) { js_check(worker.execute("denoMain()")); debug!("main_module {}", main_module); - worker.execute_mod_async(&main_module, true).then(|result| { - js_check(result); - Ok(()) - }) + worker + .execute_mod_async(&main_module, None, true) + .then(|result| { + js_check(result); + Ok(()) + }) }); tokio_util::run(main_future); } fn eval_command(flags: DenoFlags, argv: Vec) { let (mut worker, state) = create_worker_and_state(flags, argv); - // Wrap provided script in async function so asynchronous methods - // work. This is required until top-level await is not supported. - let js_source = format!( - "async function _topLevelWrapper(){{ - {} - }} - _topLevelWrapper(); - ", - &state.argv[1] - ); + let ts_source = state.argv[1].clone(); + // Force TypeScript compile. + let main_module = + ModuleSpecifier::resolve_url_or_path("./__$deno$eval.ts").unwrap(); let main_future = lazy(move || { js_check(worker.execute("denoMain()")); - // ATM imports in `deno eval` are not allowed - // TODO Support ES modules once Worker supports evaluating anonymous modules. - js_check(worker.execute(&js_source)); - worker.then(|result| { - js_check(result); - Ok(()) - }) + debug!("main_module {}", &main_module); + + let mut worker_ = worker.clone(); + worker + .execute_mod_async(&main_module, Some(ts_source), false) + .and_then(move |()| { + js_check(worker.execute("window.dispatchEvent(new Event('load'))")); + worker.then(move |result| { + js_check(result); + js_check( + worker_.execute("window.dispatchEvent(new Event('unload'))"), + ); + Ok(()) + }) + }) + .map_err(print_err_and_exit) }); tokio_util::run(main_future); } @@ -356,7 +361,7 @@ fn run_script(flags: DenoFlags, argv: Vec) { let mut worker_ = worker.clone(); worker - .execute_mod_async(&main_module, false) + .execute_mod_async(&main_module, None, false) .and_then(move |()| { js_check(worker.execute("window.dispatchEvent(new Event('load'))")); worker.then(move |result| { diff --git a/cli/ops/workers.rs b/cli/ops/workers.rs index c8c4252c..670ca6b4 100644 --- a/cli/ops/workers.rs +++ b/cli/ops/workers.rs @@ -172,7 +172,7 @@ fn op_create_worker( } let op = worker - .execute_mod_async(&module_specifier, false) + .execute_mod_async(&module_specifier, None, false) .and_then(move |()| Ok(exec_cb(worker))); let result = op.wait()?; diff --git a/cli/worker.rs b/cli/worker.rs index 990dd613..1091164c 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -91,15 +91,20 @@ impl Worker { pub fn execute_mod_async( &mut self, module_specifier: &ModuleSpecifier, + maybe_code: Option, is_prefetch: bool, ) -> impl Future { let worker = self.clone(); let loader = self.state.clone(); let isolate = self.isolate.clone(); let modules = self.state.modules.clone(); - let recursive_load = - RecursiveLoad::main(&module_specifier.to_string(), loader, modules) - .get_future(isolate); + let recursive_load = RecursiveLoad::main( + &module_specifier.to_string(), + maybe_code, + loader, + modules, + ) + .get_future(isolate); recursive_load.and_then(move |id| -> Result<(), ErrBox> { worker.state.progress.done(); if is_prefetch { @@ -156,7 +161,7 @@ mod tests { let mut worker = Worker::new("TEST".to_string(), StartupData::None, state); worker - .execute_mod_async(&module_specifier, false) + .execute_mod_async(&module_specifier, None, false) .then(|result| { if let Err(err) = result { eprintln!("execute_mod err {:?}", err); @@ -193,7 +198,7 @@ mod tests { let mut worker = Worker::new("TEST".to_string(), StartupData::None, state); worker - .execute_mod_async(&module_specifier, false) + .execute_mod_async(&module_specifier, None, false) .then(|result| { if let Err(err) = result { eprintln!("execute_mod err {:?}", err); @@ -233,7 +238,7 @@ mod tests { ); worker.execute("denoMain()").unwrap(); worker - .execute_mod_async(&module_specifier, false) + .execute_mod_async(&module_specifier, None, false) .then(|result| { if let Err(err) = result { eprintln!("execute_mod err {:?}", err); @@ -354,7 +359,9 @@ mod tests { let mut worker = create_test_worker(); let module_specifier = ModuleSpecifier::resolve_url_or_path("does-not-exist").unwrap(); - let result = worker.execute_mod_async(&module_specifier, false).wait(); + let result = worker + .execute_mod_async(&module_specifier, None, false) + .wait(); assert!(result.is_err()); }) } @@ -372,7 +379,9 @@ mod tests { .to_owned(); let module_specifier = ModuleSpecifier::resolve_url_or_path(&p.to_string_lossy()).unwrap(); - let result = worker.execute_mod_async(&module_specifier, false).wait(); + let result = worker + .execute_mod_async(&module_specifier, None, false) + .wait(); assert!(result.is_ok()); }) } diff --git a/core/modules.rs b/core/modules.rs index 5956a731..6f71537a 100644 --- a/core/modules.rs +++ b/core/modules.rs @@ -58,8 +58,8 @@ enum Kind { #[derive(Debug, Eq, PartialEq)] enum State { - ResolveMain(String), // specifier - ResolveImport(String, String), // specifier, referrer + ResolveMain(String, Option), // specifier, maybe code + ResolveImport(String, String), // specifier, referrer LoadingRoot, LoadingImports(deno_mod), Instantiated(deno_mod), @@ -81,11 +81,12 @@ impl RecursiveLoad { /// Starts a new parallel load of the given URL of the main module. pub fn main( specifier: &str, + code: Option, loader: L, modules: Arc>, ) -> Self { let kind = Kind::Main; - let state = State::ResolveMain(specifier.to_owned()); + let state = State::ResolveMain(specifier.to_owned(), code); Self::new(kind, state, loader, modules) } @@ -126,7 +127,7 @@ impl RecursiveLoad { fn add_root(&mut self) -> Result<(), ErrBox> { let module_specifier = match self.state { - State::ResolveMain(ref specifier) => self.loader.resolve( + State::ResolveMain(ref specifier, _) => self.loader.resolve( specifier, ".", true, @@ -313,6 +314,21 @@ impl Stream for RecursiveLoad { fn poll(&mut self) -> Poll, Self::Error> { Ok(match self.state { + State::ResolveMain(ref specifier, Some(ref code)) => { + let module_specifier = self.loader.resolve( + specifier, + ".", + true, + self.dyn_import_id().is_some(), + )?; + let info = SourceCodeInfo { + code: code.to_owned(), + module_url_specified: module_specifier.to_string(), + module_url_found: module_specifier.to_string(), + }; + self.state = State::LoadingRoot; + Ready(Some(Event::Fetch(info))) + } State::ResolveMain(..) | State::ResolveImport(..) => { self.add_root()?; self.poll()? @@ -630,6 +646,8 @@ mod tests { } "/main.js" => Some((MAIN_SRC, "file:///main.js")), "/bad_import.js" => Some((BAD_IMPORT_SRC, "file:///bad_import.js")), + // deliberately empty code. + "/main_with_code.js" => Some(("", "file:///main_with_code.js")), _ => None, } } @@ -769,7 +787,8 @@ mod tests { let isolate = loader.isolate.clone(); let isolate_ = isolate.clone(); let loads = loader.loads.clone(); - let mut recursive_load = RecursiveLoad::main("/a.js", loader, modules); + let mut recursive_load = + RecursiveLoad::main("/a.js", None, loader, modules); let a_id = loop { match recursive_load.poll() { @@ -848,7 +867,7 @@ mod tests { let modules_ = modules.clone(); let loads = loader.loads.clone(); let recursive_load = - RecursiveLoad::main("/circular1.js", loader, modules); + RecursiveLoad::main("/circular1.js", None, loader, modules); let result = recursive_load.get_future(isolate.clone()).poll(); assert!(result.is_ok()); if let Async::Ready(circular1_id) = result.ok().unwrap() { @@ -919,7 +938,7 @@ mod tests { let modules_ = modules.clone(); let loads = loader.loads.clone(); let recursive_load = - RecursiveLoad::main("/redirect1.js", loader, modules); + RecursiveLoad::main("/redirect1.js", None, loader, modules); let result = recursive_load.get_future(isolate.clone()).poll(); println!(">> result {:?}", result); assert!(result.is_ok()); @@ -982,7 +1001,8 @@ mod tests { let modules = loader.modules.clone(); let loads = loader.loads.clone(); let mut recursive_load = - RecursiveLoad::main("/main.js", loader, modules).get_future(isolate); + RecursiveLoad::main("/main.js", None, loader, modules) + .get_future(isolate); let result = recursive_load.poll(); assert!(result.is_ok()); @@ -1030,7 +1050,7 @@ mod tests { let isolate = loader.isolate.clone(); let modules = loader.modules.clone(); let recursive_load = - RecursiveLoad::main("/bad_import.js", loader, modules); + RecursiveLoad::main("/bad_import.js", None, loader, modules); let result = recursive_load.get_future(isolate).poll(); assert!(result.is_err()); let err = result.err().unwrap(); @@ -1041,6 +1061,80 @@ mod tests { }) } + const MAIN_WITH_CODE_SRC: &str = r#" + import { b } from "/b.js"; + import { c } from "/c.js"; + if (b() != 'b') throw Error(); + if (c() != 'c') throw Error(); + if (!import.meta.main) throw Error(); + if (import.meta.url != 'file:///main_with_code.js') throw Error(); + "#; + + #[test] + fn recursive_load_main_with_code() { + run_in_task(|| { + let loader = MockLoader::new(); + let modules = loader.modules.clone(); + let modules_ = modules.clone(); + let isolate = loader.isolate.clone(); + let isolate_ = isolate.clone(); + let loads = loader.loads.clone(); + // In default resolution code should be empty. + // Instead we explicitly pass in our own code. + // The behavior should be very similar to /a.js. + let mut recursive_load = RecursiveLoad::main( + "/main_with_code.js", + Some(MAIN_WITH_CODE_SRC.to_owned()), + loader, + modules, + ); + + let main_id = loop { + match recursive_load.poll() { + Ok(Ready(Some(Event::Fetch(info)))) => { + let mut isolate = isolate.lock().unwrap(); + recursive_load.register(info, &mut isolate).unwrap(); + } + Ok(Ready(Some(Event::Instantiate(id)))) => break id, + _ => panic!("unexpected result"), + }; + }; + + let mut isolate = isolate_.lock().unwrap(); + js_check(isolate.mod_evaluate(main_id)); + + let l = loads.lock().unwrap(); + assert_eq!( + l.to_vec(), + vec!["file:///b.js", "file:///c.js", "file:///d.js"] + ); + + let modules = modules_.lock().unwrap(); + + assert_eq!(modules.get_id("file:///main_with_code.js"), Some(main_id)); + let b_id = modules.get_id("file:///b.js").unwrap(); + let c_id = modules.get_id("file:///c.js").unwrap(); + let d_id = modules.get_id("file:///d.js").unwrap(); + + assert_eq!( + modules.get_children(main_id), + Some(&vec![ + "file:///b.js".to_string(), + "file:///c.js".to_string() + ]) + ); + assert_eq!( + modules.get_children(b_id), + Some(&vec!["file:///c.js".to_string()]) + ); + assert_eq!( + modules.get_children(c_id), + Some(&vec!["file:///d.js".to_string()]) + ); + assert_eq!(modules.get_children(d_id), Some(&vec![])); + }) + } + #[test] fn empty_deps() { let modules = Modules::new(); -- GitLab