From f2448c5de2a2294abea304fe3e239988b2ee5fc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 6 Sep 2022 12:56:34 +0200 Subject: [PATCH] fix(npm): conditional exports in npm: specifiers (#15778) --- cli/node/mod.rs | 113 +++++++---------------------------------- ext/node/resolution.rs | 36 +++++++++++-- 2 files changed, 51 insertions(+), 98 deletions(-) diff --git a/cli/node/mod.rs b/cli/node/mod.rs index 6e8b4bf13a..2ad026427b 100644 --- a/cli/node/mod.rs +++ b/cli/node/mod.rs @@ -12,7 +12,6 @@ use deno_core::anyhow::Context; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::located_script_name; -use deno_core::serde_json::Map; use deno_core::serde_json::Value; use deno_core::url::Url; use deno_core::JsRuntime; @@ -345,7 +344,6 @@ pub fn node_resolve( npm_resolver: &dyn DenoDirNpmResolver, ) -> Result, AnyError> { // Note: if we are here, then the referrer is an esm module - // TODO(bartlomieju): skipped "policy" part as we don't plan to support it // NOTE(bartlomieju): this will force `ProcState` to use Node.js polyfill for @@ -823,17 +821,6 @@ pub fn translate_cjs_to_esm( Ok(translated_source) } -fn resolve_package_target_string( - target: &str, - subpath: Option, -) -> String { - if let Some(subpath) = subpath { - target.replace('*', &subpath) - } else { - target.to_string() - } -} - fn resolve( specifier: &str, referrer: &ModuleSpecifier, @@ -855,26 +842,30 @@ fn resolve( // We've got a bare specifier or maybe bare_specifier/blah.js" - let (_, package_subpath) = parse_specifier(specifier).unwrap(); + let (package_specifier, package_subpath) = + parse_specifier(specifier).unwrap(); // todo(dsherret): use not_found error on not found here - let module_dir = - npm_resolver.resolve_package_folder_from_path(&referrer_path)?; + let module_dir = npm_resolver.resolve_package_folder_from_package( + package_specifier.as_str(), + &referrer_path, + )?; let package_json_path = module_dir.join("package.json"); if package_json_path.exists() { - let package_json = PackageJson::load(npm_resolver, package_json_path)?; + let package_json = + PackageJson::load(npm_resolver, package_json_path.clone())?; - if let Some(map) = package_json.exports { - if let Some((key, subpath)) = exports_resolve(&map, &package_subpath) { - let value = map.get(&key).unwrap(); - let s = conditions_resolve(value, conditions); - - let t = resolve_package_target_string(&s, subpath); - return Ok(module_dir.join(t).clean()); - } else { - todo!() - } + if let Some(exports) = &package_json.exports { + return package_exports_resolve( + &package_json_path, + package_subpath, + exports, + referrer, + NodeModuleKind::Esm, + conditions, + npm_resolver, + ); } // old school @@ -896,25 +887,6 @@ fn resolve( Err(not_found(specifier, &referrer_path)) } -fn conditions_resolve(value: &Value, conditions: &[&str]) -> String { - match value { - Value::String(s) => s.to_string(), - Value::Object(map) => { - for condition in conditions { - if let Some(x) = map.get(&condition.to_string()) { - if let Value::String(s) = x { - return s.to_string(); - } else { - todo!() - } - } - } - todo!() - } - _ => todo!(), - } -} - fn parse_specifier(specifier: &str) -> Option<(String, String)> { let mut separator_index = specifier.find('/'); let mut valid_package_name = true; @@ -957,46 +929,6 @@ fn parse_specifier(specifier: &str) -> Option<(String, String)> { Some((package_name, package_subpath)) } -fn exports_resolve( - map: &Map, - subpath: &str, -) -> Option<(String, Option)> { - if map.contains_key(subpath) { - return Some((subpath.to_string(), None)); - } - - // best match - let mut best_match = None; - for key in map.keys() { - if let Some(pattern_index) = key.find('*') { - let key_sub = &key[0..pattern_index]; - if subpath.starts_with(key_sub) { - if subpath.ends_with('/') { - todo!() - } - let pattern_trailer = &key[pattern_index + 1..]; - - if subpath.len() > key.len() - && subpath.ends_with(pattern_trailer) - // && pattern_key_compare(best_match, key) == 1 - && key.rfind('*') == Some(pattern_index) - { - let rest = subpath - [pattern_index..(subpath.len() - pattern_trailer.len())] - .to_string(); - best_match = Some((key, rest)); - } - } - } - } - - if let Some((key, subpath_)) = best_match { - return Some((key.to_string(), Some(subpath_))); - } - - None -} - fn to_file_path(url: &ModuleSpecifier) -> PathBuf { url .to_file_path() @@ -1097,13 +1029,4 @@ mod tests { ] ) } - - #[test] - fn test_resolve_package_target_string() { - assert_eq!(resolve_package_target_string("foo", None), "foo"); - assert_eq!( - resolve_package_target_string("*foo", Some("bar".to_string())), - "barfoo" - ); - } } diff --git a/ext/node/resolution.rs b/ext/node/resolution.rs index 21ce589c25..d6734066cf 100644 --- a/ext/node/resolution.rs +++ b/ext/node/resolution.rs @@ -548,7 +548,9 @@ fn parse_package_name( } else if specifier.starts_with('@') { is_scoped = true; if let Some(index) = separator_index { - separator_index = specifier[index + 1..].find('/'); + separator_index = specifier[index + 1..] + .find('/') + .map(|new_index| index + 1 + new_index); } else { valid_package_name = false; } @@ -707,9 +709,9 @@ pub fn legacy_main_resolve( ) -> Result { let maybe_main = if referrer_kind == NodeModuleKind::Esm && package_json.typ == "module" { - &package_json.module + package_json.module.as_ref().or(package_json.main.as_ref()) } else { - &package_json.main + package_json.main.as_ref() }; let mut guess; @@ -791,3 +793,31 @@ pub fn legacy_main_resolve( Err(generic_error("not found")) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_package_name() { + let dummy_referrer = Url::parse("http://example.com").unwrap(); + + assert_eq!( + parse_package_name("fetch-blob", &dummy_referrer).unwrap(), + ("fetch-blob".to_string(), ".".to_string(), false) + ); + assert_eq!( + parse_package_name("@vue/plugin-vue", &dummy_referrer).unwrap(), + ("@vue/plugin-vue".to_string(), ".".to_string(), true) + ); + assert_eq!( + parse_package_name("@astrojs/prism/dist/highlighter", &dummy_referrer) + .unwrap(), + ( + "@astrojs/prism".to_string(), + "./dist/highlighter".to_string(), + true + ) + ); + } +}