diff --git a/Cargo.lock b/Cargo.lock index 6d794d7c3..3db2c77e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1892,6 +1892,7 @@ dependencies = [ "tokio", "tokio-util", "twox-hash", + "urlencoding", "winapi", ] @@ -2172,7 +2173,6 @@ dependencies = [ "tokio", "tracing", "url", - "urlencoding", ] [[package]] diff --git a/crates/base/src/runtime/mod.rs b/crates/base/src/runtime/mod.rs index e65320ee9..8567eb439 100644 --- a/crates/base/src/runtime/mod.rs +++ b/crates/base/src/runtime/mod.rs @@ -517,6 +517,11 @@ where let base_dir_path = std::env::current_dir().map(|p| p.join(&service_path))?; + let maybe_import_map_path = context + .get("importMapPath") + .and_then(|it| it.as_str()) + .map(str::to_string); + let eszip = if let Some(eszip_payload) = maybe_eszip { eszip_payload } else { @@ -585,7 +590,9 @@ where if let Some(module_url) = main_module_url.as_ref() { builder.set_entrypoint(Some(module_url.to_file_path().unwrap())); } - builder.set_no_npm(no_npm); + builder + .set_no_npm(no_npm) + .set_import_map_path(maybe_import_map_path.clone()); emitter_factory.set_deno_options(builder.build()?); @@ -634,10 +641,6 @@ where .get("sourceMap") .and_then(serde_json::Value::as_bool) .unwrap_or_default(); - let maybe_import_map_path = context - .get("importMapPath") - .and_then(|it| it.as_str()) - .map(str::to_string); let rt_provider = create_module_loader_for_standalone_from_eszip_kind( eszip, diff --git a/crates/base/test_cases/user-worker-with-import-map/helper.ts b/crates/base/test_cases/user-worker-with-import-map/helper.ts new file mode 100644 index 000000000..78d5d4fb3 --- /dev/null +++ b/crates/base/test_cases/user-worker-with-import-map/helper.ts @@ -0,0 +1,3 @@ +export function getHelperMessage(): string { + return "import map works!"; +} diff --git a/crates/base/test_cases/user-worker-with-import-map/import_map.json b/crates/base/test_cases/user-worker-with-import-map/import_map.json new file mode 100644 index 000000000..896da0b00 --- /dev/null +++ b/crates/base/test_cases/user-worker-with-import-map/import_map.json @@ -0,0 +1,5 @@ +{ + "imports": { + "helper-from-import-map": "./helper.ts" + } +} diff --git a/crates/base/test_cases/user-worker-with-import-map/index.ts b/crates/base/test_cases/user-worker-with-import-map/index.ts new file mode 100644 index 000000000..17c568362 --- /dev/null +++ b/crates/base/test_cases/user-worker-with-import-map/index.ts @@ -0,0 +1,40 @@ +import * as path from "jsr:@std/path"; + +Deno.serve(async (req: Request) => { + const basePath = "test_cases/user-worker-with-import-map"; + const url = new URL(req.url); + const { pathname } = url; + + const userWorkerPath = path.join(basePath, "user_worker"); + if (pathname === "/import_map") { + const worker = await EdgeRuntime.userWorkers.create({ + servicePath: userWorkerPath, + forceCreate: true, + context: { + importMapPath: path.join(basePath, "import_map.json"), + }, + }); + return worker.fetch(req); + } + if (pathname === "/inline_import_map") { + const inlineImportMap = { + imports: { + "helper-from-import-map": `./${path.join(basePath, "helper.ts")}`, + }, + }; + const importMapPath = `data:${ + encodeURIComponent(JSON.stringify(inlineImportMap)) + }`; + + const worker = await EdgeRuntime.userWorkers.create({ + servicePath: userWorkerPath, + forceCreate: true, + context: { + importMapPath: importMapPath, + }, + }); + return worker.fetch(req); + } + + return new Response("Not Found", { status: 404 }); +}); diff --git a/crates/base/test_cases/user-worker-with-import-map/user_worker/index.ts b/crates/base/test_cases/user-worker-with-import-map/user_worker/index.ts new file mode 100644 index 000000000..5dadf827a --- /dev/null +++ b/crates/base/test_cases/user-worker-with-import-map/user_worker/index.ts @@ -0,0 +1,16 @@ +import { getHelperMessage } from "helper-from-import-map"; + +Deno.serve((_req: Request) => { + return new Response( + JSON.stringify({ + message: getHelperMessage(), + success: true, + }), + { + headers: { + "Content-Type": "application/json", + Connection: "keep-alive", + }, + }, + ); +}); diff --git a/crates/base/tests/integration_tests.rs b/crates/base/tests/integration_tests.rs index 570a0a46f..0d3b989c1 100644 --- a/crates/base/tests/integration_tests.rs +++ b/crates/base/tests/integration_tests.rs @@ -25,13 +25,13 @@ use base::server::ServerEvent; use base::server::ServerFlags; use base::server::ServerHealth; use base::server::Tls; +use base::utils::test_utils; use base::utils::test_utils::create_test_user_worker; use base::utils::test_utils::ensure_npm_package_installed; use base::utils::test_utils::test_user_runtime_opts; use base::utils::test_utils::test_user_worker_pool_policy; use base::utils::test_utils::TestBed; use base::utils::test_utils::TestBedBuilder; -use base::utils::test_utils::{self}; use base::worker; use base::worker::TerminationToken; use base::WorkerKind; @@ -4176,6 +4176,54 @@ directory." } } +#[tokio::test] +#[serial] +async fn test_user_worker_with_import_map() { + let assert_fn = |resp: Result| async { + let res = resp.unwrap(); + let status = res.status().as_u16(); + + let body_bytes = res.bytes().await.unwrap(); + let body_str = String::from_utf8_lossy(&body_bytes); + + assert_eq!( + status, 200, + "Expected 200, got {} with body: {}", + status, body_str + ); + + assert!( + body_str.contains("import map works!"), + "Expected import map works!, got: {}", + body_str + ); + }; + { + integration_test!( + "./test_cases/user-worker-with-import-map", + NON_SECURE_PORT, + "import_map", + None, + None, + None, + (assert_fn), + TerminationToken::new() + ); + } + { + integration_test!( + "./test_cases/user-worker-with-import-map", + NON_SECURE_PORT, + "inline_import_map", + None, + None, + None, + (assert_fn), + TerminationToken::new() + ); + } +} + #[derive(Deserialize)] struct ErrorResponsePayload { msg: String, diff --git a/crates/deno_facade/Cargo.toml b/crates/deno_facade/Cargo.toml index abbf77931..d0435df38 100644 --- a/crates/deno_facade/Cargo.toml +++ b/crates/deno_facade/Cargo.toml @@ -39,7 +39,6 @@ thiserror.workspace = true tokio.workspace = true tracing.workspace = true url.workspace = true -urlencoding.workspace = true dashmap = "5.5.3" hashlink = "0.8" diff --git a/crates/deno_facade/lib.rs b/crates/deno_facade/lib.rs index b7e6a540e..b26056abf 100644 --- a/crates/deno_facade/lib.rs +++ b/crates/deno_facade/lib.rs @@ -15,7 +15,6 @@ mod eszip; pub mod errors; pub mod graph; -pub mod import_map; pub mod jsr; pub mod metadata; pub mod module_loader; diff --git a/deno/Cargo.toml b/deno/Cargo.toml index 3f0009e15..86a24a05f 100644 --- a/deno/Cargo.toml +++ b/deno/Cargo.toml @@ -77,6 +77,7 @@ thiserror.workspace = true tokio.workspace = true tokio-util.workspace = true twox-hash.workspace = true +urlencoding.workspace = true bincode = "=1.3.3" dissimilar = "=1.0.4" diff --git a/crates/deno_facade/import_map.rs b/deno/import_map.rs similarity index 56% rename from crates/deno_facade/import_map.rs rename to deno/import_map.rs index d7e0746de..f6427ce54 100644 --- a/crates/deno_facade/import_map.rs +++ b/deno/import_map.rs @@ -1,16 +1,15 @@ use anyhow::anyhow; use anyhow::Error; +use deno_config::workspace::SpecifiedImportMap; +use deno_core::serde_json; use deno_core::url::Url; -use import_map::parse_from_json; -use import_map::ImportMap; -use urlencoding::decode; - use std::fs; use std::path::Path; +use urlencoding::decode; pub fn load_import_map( - maybe_path: Option, -) -> Result, Error> { + maybe_path: Option<&str>, +) -> Result, Error> { if let Some(path_str) = maybe_path { let json_str; let base_url; @@ -19,12 +18,15 @@ pub fn load_import_map( // the data URI takes the following format // data:{encodeURIComponent(mport_map.json)?{encodeURIComponent(base_path)} if path_str.starts_with("data:") { - let data_uri = Url::parse(&path_str)?; + let data_uri = Url::parse(path_str)?; json_str = decode(data_uri.path())?.into_owned(); - base_url = Url::from_directory_path( - decode(data_uri.query().unwrap_or(""))?.into_owned(), - ) - .map_err(|_| anyhow!("invalid import map base url"))?; + if let Some(query) = data_uri.query() { + base_url = Url::from_directory_path(decode(query)?.into_owned()) + .map_err(|_| anyhow!("invalid import map base url"))?; + } else { + base_url = Url::from_directory_path(std::env::current_dir().unwrap()) + .map_err(|_| anyhow!("invalid import map base url"))?; + } } else { let path = Path::new(&path_str); let abs_path = std::env::current_dir().map(|p| p.join(path))?; @@ -33,8 +35,9 @@ pub fn load_import_map( .map_err(|_| anyhow!("invalid import map base url"))?; } - let result = parse_from_json(base_url, json_str.as_str())?; - Ok(Some(result.import_map)) + let value = serde_json::from_str(&json_str)?; + + Ok(Some(SpecifiedImportMap { base_url, value })) } else { Ok(None) } diff --git a/deno/lib.rs b/deno/lib.rs index d1ef3baee..58257e8d5 100644 --- a/deno/lib.rs +++ b/deno/lib.rs @@ -28,6 +28,7 @@ use deno_npm::npm_rc::ResolvedNpmRc; use deno_path_util::normalize_path; use dotenvy::from_filename; use file_fetcher::FileFetcher; +use import_map::load_import_map; pub mod args; pub mod auth_tokens; @@ -37,6 +38,7 @@ pub mod errors; pub mod file_fetcher; pub mod graph_util; pub mod http_util; +pub mod import_map; pub mod node; pub mod npm; pub mod npmrc; @@ -204,7 +206,9 @@ impl DenoOptions { Ok(self.workspace().create_resolver( CreateResolverOptions { pkg_json_dep_resolution, - specified_import_map: None, + specified_import_map: load_import_map( + self.builder.import_map_path.as_deref(), + )?, }, |path| Ok(std::fs::read_to_string(path)?), )?) @@ -373,6 +377,7 @@ pub struct DenoOptionsBuilder { env_file: Option>, frozen_lockfile: Option, force_global_cache: Option, + import_map_path: Option, } impl Default for DenoOptionsBuilder { @@ -397,6 +402,7 @@ impl DenoOptionsBuilder { env_file: None, frozen_lockfile: None, force_global_cache: None, + import_map_path: None, } } @@ -536,6 +542,16 @@ impl DenoOptionsBuilder { self } + pub fn import_map_path(mut self, value: String) -> Self { + self.import_map_path = Some(value); + self + } + + pub fn set_import_map_path(&mut self, value: Option) -> &mut Self { + self.import_map_path = value; + self + } + fn lockfile_skip_write(&self) -> bool { self.frozen_lockfile.is_none() }