From f6d6b24506410816833d802e1a8d9cd704f73289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 17 Feb 2021 14:32:57 +0100 Subject: [PATCH] feat: support loading import map from URL (#9519) This commit adds support for loading import maps from URLs, both remote and local. This feature is supported in CLI flag as well as in runtime compiler API. --- cli/import_map.rs | 33 ----------------- cli/lsp/sources.rs | 2 +- cli/main.rs | 26 +++++++------- cli/ops/runtime_compiler.rs | 13 ++++--- cli/program_state.rs | 38 +++++++------------- cli/tests/033_import_map_remote.out | 5 +++ cli/tests/import_maps/import_map_remote.json | 9 +++++ cli/tests/import_maps/test_remote.ts | 5 +++ cli/tests/integration_tests.rs | 7 ++++ 9 files changed, 60 insertions(+), 78 deletions(-) create mode 100644 cli/tests/033_import_map_remote.out create mode 100644 cli/tests/import_maps/import_map_remote.json create mode 100644 cli/tests/import_maps/test_remote.ts diff --git a/cli/import_map.rs b/cli/import_map.rs index 63e4e1ca1c..a71a4f405e 100644 --- a/cli/import_map.rs +++ b/cli/import_map.rs @@ -1,6 +1,5 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -use deno_core::error::AnyError; use deno_core::serde_json; use deno_core::serde_json::Map; use deno_core::serde_json::Value; @@ -10,8 +9,6 @@ use indexmap::IndexMap; use std::cmp::Ordering; use std::error::Error; use std::fmt; -use std::fs; -use std::io; #[derive(Debug)] pub struct ImportMapError { @@ -49,30 +46,6 @@ pub struct ImportMap { } impl ImportMap { - pub fn load(file_path: &str) -> Result { - let file_url = ModuleSpecifier::resolve_url_or_path(file_path)?.to_string(); - let resolved_path = std::env::current_dir().unwrap().join(file_path); - debug!( - "Attempt to load import map: {}", - resolved_path.to_str().unwrap() - ); - - // Load the contents of import map - let json_string = fs::read_to_string(&resolved_path).map_err(|err| { - io::Error::new( - io::ErrorKind::InvalidInput, - format!( - "Error retrieving import map file at \"{}\": {}", - resolved_path.to_str().unwrap(), - err.to_string() - ) - .as_str(), - ) - })?; - // The URL of the import map is the base URL for its values. - ImportMap::from_json(&file_url, &json_string).map_err(AnyError::from) - } - pub fn from_json( base_url: &str, json_string: &str, @@ -476,12 +449,6 @@ mod tests { use super::*; use deno_core::serde_json::json; - #[test] - fn load_nonexistent() { - let file_path = "nonexistent_import_map.json"; - assert!(ImportMap::load(file_path).is_err()); - } - #[test] fn from_json_1() { let base_url = "https://deno.land"; diff --git a/cli/lsp/sources.rs b/cli/lsp/sources.rs index 307170d722..2654918956 100644 --- a/cli/lsp/sources.rs +++ b/cli/lsp/sources.rs @@ -31,7 +31,7 @@ pub async fn cache( specifier: &ModuleSpecifier, maybe_import_map: &Option, ) -> Result<(), AnyError> { - let program_state = Arc::new(ProgramState::new(Default::default())?); + let program_state = Arc::new(ProgramState::build(Default::default()).await?); let handler = Arc::new(Mutex::new(FetchHandler::new( &program_state, Permissions::allow_all(), diff --git a/cli/main.rs b/cli/main.rs index 2addfa0d51..bae9fdb94e 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -313,7 +313,7 @@ async fn compile_command( tools::standalone::compile_to_runtime_flags(flags.clone(), args)?; let module_specifier = ModuleSpecifier::resolve_url_or_path(&source_file)?; - let program_state = ProgramState::new(flags.clone())?; + let program_state = ProgramState::build(flags.clone()).await?; let deno_dir = &program_state.dir; let output = output.or_else(|| { @@ -367,7 +367,7 @@ async fn info_command( if json && !flags.unstable { exit_unstable("--json"); } - let program_state = ProgramState::new(flags)?; + let program_state = ProgramState::build(flags).await?; if let Some(specifier) = maybe_specifier { let specifier = ModuleSpecifier::resolve_url_or_path(&specifier)?; let handler = Arc::new(Mutex::new(specifier_handler::FetchHandler::new( @@ -409,7 +409,7 @@ async fn install_command( preload_flags.inspect = None; preload_flags.inspect_brk = None; let permissions = Permissions::from_options(&preload_flags.clone().into()); - let program_state = ProgramState::new(preload_flags)?; + let program_state = ProgramState::build(preload_flags).await?; let main_module = ModuleSpecifier::resolve_url_or_path(&module_url)?; let mut worker = create_main_worker(&program_state, main_module.clone(), permissions); @@ -450,7 +450,7 @@ async fn cache_command( } else { module_graph::TypeLib::DenoWindow }; - let program_state = ProgramState::new(flags)?; + let program_state = ProgramState::build(flags).await?; for file in files { let specifier = ModuleSpecifier::resolve_url_or_path(&file)?; @@ -478,7 +478,7 @@ async fn eval_command( let main_module = ModuleSpecifier::resolve_url_or_path("./$deno$eval.ts").unwrap(); let permissions = Permissions::from_options(&flags.clone().into()); - let program_state = ProgramState::new(flags)?; + let program_state = ProgramState::build(flags).await?; let mut worker = create_main_worker(&program_state, main_module.clone(), permissions); let main_module_url = main_module.as_url().to_owned(); @@ -596,7 +596,7 @@ async fn bundle_command( ModuleSpecifier::resolve_url_or_path(&source_file1)?; debug!(">>>>> bundle START"); - let program_state = ProgramState::new(flags.clone())?; + let program_state = ProgramState::build(flags.clone()).await?; info!( "{} {}", @@ -742,7 +742,7 @@ async fn doc_command( maybe_filter: Option, private: bool, ) -> Result<(), AnyError> { - let program_state = ProgramState::new(flags.clone())?; + let program_state = ProgramState::build(flags.clone()).await?; let source_file = source_file.unwrap_or_else(|| "--builtin".to_string()); let loader = Box::new(DocLoader { @@ -822,7 +822,7 @@ async fn run_repl(flags: Flags) -> Result<(), AnyError> { let main_module = ModuleSpecifier::resolve_url_or_path("./$deno$repl.ts").unwrap(); let permissions = Permissions::from_options(&flags.clone().into()); - let program_state = ProgramState::new(flags)?; + let program_state = ProgramState::build(flags).await?; let mut worker = create_main_worker(&program_state, main_module.clone(), permissions); worker.run_event_loop().await?; @@ -831,7 +831,7 @@ async fn run_repl(flags: Flags) -> Result<(), AnyError> { } async fn run_from_stdin(flags: Flags) -> Result<(), AnyError> { - let program_state = ProgramState::new(flags.clone())?; + let program_state = ProgramState::build(flags.clone()).await?; let permissions = Permissions::from_options(&flags.clone().into()); let main_module = ModuleSpecifier::resolve_url_or_path("./$deno$stdin.ts").unwrap(); @@ -871,7 +871,7 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<(), AnyError> { let flags = flags.clone(); async move { let main_module = ModuleSpecifier::resolve_url_or_path(&script1)?; - let program_state = ProgramState::new(flags)?; + let program_state = ProgramState::build(flags).await?; let handler = Arc::new(Mutex::new(FetchHandler::new( &program_state, Permissions::allow_all(), @@ -916,7 +916,7 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<(), AnyError> { let permissions = Permissions::from_options(&flags.clone().into()); async move { let main_module = main_module.clone(); - let program_state = ProgramState::new(flags)?; + let program_state = ProgramState::build(flags).await?; let mut worker = create_main_worker(&program_state, main_module.clone(), permissions); debug!("main_module {}", main_module); @@ -948,7 +948,7 @@ async fn run_command(flags: Flags, script: String) -> Result<(), AnyError> { } let main_module = ModuleSpecifier::resolve_url_or_path(&script)?; - let program_state = ProgramState::new(flags.clone())?; + let program_state = ProgramState::build(flags.clone()).await?; let permissions = Permissions::from_options(&flags.clone().into()); let mut worker = create_main_worker(&program_state, main_module.clone(), permissions); @@ -989,7 +989,7 @@ async fn test_command( allow_none: bool, filter: Option, ) -> Result<(), AnyError> { - let program_state = ProgramState::new(flags.clone())?; + let program_state = ProgramState::build(flags.clone()).await?; let permissions = Permissions::from_options(&flags.clone().into()); let cwd = std::env::current_dir().expect("No current directory"); let include = include.unwrap_or_else(|| vec![".".to_string()]); diff --git a/cli/ops/runtime_compiler.rs b/cli/ops/runtime_compiler.rs index a3c0af4d1b..f54e7d2061 100644 --- a/cli/ops/runtime_compiler.rs +++ b/cli/ops/runtime_compiler.rs @@ -75,19 +75,22 @@ async fn op_emit( is_dynamic = true; Arc::new(Mutex::new(FetchHandler::new( &program_state, - runtime_permissions, + runtime_permissions.clone(), )?)) }; let maybe_import_map = if let Some(import_map_str) = args.import_map_path { let import_map_specifier = - ModuleSpecifier::resolve_url_or_path(&import_map_str).context( - format!("Bad file path (\"{}\") for import map.", import_map_str), - )?; + ModuleSpecifier::resolve_url_or_path(&import_map_str) + .context(format!("Bad URL (\"{}\") for import map.", import_map_str))?; let import_map_url = import_map_specifier.as_url(); let import_map = if let Some(value) = args.import_map { ImportMap::from_json(&import_map_url.to_string(), &value.to_string())? } else { - ImportMap::load(&import_map_str)? + let file = program_state + .file_fetcher + .fetch(&import_map_specifier, &runtime_permissions) + .await?; + ImportMap::from_json(import_map_specifier.as_str(), &file.source)? }; Some(import_map) } else if args.import_map.is_some() { diff --git a/cli/program_state.rs b/cli/program_state.rs index 684d33a8e6..7009813687 100644 --- a/cli/program_state.rs +++ b/cli/program_state.rs @@ -56,7 +56,7 @@ pub struct ProgramState { } impl ProgramState { - pub fn new(flags: flags::Flags) -> Result, AnyError> { + pub async fn build(flags: flags::Flags) -> Result, AnyError> { let custom_root = env::var("DENO_DIR").map(String::into).ok(); let dir = deno_dir::DenoDir::new(custom_root)?; let deps_cache_location = dir.root.join("deps"); @@ -94,11 +94,20 @@ impl ProgramState { let maybe_import_map: Option = match flags.import_map_path.as_ref() { None => None, - Some(file_path) => { + Some(import_map_url) => { if !flags.unstable { exit_unstable("--import-map") } - Some(ImportMap::load(file_path)?) + let import_map_specifier = + ModuleSpecifier::resolve_url_or_path(&import_map_url).context( + format!("Bad URL (\"{}\") for import map.", import_map_url), + )?; + let file = file_fetcher + .fetch(&import_map_specifier, &Permissions::allow_all()) + .await?; + let import_map = + ImportMap::from_json(import_map_specifier.as_str(), &file.source)?; + Some(import_map) } }; @@ -276,18 +285,6 @@ impl ProgramState { None } } - - #[cfg(test)] - pub fn mock( - argv: Vec, - maybe_flags: Option, - ) -> Arc { - ProgramState::new(flags::Flags { - argv, - ..maybe_flags.unwrap_or_default() - }) - .unwrap() - } } // TODO(@kitsonk) this is only temporary, but should be refactored to somewhere @@ -346,14 +343,3 @@ fn source_map_from_code(code: String) -> Option> { None } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn thread_safe() { - fn f(_: S) {} - f(ProgramState::mock(vec![], None)); - } -} diff --git a/cli/tests/033_import_map_remote.out b/cli/tests/033_import_map_remote.out new file mode 100644 index 0000000000..804fa0d57e --- /dev/null +++ b/cli/tests/033_import_map_remote.out @@ -0,0 +1,5 @@ +Hello from remapped moment! +Hello from remapped moment dir! +Hello from remapped lodash! +Hello from remapped lodash dir! +Hello from remapped Vue! diff --git a/cli/tests/import_maps/import_map_remote.json b/cli/tests/import_maps/import_map_remote.json new file mode 100644 index 0000000000..51f90f69c6 --- /dev/null +++ b/cli/tests/import_maps/import_map_remote.json @@ -0,0 +1,9 @@ +{ + "imports": { + "moment": "./moment/moment.ts", + "moment/": "./moment/", + "lodash": "./lodash/lodash.ts", + "lodash/": "./lodash/", + "https://www.unpkg.com/vue/dist/vue.runtime.esm.js": "./vue.ts" + } +} diff --git a/cli/tests/import_maps/test_remote.ts b/cli/tests/import_maps/test_remote.ts new file mode 100644 index 0000000000..206bdbd5ff --- /dev/null +++ b/cli/tests/import_maps/test_remote.ts @@ -0,0 +1,5 @@ +import "moment"; +import "moment/other_file.ts"; +import "lodash"; +import "lodash/other_file.ts"; +import "https://www.unpkg.com/vue/dist/vue.runtime.esm.js"; diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index a25a51b200..988372b17f 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -2475,6 +2475,13 @@ console.log("finish"); output: "033_import_map.out", }); + itest!(_033_import_map_remote { + args: + "run --quiet --reload --import-map=http://127.0.0.1:4545/cli/tests/import_maps/import_map_remote.json --unstable import_maps/test_remote.ts", + output: "033_import_map_remote.out", + http_server: true, + }); + itest!(_034_onload { args: "run --quiet --reload 034_onload/main.ts", output: "034_onload.out",