From 1a0cb5b5312941521ab021cfe9eaed498f35900b Mon Sep 17 00:00:00 2001 From: David Sherret Date: Mon, 14 Oct 2024 20:48:39 -0400 Subject: feat(unstable): `--unstable-detect-cjs` for respecting explicit `"type": "commonjs"` (#26149) When using the `--unstable-detect-cjs` flag or adding `"unstable": ["detect-cjs"]` to a deno.json, it will make a JS file CJS if the closest package.json contains `"type": "commonjs"` and the file is not an ESM module (no TLA, no `import.meta`, no `import`/`export`). --- cli/resolver.rs | 50 +++++++++++++++++++++----------------------------- 1 file changed, 21 insertions(+), 29 deletions(-) (limited to 'cli/resolver.rs') diff --git a/cli/resolver.rs b/cli/resolver.rs index 7804261b8..84c671268 100644 --- a/cli/resolver.rs +++ b/cli/resolver.rs @@ -43,7 +43,6 @@ use node_resolver::NodeModuleKind; use node_resolver::NodeResolution; use node_resolver::NodeResolutionMode; use node_resolver::PackageJson; -use std::borrow::Cow; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; @@ -53,7 +52,9 @@ use crate::args::DENO_DISABLE_PEDANTIC_NODE_WARNINGS; use crate::node::CliNodeCodeTranslator; use crate::npm::CliNpmResolver; use crate::npm::InnerCliNpmResolverRef; +use crate::util::path::specifier_has_extension; use crate::util::sync::AtomicFlag; +use crate::util::text_encoding::from_utf8_lossy_owned; pub struct ModuleCodeStringSource { pub code: ModuleSourceCode, @@ -215,7 +216,7 @@ impl CliNodeResolver { referrer: &ModuleSpecifier, mode: NodeResolutionMode, ) -> Result { - let referrer_kind = if self.cjs_resolutions.contains(referrer) { + let referrer_kind = if self.cjs_resolutions.is_known_cjs(referrer) { NodeModuleKind::Cjs } else { NodeModuleKind::Esm @@ -310,9 +311,7 @@ impl CliNodeResolver { if self.in_npm_package(&specifier) { let resolution = self.node_resolver.url_to_node_resolution(specifier)?; - if let NodeResolution::CommonJs(specifier) = &resolution { - self.cjs_resolutions.insert(specifier.clone()); - } + let resolution = self.handle_node_resolution(resolution); return Ok(Some(resolution.into_url())); } } @@ -333,12 +332,17 @@ impl CliNodeResolver { ) -> NodeResolution { if let NodeResolution::CommonJs(specifier) = &resolution { // remember that this was a common js resolution - self.cjs_resolutions.insert(specifier.clone()); + self.mark_cjs_resolution(specifier.clone()); } resolution } + + pub fn mark_cjs_resolution(&self, specifier: ModuleSpecifier) { + self.cjs_resolutions.insert(specifier); + } } +// todo(dsherret): move to module_loader.rs #[derive(Clone)] pub struct NpmModuleLoader { cjs_resolutions: Arc, @@ -362,18 +366,9 @@ impl NpmModuleLoader { } } - pub async fn load_if_in_npm_package( - &self, - specifier: &ModuleSpecifier, - maybe_referrer: Option<&ModuleSpecifier>, - ) -> Option> { - if self.node_resolver.in_npm_package(specifier) - || (specifier.scheme() == "file" && specifier.path().ends_with(".cjs")) - { - Some(self.load(specifier, maybe_referrer).await) - } else { - None - } + pub fn if_in_npm_package(&self, specifier: &ModuleSpecifier) -> bool { + self.node_resolver.in_npm_package(specifier) + || self.cjs_resolutions.is_known_cjs(specifier) } pub async fn load( @@ -418,16 +413,9 @@ impl NpmModuleLoader { } })?; - let code = if self.cjs_resolutions.contains(specifier) - || (specifier.scheme() == "file" && specifier.path().ends_with(".cjs")) - { + let code = if self.cjs_resolutions.is_known_cjs(specifier) { // translate cjs to esm if it's cjs and inject node globals - let code = match String::from_utf8_lossy(&code) { - Cow::Owned(code) => code, - // SAFETY: `String::from_utf8_lossy` guarantees that the result is valid - // UTF-8 if `Cow::Borrowed` is returned. - Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(code) }, - }; + let code = from_utf8_lossy_owned(code); ModuleSourceCode::String( self .node_code_translator @@ -452,8 +440,12 @@ impl NpmModuleLoader { pub struct CjsResolutionStore(DashSet); impl CjsResolutionStore { - pub fn contains(&self, specifier: &ModuleSpecifier) -> bool { - self.0.contains(specifier) + pub fn is_known_cjs(&self, specifier: &ModuleSpecifier) -> bool { + if specifier.scheme() != "file" { + return false; + } + + specifier_has_extension(specifier, "cjs") || self.0.contains(specifier) } pub fn insert(&self, specifier: ModuleSpecifier) { -- cgit v1.2.3 From 826e42a5b5880c974ae019a7a21aade6a718062c Mon Sep 17 00:00:00 2001 From: David Sherret Date: Fri, 1 Nov 2024 12:27:00 -0400 Subject: fix: improved support for cjs and cts modules (#26558) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * cts support * better cjs/cts type checking * deno compile cjs/cts support * More efficient detect cjs (going towards stabilization) * Determination of whether .js, .ts, .jsx, or .tsx is cjs or esm is only done after loading * Support `import x = require(...);` Co-authored-by: Bartek IwaƄczuk --- cli/resolver.rs | 348 +++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 234 insertions(+), 114 deletions(-) (limited to 'cli/resolver.rs') diff --git a/cli/resolver.rs b/cli/resolver.rs index 84c671268..710b97509 100644 --- a/cli/resolver.rs +++ b/cli/resolver.rs @@ -4,6 +4,7 @@ use async_trait::async_trait; use dashmap::DashMap; use dashmap::DashSet; use deno_ast::MediaType; +use deno_ast::ModuleKind; use deno_config::workspace::MappedResolution; use deno_config::workspace::MappedResolutionDiagnostic; use deno_config::workspace::MappedResolutionError; @@ -11,6 +12,7 @@ use deno_config::workspace::WorkspaceResolver; use deno_core::anyhow::anyhow; use deno_core::anyhow::Context; use deno_core::error::AnyError; +use deno_core::url::Url; use deno_core::ModuleSourceCode; use deno_core::ModuleSpecifier; use deno_graph::source::ResolutionMode; @@ -29,6 +31,7 @@ use deno_runtime::deno_fs; use deno_runtime::deno_fs::FileSystem; use deno_runtime::deno_node::is_builtin_node_module; use deno_runtime::deno_node::NodeResolver; +use deno_runtime::deno_node::PackageJsonResolver; use deno_semver::npm::NpmPackageReqReference; use deno_semver::package::PackageReq; use node_resolver::errors::ClosestPkgJsonError; @@ -38,21 +41,22 @@ use node_resolver::errors::PackageFolderResolveErrorKind; use node_resolver::errors::PackageFolderResolveIoError; use node_resolver::errors::PackageNotFoundError; use node_resolver::errors::PackageResolveErrorKind; -use node_resolver::errors::UrlToNodeResolutionError; +use node_resolver::errors::PackageSubpathResolveError; +use node_resolver::InNpmPackageChecker; use node_resolver::NodeModuleKind; use node_resolver::NodeResolution; use node_resolver::NodeResolutionMode; -use node_resolver::PackageJson; +use std::borrow::Cow; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; +use thiserror::Error; use crate::args::JsxImportSourceConfig; use crate::args::DENO_DISABLE_PEDANTIC_NODE_WARNINGS; use crate::node::CliNodeCodeTranslator; use crate::npm::CliNpmResolver; use crate::npm::InnerCliNpmResolverRef; -use crate::util::path::specifier_has_extension; use crate::util::sync::AtomicFlag; use crate::util::text_encoding::from_utf8_lossy_owned; @@ -104,36 +108,32 @@ impl deno_resolver::fs::DenoResolverFs for CliDenoResolverFs { #[derive(Debug)] pub struct CliNodeResolver { - cjs_resolutions: Arc, + cjs_tracker: Arc, fs: Arc, + in_npm_pkg_checker: Arc, node_resolver: Arc, npm_resolver: Arc, } impl CliNodeResolver { pub fn new( - cjs_resolutions: Arc, + cjs_tracker: Arc, fs: Arc, + in_npm_pkg_checker: Arc, node_resolver: Arc, npm_resolver: Arc, ) -> Self { Self { - cjs_resolutions, + cjs_tracker, fs, + in_npm_pkg_checker, node_resolver, npm_resolver, } } pub fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool { - self.npm_resolver.in_npm_package(specifier) - } - - pub fn get_closest_package_json( - &self, - referrer: &ModuleSpecifier, - ) -> Result>, ClosestPkgJsonError> { - self.node_resolver.get_closest_package_json(referrer) + self.in_npm_pkg_checker.in_npm_package(specifier) } pub fn resolve_if_for_npm_pkg( @@ -153,8 +153,7 @@ impl CliNodeResolver { | NodeResolveErrorKind::UnsupportedEsmUrlScheme(_) | NodeResolveErrorKind::DataUrlReferrer(_) | NodeResolveErrorKind::TypesNotFound(_) - | NodeResolveErrorKind::FinalizeResolution(_) - | NodeResolveErrorKind::UrlToNodeResolution(_) => Err(err.into()), + | NodeResolveErrorKind::FinalizeResolution(_) => Err(err.into()), NodeResolveErrorKind::PackageResolve(err) => { let err = err.into_kind(); match err { @@ -216,7 +215,11 @@ impl CliNodeResolver { referrer: &ModuleSpecifier, mode: NodeResolutionMode, ) -> Result { - let referrer_kind = if self.cjs_resolutions.is_known_cjs(referrer) { + let referrer_kind = if self + .cjs_tracker + .is_maybe_cjs(referrer, MediaType::from_specifier(referrer)) + .map_err(|err| NodeResolveErrorKind::PackageResolve(err.into()))? + { NodeModuleKind::Cjs } else { NodeModuleKind::Esm @@ -226,7 +229,7 @@ impl CliNodeResolver { self .node_resolver .resolve(specifier, referrer, referrer_kind, mode)?; - Ok(self.handle_node_resolution(res)) + Ok(res) } pub fn resolve_req_reference( @@ -234,7 +237,7 @@ impl CliNodeResolver { req_ref: &NpmPackageReqReference, referrer: &ModuleSpecifier, mode: NodeResolutionMode, - ) -> Result { + ) -> Result { self.resolve_req_with_sub_path( req_ref.req(), req_ref.sub_path(), @@ -249,7 +252,7 @@ impl CliNodeResolver { sub_path: Option<&str>, referrer: &ModuleSpecifier, mode: NodeResolutionMode, - ) -> Result { + ) -> Result { let package_folder = self .npm_resolver .resolve_pkg_folder_from_deno_module_req(req, referrer)?; @@ -260,7 +263,7 @@ impl CliNodeResolver { mode, ); match resolution_result { - Ok(resolution) => Ok(resolution), + Ok(url) => Ok(url), Err(err) => { if self.npm_resolver.as_byonm().is_some() { let package_json_path = package_folder.join("package.json"); @@ -271,7 +274,7 @@ impl CliNodeResolver { )); } } - Err(err) + Err(err.into()) } } } @@ -282,16 +285,13 @@ impl CliNodeResolver { sub_path: Option<&str>, maybe_referrer: Option<&ModuleSpecifier>, mode: NodeResolutionMode, - ) -> Result { - let res = self - .node_resolver - .resolve_package_subpath_from_deno_module( - package_folder, - sub_path, - maybe_referrer, - mode, - )?; - Ok(self.handle_node_resolution(res)) + ) -> Result { + self.node_resolver.resolve_package_subpath_from_deno_module( + package_folder, + sub_path, + maybe_referrer, + mode, + ) } pub fn handle_if_in_node_modules( @@ -306,71 +306,45 @@ impl CliNodeResolver { // so canoncalize then check if it's in the node_modules directory. // If so, check if we need to store this specifier as being a CJS // resolution. - let specifier = - crate::node::resolve_specifier_into_node_modules(specifier); - if self.in_npm_package(&specifier) { - let resolution = - self.node_resolver.url_to_node_resolution(specifier)?; - let resolution = self.handle_node_resolution(resolution); - return Ok(Some(resolution.into_url())); - } + let specifier = crate::node::resolve_specifier_into_node_modules( + specifier, + self.fs.as_ref(), + ); + return Ok(Some(specifier)); } Ok(None) } +} - pub fn url_to_node_resolution( - &self, - specifier: ModuleSpecifier, - ) -> Result { - self.node_resolver.url_to_node_resolution(specifier) - } - - fn handle_node_resolution( - &self, - resolution: NodeResolution, - ) -> NodeResolution { - if let NodeResolution::CommonJs(specifier) = &resolution { - // remember that this was a common js resolution - self.mark_cjs_resolution(specifier.clone()); - } - resolution - } - - pub fn mark_cjs_resolution(&self, specifier: ModuleSpecifier) { - self.cjs_resolutions.insert(specifier); - } +#[derive(Debug, Error)] +#[error("{media_type} files are not supported in npm packages: {specifier}")] +pub struct NotSupportedKindInNpmError { + pub media_type: MediaType, + pub specifier: Url, } -// todo(dsherret): move to module_loader.rs +// todo(dsherret): move to module_loader.rs (it seems to be here due to use in standalone) #[derive(Clone)] pub struct NpmModuleLoader { - cjs_resolutions: Arc, - node_code_translator: Arc, + cjs_tracker: Arc, fs: Arc, - node_resolver: Arc, + node_code_translator: Arc, } impl NpmModuleLoader { pub fn new( - cjs_resolutions: Arc, - node_code_translator: Arc, + cjs_tracker: Arc, fs: Arc, - node_resolver: Arc, + node_code_translator: Arc, ) -> Self { Self { - cjs_resolutions, + cjs_tracker, node_code_translator, fs, - node_resolver, } } - pub fn if_in_npm_package(&self, specifier: &ModuleSpecifier) -> bool { - self.node_resolver.in_npm_package(specifier) - || self.cjs_resolutions.is_known_cjs(specifier) - } - pub async fn load( &self, specifier: &ModuleSpecifier, @@ -413,20 +387,30 @@ impl NpmModuleLoader { } })?; - let code = if self.cjs_resolutions.is_known_cjs(specifier) { + let media_type = MediaType::from_specifier(specifier); + if media_type.is_emittable() { + return Err(AnyError::from(NotSupportedKindInNpmError { + media_type, + specifier: specifier.clone(), + })); + } + + let code = if self.cjs_tracker.is_maybe_cjs(specifier, media_type)? { // translate cjs to esm if it's cjs and inject node globals let code = from_utf8_lossy_owned(code); ModuleSourceCode::String( self .node_code_translator - .translate_cjs_to_esm(specifier, Some(code)) + .translate_cjs_to_esm(specifier, Some(Cow::Owned(code))) .await? + .into_owned() .into(), ) } else { // esm and json code is untouched ModuleSourceCode::Bytes(code.into_boxed_slice().into()) }; + Ok(ModuleCodeStringSource { code, found_url: specifier.clone(), @@ -435,21 +419,165 @@ impl NpmModuleLoader { } } +pub struct CjsTrackerOptions { + pub unstable_detect_cjs: bool, +} + /// Keeps track of what module specifiers were resolved as CJS. -#[derive(Debug, Default)] -pub struct CjsResolutionStore(DashSet); +/// +/// Modules that are `.js` or `.ts` are only known to be CJS or +/// ESM after they're loaded based on their contents. So these files +/// will be "maybe CJS" until they're loaded. +#[derive(Debug)] +pub struct CjsTracker { + in_npm_pkg_checker: Arc, + pkg_json_resolver: Arc, + unstable_detect_cjs: bool, + known: DashMap, +} + +impl CjsTracker { + pub fn new( + in_npm_pkg_checker: Arc, + pkg_json_resolver: Arc, + options: CjsTrackerOptions, + ) -> Self { + Self { + in_npm_pkg_checker, + pkg_json_resolver, + unstable_detect_cjs: options.unstable_detect_cjs, + known: Default::default(), + } + } -impl CjsResolutionStore { - pub fn is_known_cjs(&self, specifier: &ModuleSpecifier) -> bool { + /// Checks whether the file might be treated as CJS, but it's not for sure + /// yet because the source hasn't been loaded to see whether it contains + /// imports or exports. + pub fn is_maybe_cjs( + &self, + specifier: &ModuleSpecifier, + media_type: MediaType, + ) -> Result { + self.treat_as_cjs_with_is_script(specifier, media_type, None) + } + + /// Gets whether the file is CJS. If true, this is for sure + /// cjs because `is_script` is provided. + /// + /// `is_script` should be `true` when the contents of the file at the + /// provided specifier are known to be a script and not an ES module. + pub fn is_cjs_with_known_is_script( + &self, + specifier: &ModuleSpecifier, + media_type: MediaType, + is_script: bool, + ) -> Result { + self.treat_as_cjs_with_is_script(specifier, media_type, Some(is_script)) + } + + fn treat_as_cjs_with_is_script( + &self, + specifier: &ModuleSpecifier, + media_type: MediaType, + is_script: Option, + ) -> Result { + let kind = match self + .get_known_kind_with_is_script(specifier, media_type, is_script) + { + Some(kind) => kind, + None => self.check_based_on_pkg_json(specifier)?, + }; + Ok(kind.is_cjs()) + } + + pub fn get_known_kind( + &self, + specifier: &ModuleSpecifier, + media_type: MediaType, + ) -> Option { + self.get_known_kind_with_is_script(specifier, media_type, None) + } + + fn get_known_kind_with_is_script( + &self, + specifier: &ModuleSpecifier, + media_type: MediaType, + is_script: Option, + ) -> Option { if specifier.scheme() != "file" { - return false; + return Some(ModuleKind::Esm); } - specifier_has_extension(specifier, "cjs") || self.0.contains(specifier) + match media_type { + MediaType::Mts | MediaType::Mjs | MediaType::Dmts => Some(ModuleKind::Esm), + MediaType::Cjs | MediaType::Cts | MediaType::Dcts => Some(ModuleKind::Cjs), + MediaType::Dts => { + // dts files are always determined based on the package.json because + // they contain imports/exports even when considered CJS + if let Some(value) = self.known.get(specifier).map(|v| *v) { + Some(value) + } else { + let value = self.check_based_on_pkg_json(specifier).ok(); + if let Some(value) = value { + self.known.insert(specifier.clone(), value); + } + Some(value.unwrap_or(ModuleKind::Esm)) + } + } + MediaType::Wasm | + MediaType::Json => Some(ModuleKind::Esm), + MediaType::JavaScript + | MediaType::Jsx + | MediaType::TypeScript + | MediaType::Tsx + // treat these as unknown + | MediaType::Css + | MediaType::SourceMap + | MediaType::Unknown => { + if let Some(value) = self.known.get(specifier).map(|v| *v) { + if value.is_cjs() && is_script == Some(false) { + // we now know this is actually esm + self.known.insert(specifier.clone(), ModuleKind::Esm); + Some(ModuleKind::Esm) + } else { + Some(value) + } + } else if is_script == Some(false) { + // we know this is esm + self.known.insert(specifier.clone(), ModuleKind::Esm); + Some(ModuleKind::Esm) + } else { + None + } + } + } } - pub fn insert(&self, specifier: ModuleSpecifier) { - self.0.insert(specifier); + fn check_based_on_pkg_json( + &self, + specifier: &ModuleSpecifier, + ) -> Result { + if self.in_npm_pkg_checker.in_npm_package(specifier) { + if let Some(pkg_json) = + self.pkg_json_resolver.get_closest_package_json(specifier)? + { + let is_file_location_cjs = pkg_json.typ != "module"; + Ok(ModuleKind::from_is_cjs(is_file_location_cjs)) + } else { + Ok(ModuleKind::Cjs) + } + } else if self.unstable_detect_cjs { + if let Some(pkg_json) = + self.pkg_json_resolver.get_closest_package_json(specifier)? + { + let is_cjs_type = pkg_json.typ == "commonjs"; + Ok(ModuleKind::from_is_cjs(is_cjs_type)) + } else { + Ok(ModuleKind::Esm) + } + } else { + Ok(ModuleKind::Esm) + } } } @@ -633,8 +761,7 @@ impl Resolver for CliGraphResolver { Some(referrer), to_node_mode(mode), ) - .map_err(ResolveError::Other) - .map(|res| res.into_url()), + .map_err(|e| ResolveError::Other(e.into())), MappedResolution::PackageJson { dep_result, alias, @@ -665,19 +792,17 @@ impl Resolver for CliGraphResolver { ) .map_err(|e| ResolveError::Other(e.into())) .and_then(|pkg_folder| { - Ok( - self - .node_resolver - .as_ref() - .unwrap() - .resolve_package_sub_path_from_deno_module( - pkg_folder, - sub_path.as_deref(), - Some(referrer), - to_node_mode(mode), - )? - .into_url(), - ) + self + .node_resolver + .as_ref() + .unwrap() + .resolve_package_sub_path_from_deno_module( + pkg_folder, + sub_path.as_deref(), + Some(referrer), + to_node_mode(mode), + ) + .map_err(|e| ResolveError::Other(e.into())) }), }) } @@ -717,23 +842,20 @@ impl Resolver for CliGraphResolver { npm_req_ref.req(), ) { - return Ok( - node_resolver - .resolve_package_sub_path_from_deno_module( - pkg_folder, - npm_req_ref.sub_path(), - Some(referrer), - to_node_mode(mode), - )? - .into_url(), - ); + return node_resolver + .resolve_package_sub_path_from_deno_module( + pkg_folder, + npm_req_ref.sub_path(), + Some(referrer), + to_node_mode(mode), + ) + .map_err(|e| ResolveError::Other(e.into())); } // do npm resolution for byonm if is_byonm { return node_resolver .resolve_req_reference(&npm_req_ref, referrer, to_node_mode(mode)) - .map(|res| res.into_url()) .map_err(|err| err.into()); } } @@ -751,9 +873,7 @@ impl Resolver for CliGraphResolver { .map_err(ResolveError::Other)?; if let Some(res) = maybe_resolution { match res { - NodeResolution::Esm(url) | NodeResolution::CommonJs(url) => { - return Ok(url) - } + NodeResolution::Module(url) => return Ok(url), NodeResolution::BuiltIn(_) => { // don't resolve bare specifiers for built-in modules via node resolution } -- cgit v1.2.3 From f091d1ad69b4e5217ae3272b641171781a372c4f Mon Sep 17 00:00:00 2001 From: David Sherret Date: Wed, 13 Nov 2024 10:10:09 -0500 Subject: feat(node): stabilize detecting if CJS via `"type": "commonjs"` in a package.json (#26439) This will respect `"type": "commonjs"` in a package.json to determine if `.js`/`.jsx`/`.ts`/.tsx` files are CJS or ESM. If the file is found to be ESM it will be loaded as ESM though. --- cli/resolver.rs | 271 ++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 167 insertions(+), 104 deletions(-) (limited to 'cli/resolver.rs') diff --git a/cli/resolver.rs b/cli/resolver.rs index 710b97509..786e5d0db 100644 --- a/cli/resolver.rs +++ b/cli/resolver.rs @@ -4,7 +4,6 @@ use async_trait::async_trait; use dashmap::DashMap; use dashmap::DashSet; use deno_ast::MediaType; -use deno_ast::ModuleKind; use deno_config::workspace::MappedResolution; use deno_config::workspace::MappedResolutionDiagnostic; use deno_config::workspace::MappedResolutionError; @@ -17,9 +16,7 @@ use deno_core::ModuleSourceCode; use deno_core::ModuleSpecifier; use deno_graph::source::ResolutionMode; use deno_graph::source::ResolveError; -use deno_graph::source::Resolver; use deno_graph::source::UnknownBuiltInNodeModuleError; -use deno_graph::source::DEFAULT_JSX_IMPORT_SOURCE_MODULE; use deno_graph::NpmLoadError; use deno_graph::NpmResolvePkgReqsResult; use deno_npm::resolution::NpmResolutionError; @@ -52,7 +49,6 @@ use std::path::PathBuf; use std::sync::Arc; use thiserror::Error; -use crate::args::JsxImportSourceConfig; use crate::args::DENO_DISABLE_PEDANTIC_NODE_WARNINGS; use crate::node::CliNodeCodeTranslator; use crate::npm::CliNpmResolver; @@ -108,7 +104,6 @@ impl deno_resolver::fs::DenoResolverFs for CliDenoResolverFs { #[derive(Debug)] pub struct CliNodeResolver { - cjs_tracker: Arc, fs: Arc, in_npm_pkg_checker: Arc, node_resolver: Arc, @@ -117,14 +112,12 @@ pub struct CliNodeResolver { impl CliNodeResolver { pub fn new( - cjs_tracker: Arc, fs: Arc, in_npm_pkg_checker: Arc, node_resolver: Arc, npm_resolver: Arc, ) -> Self { Self { - cjs_tracker, fs, in_npm_pkg_checker, node_resolver, @@ -140,9 +133,11 @@ impl CliNodeResolver { &self, specifier: &str, referrer: &ModuleSpecifier, + referrer_kind: NodeModuleKind, mode: NodeResolutionMode, ) -> Result, AnyError> { - let resolution_result = self.resolve(specifier, referrer, mode); + let resolution_result = + self.resolve(specifier, referrer, referrer_kind, mode); match resolution_result { Ok(res) => Ok(Some(res)), Err(err) => { @@ -213,35 +208,26 @@ impl CliNodeResolver { &self, specifier: &str, referrer: &ModuleSpecifier, + referrer_kind: NodeModuleKind, mode: NodeResolutionMode, ) -> Result { - let referrer_kind = if self - .cjs_tracker - .is_maybe_cjs(referrer, MediaType::from_specifier(referrer)) - .map_err(|err| NodeResolveErrorKind::PackageResolve(err.into()))? - { - NodeModuleKind::Cjs - } else { - NodeModuleKind::Esm - }; - - let res = - self - .node_resolver - .resolve(specifier, referrer, referrer_kind, mode)?; - Ok(res) + self + .node_resolver + .resolve(specifier, referrer, referrer_kind, mode) } pub fn resolve_req_reference( &self, req_ref: &NpmPackageReqReference, referrer: &ModuleSpecifier, + referrer_kind: NodeModuleKind, mode: NodeResolutionMode, ) -> Result { self.resolve_req_with_sub_path( req_ref.req(), req_ref.sub_path(), referrer, + referrer_kind, mode, ) } @@ -251,6 +237,7 @@ impl CliNodeResolver { req: &PackageReq, sub_path: Option<&str>, referrer: &ModuleSpecifier, + referrer_kind: NodeModuleKind, mode: NodeResolutionMode, ) -> Result { let package_folder = self @@ -260,6 +247,7 @@ impl CliNodeResolver { &package_folder, sub_path, Some(referrer), + referrer_kind, mode, ); match resolution_result { @@ -284,12 +272,14 @@ impl CliNodeResolver { package_folder: &Path, sub_path: Option<&str>, maybe_referrer: Option<&ModuleSpecifier>, + referrer_kind: NodeModuleKind, mode: NodeResolutionMode, ) -> Result { self.node_resolver.resolve_package_subpath_from_deno_module( package_folder, sub_path, maybe_referrer, + referrer_kind, mode, ) } @@ -419,10 +409,6 @@ impl NpmModuleLoader { } } -pub struct CjsTrackerOptions { - pub unstable_detect_cjs: bool, -} - /// Keeps track of what module specifiers were resolved as CJS. /// /// Modules that are `.js` or `.ts` are only known to be CJS or @@ -430,22 +416,22 @@ pub struct CjsTrackerOptions { /// will be "maybe CJS" until they're loaded. #[derive(Debug)] pub struct CjsTracker { - in_npm_pkg_checker: Arc, - pkg_json_resolver: Arc, - unstable_detect_cjs: bool, - known: DashMap, + is_cjs_resolver: IsCjsResolver, + known: DashMap, } impl CjsTracker { pub fn new( in_npm_pkg_checker: Arc, pkg_json_resolver: Arc, - options: CjsTrackerOptions, + options: IsCjsResolverOptions, ) -> Self { Self { - in_npm_pkg_checker, - pkg_json_resolver, - unstable_detect_cjs: options.unstable_detect_cjs, + is_cjs_resolver: IsCjsResolver::new( + in_npm_pkg_checker, + pkg_json_resolver, + options, + ), known: Default::default(), } } @@ -485,47 +471,135 @@ impl CjsTracker { .get_known_kind_with_is_script(specifier, media_type, is_script) { Some(kind) => kind, - None => self.check_based_on_pkg_json(specifier)?, + None => self.is_cjs_resolver.check_based_on_pkg_json(specifier)?, }; - Ok(kind.is_cjs()) + Ok(kind == NodeModuleKind::Cjs) } pub fn get_known_kind( &self, specifier: &ModuleSpecifier, media_type: MediaType, - ) -> Option { + ) -> Option { self.get_known_kind_with_is_script(specifier, media_type, None) } + pub fn get_referrer_kind( + &self, + specifier: &ModuleSpecifier, + ) -> NodeModuleKind { + if specifier.scheme() != "file" { + return NodeModuleKind::Esm; + } + self + .get_known_kind(specifier, MediaType::from_specifier(specifier)) + .unwrap_or(NodeModuleKind::Esm) + } + + fn get_known_kind_with_is_script( + &self, + specifier: &ModuleSpecifier, + media_type: MediaType, + is_script: Option, + ) -> Option { + self.is_cjs_resolver.get_known_kind_with_is_script( + specifier, + media_type, + is_script, + &self.known, + ) + } +} + +#[derive(Debug)] +pub struct IsCjsResolverOptions { + pub detect_cjs: bool, + pub is_node_main: bool, +} + +#[derive(Debug)] +pub struct IsCjsResolver { + in_npm_pkg_checker: Arc, + pkg_json_resolver: Arc, + options: IsCjsResolverOptions, +} + +impl IsCjsResolver { + pub fn new( + in_npm_pkg_checker: Arc, + pkg_json_resolver: Arc, + options: IsCjsResolverOptions, + ) -> Self { + Self { + in_npm_pkg_checker, + pkg_json_resolver, + options, + } + } + + pub fn get_lsp_referrer_kind( + &self, + specifier: &ModuleSpecifier, + is_script: Option, + ) -> NodeModuleKind { + if specifier.scheme() != "file" { + return NodeModuleKind::Esm; + } + match MediaType::from_specifier(specifier) { + MediaType::Mts | MediaType::Mjs | MediaType::Dmts => NodeModuleKind::Esm, + MediaType::Cjs | MediaType::Cts | MediaType::Dcts => NodeModuleKind::Cjs, + MediaType::Dts => { + // dts files are always determined based on the package.json because + // they contain imports/exports even when considered CJS + self.check_based_on_pkg_json(specifier).unwrap_or(NodeModuleKind::Esm) + } + MediaType::Wasm | + MediaType::Json => NodeModuleKind::Esm, + MediaType::JavaScript + | MediaType::Jsx + | MediaType::TypeScript + | MediaType::Tsx + // treat these as unknown + | MediaType::Css + | MediaType::SourceMap + | MediaType::Unknown => { + match is_script { + Some(true) => self.check_based_on_pkg_json(specifier).unwrap_or(NodeModuleKind::Esm), + Some(false) | None => NodeModuleKind::Esm, + } + } + } + } + fn get_known_kind_with_is_script( &self, specifier: &ModuleSpecifier, media_type: MediaType, is_script: Option, - ) -> Option { + known_cache: &DashMap, + ) -> Option { if specifier.scheme() != "file" { - return Some(ModuleKind::Esm); + return Some(NodeModuleKind::Esm); } match media_type { - MediaType::Mts | MediaType::Mjs | MediaType::Dmts => Some(ModuleKind::Esm), - MediaType::Cjs | MediaType::Cts | MediaType::Dcts => Some(ModuleKind::Cjs), + MediaType::Mts | MediaType::Mjs | MediaType::Dmts => Some(NodeModuleKind::Esm), + MediaType::Cjs | MediaType::Cts | MediaType::Dcts => Some(NodeModuleKind::Cjs), MediaType::Dts => { // dts files are always determined based on the package.json because // they contain imports/exports even when considered CJS - if let Some(value) = self.known.get(specifier).map(|v| *v) { + if let Some(value) = known_cache.get(specifier).map(|v| *v) { Some(value) } else { let value = self.check_based_on_pkg_json(specifier).ok(); if let Some(value) = value { - self.known.insert(specifier.clone(), value); + known_cache.insert(specifier.clone(), value); } - Some(value.unwrap_or(ModuleKind::Esm)) + Some(value.unwrap_or(NodeModuleKind::Esm)) } } MediaType::Wasm | - MediaType::Json => Some(ModuleKind::Esm), + MediaType::Json => Some(NodeModuleKind::Esm), MediaType::JavaScript | MediaType::Jsx | MediaType::TypeScript @@ -534,18 +608,18 @@ impl CjsTracker { | MediaType::Css | MediaType::SourceMap | MediaType::Unknown => { - if let Some(value) = self.known.get(specifier).map(|v| *v) { - if value.is_cjs() && is_script == Some(false) { + if let Some(value) = known_cache.get(specifier).map(|v| *v) { + if value == NodeModuleKind::Cjs && is_script == Some(false) { // we now know this is actually esm - self.known.insert(specifier.clone(), ModuleKind::Esm); - Some(ModuleKind::Esm) + known_cache.insert(specifier.clone(), NodeModuleKind::Esm); + Some(NodeModuleKind::Esm) } else { Some(value) } } else if is_script == Some(false) { // we know this is esm - self.known.insert(specifier.clone(), ModuleKind::Esm); - Some(ModuleKind::Esm) + known_cache.insert(specifier.clone(), NodeModuleKind::Esm); + Some(NodeModuleKind::Esm) } else { None } @@ -556,27 +630,38 @@ impl CjsTracker { fn check_based_on_pkg_json( &self, specifier: &ModuleSpecifier, - ) -> Result { + ) -> Result { if self.in_npm_pkg_checker.in_npm_package(specifier) { if let Some(pkg_json) = self.pkg_json_resolver.get_closest_package_json(specifier)? { let is_file_location_cjs = pkg_json.typ != "module"; - Ok(ModuleKind::from_is_cjs(is_file_location_cjs)) + Ok(if is_file_location_cjs { + NodeModuleKind::Cjs + } else { + NodeModuleKind::Esm + }) } else { - Ok(ModuleKind::Cjs) + Ok(NodeModuleKind::Cjs) } - } else if self.unstable_detect_cjs { + } else if self.options.detect_cjs || self.options.is_node_main { if let Some(pkg_json) = self.pkg_json_resolver.get_closest_package_json(specifier)? { - let is_cjs_type = pkg_json.typ == "commonjs"; - Ok(ModuleKind::from_is_cjs(is_cjs_type)) + let is_cjs_type = pkg_json.typ == "commonjs" + || self.options.is_node_main && pkg_json.typ == "none"; + Ok(if is_cjs_type { + NodeModuleKind::Cjs + } else { + NodeModuleKind::Esm + }) + } else if self.options.is_node_main { + Ok(NodeModuleKind::Cjs) } else { - Ok(ModuleKind::Esm) + Ok(NodeModuleKind::Esm) } } else { - Ok(ModuleKind::Esm) + Ok(NodeModuleKind::Esm) } } } @@ -587,48 +672,33 @@ pub type CliSloppyImportsResolver = /// A resolver that takes care of resolution, taking into account loaded /// import map, JSX settings. #[derive(Debug)] -pub struct CliGraphResolver { +pub struct CliResolver { node_resolver: Option>, npm_resolver: Option>, sloppy_imports_resolver: Option>, workspace_resolver: Arc, - maybe_default_jsx_import_source: Option, - maybe_default_jsx_import_source_types: Option, - maybe_jsx_import_source_module: Option, maybe_vendor_specifier: Option, found_package_json_dep_flag: AtomicFlag, bare_node_builtins_enabled: bool, warned_pkgs: DashSet, } -pub struct CliGraphResolverOptions<'a> { +pub struct CliResolverOptions<'a> { pub node_resolver: Option>, pub npm_resolver: Option>, pub sloppy_imports_resolver: Option>, pub workspace_resolver: Arc, pub bare_node_builtins_enabled: bool, - pub maybe_jsx_import_source_config: Option, pub maybe_vendor_dir: Option<&'a PathBuf>, } -impl CliGraphResolver { - pub fn new(options: CliGraphResolverOptions) -> Self { +impl CliResolver { + pub fn new(options: CliResolverOptions) -> Self { Self { node_resolver: options.node_resolver, npm_resolver: options.npm_resolver, sloppy_imports_resolver: options.sloppy_imports_resolver, workspace_resolver: options.workspace_resolver, - maybe_default_jsx_import_source: options - .maybe_jsx_import_source_config - .as_ref() - .and_then(|c| c.default_specifier.clone()), - maybe_default_jsx_import_source_types: options - .maybe_jsx_import_source_config - .as_ref() - .and_then(|c| c.default_types_specifier.clone()), - maybe_jsx_import_source_module: options - .maybe_jsx_import_source_config - .map(|c| c.module), maybe_vendor_specifier: options .maybe_vendor_dir .and_then(|v| ModuleSpecifier::from_directory_path(v).ok()), @@ -638,10 +708,6 @@ impl CliGraphResolver { } } - pub fn as_graph_resolver(&self) -> &dyn Resolver { - self - } - pub fn create_graph_npm_resolver(&self) -> WorkerCliNpmGraphResolver { WorkerCliNpmGraphResolver { npm_resolver: self.npm_resolver.as_ref(), @@ -649,28 +715,12 @@ impl CliGraphResolver { bare_node_builtins_enabled: self.bare_node_builtins_enabled, } } -} - -impl Resolver for CliGraphResolver { - fn default_jsx_import_source(&self) -> Option { - self.maybe_default_jsx_import_source.clone() - } - - fn default_jsx_import_source_types(&self) -> Option { - self.maybe_default_jsx_import_source_types.clone() - } - fn jsx_import_source_module(&self) -> &str { - self - .maybe_jsx_import_source_module - .as_deref() - .unwrap_or(DEFAULT_JSX_IMPORT_SOURCE_MODULE) - } - - fn resolve( + pub fn resolve( &self, raw_specifier: &str, referrer_range: &deno_graph::Range, + referrer_kind: NodeModuleKind, mode: ResolutionMode, ) -> Result { fn to_node_mode(mode: ResolutionMode) -> NodeResolutionMode { @@ -686,7 +736,7 @@ impl Resolver for CliGraphResolver { if let Some(node_resolver) = self.node_resolver.as_ref() { if referrer.scheme() == "file" && node_resolver.in_npm_package(referrer) { return node_resolver - .resolve(raw_specifier, referrer, to_node_mode(mode)) + .resolve(raw_specifier, referrer, referrer_kind, to_node_mode(mode)) .map(|res| res.into_url()) .map_err(|e| ResolveError::Other(e.into())); } @@ -759,6 +809,7 @@ impl Resolver for CliGraphResolver { pkg_json.dir_path(), sub_path.as_deref(), Some(referrer), + referrer_kind, to_node_mode(mode), ) .map_err(|e| ResolveError::Other(e.into())), @@ -800,6 +851,7 @@ impl Resolver for CliGraphResolver { pkg_folder, sub_path.as_deref(), Some(referrer), + referrer_kind, to_node_mode(mode), ) .map_err(|e| ResolveError::Other(e.into())) @@ -847,6 +899,7 @@ impl Resolver for CliGraphResolver { pkg_folder, npm_req_ref.sub_path(), Some(referrer), + referrer_kind, to_node_mode(mode), ) .map_err(|e| ResolveError::Other(e.into())); @@ -855,7 +908,12 @@ impl Resolver for CliGraphResolver { // do npm resolution for byonm if is_byonm { return node_resolver - .resolve_req_reference(&npm_req_ref, referrer, to_node_mode(mode)) + .resolve_req_reference( + &npm_req_ref, + referrer, + referrer_kind, + to_node_mode(mode), + ) .map_err(|err| err.into()); } } @@ -869,7 +927,12 @@ impl Resolver for CliGraphResolver { // If byonm, check if the bare specifier resolves to an npm package if is_byonm && referrer.scheme() == "file" { let maybe_resolution = node_resolver - .resolve_if_for_npm_pkg(raw_specifier, referrer, to_node_mode(mode)) + .resolve_if_for_npm_pkg( + raw_specifier, + referrer, + referrer_kind, + to_node_mode(mode), + ) .map_err(ResolveError::Other)?; if let Some(res) = maybe_resolution { match res { -- cgit v1.2.3 From 617350e79c58b6e01984e3d7c7436d243d0e5cff Mon Sep 17 00:00:00 2001 From: David Sherret Date: Thu, 14 Nov 2024 15:24:25 -0500 Subject: refactor(resolver): move more resolution code into deno_resolver (#26873) Follow-up to cjs refactor. This moves most of the resolution code into the deno_resolver crate. Still pending is the npm resolution code. --- cli/resolver.rs | 769 +++++--------------------------------------------------- 1 file changed, 62 insertions(+), 707 deletions(-) (limited to 'cli/resolver.rs') diff --git a/cli/resolver.rs b/cli/resolver.rs index 786e5d0db..a2dd47430 100644 --- a/cli/resolver.rs +++ b/cli/resolver.rs @@ -4,10 +4,8 @@ use async_trait::async_trait; use dashmap::DashMap; use dashmap::DashSet; use deno_ast::MediaType; -use deno_config::workspace::MappedResolution; use deno_config::workspace::MappedResolutionDiagnostic; use deno_config::workspace::MappedResolutionError; -use deno_config::workspace::WorkspaceResolver; use deno_core::anyhow::anyhow; use deno_core::anyhow::Context; use deno_core::error::AnyError; @@ -20,28 +18,14 @@ use deno_graph::source::UnknownBuiltInNodeModuleError; use deno_graph::NpmLoadError; use deno_graph::NpmResolvePkgReqsResult; use deno_npm::resolution::NpmResolutionError; -use deno_package_json::PackageJsonDepValue; -use deno_resolver::sloppy_imports::SloppyImportsResolutionMode; use deno_resolver::sloppy_imports::SloppyImportsResolver; use deno_runtime::colors; use deno_runtime::deno_fs; use deno_runtime::deno_fs::FileSystem; use deno_runtime::deno_node::is_builtin_node_module; -use deno_runtime::deno_node::NodeResolver; -use deno_runtime::deno_node::PackageJsonResolver; -use deno_semver::npm::NpmPackageReqReference; +use deno_runtime::deno_node::DenoFsNodeResolverEnv; use deno_semver::package::PackageReq; -use node_resolver::errors::ClosestPkgJsonError; -use node_resolver::errors::NodeResolveError; -use node_resolver::errors::NodeResolveErrorKind; -use node_resolver::errors::PackageFolderResolveErrorKind; -use node_resolver::errors::PackageFolderResolveIoError; -use node_resolver::errors::PackageNotFoundError; -use node_resolver::errors::PackageResolveErrorKind; -use node_resolver::errors::PackageSubpathResolveError; -use node_resolver::InNpmPackageChecker; use node_resolver::NodeModuleKind; -use node_resolver::NodeResolution; use node_resolver::NodeResolutionMode; use std::borrow::Cow; use std::path::Path; @@ -56,6 +40,20 @@ use crate::npm::InnerCliNpmResolverRef; use crate::util::sync::AtomicFlag; use crate::util::text_encoding::from_utf8_lossy_owned; +pub type CjsTracker = deno_resolver::cjs::CjsTracker; +pub type IsCjsResolver = + deno_resolver::cjs::IsCjsResolver; +pub type IsCjsResolverOptions = deno_resolver::cjs::IsCjsResolverOptions; +pub type CliSloppyImportsResolver = + SloppyImportsResolver; +pub type CliDenoResolver = deno_resolver::DenoResolver< + CliDenoResolverFs, + DenoFsNodeResolverEnv, + SloppyImportsCachedFs, +>; +pub type CliNpmReqResolver = + deno_resolver::npm::NpmReqResolver; + pub struct ModuleCodeStringSource { pub code: ModuleSourceCode, pub found_url: ModuleSpecifier, @@ -77,6 +75,10 @@ impl deno_resolver::fs::DenoResolverFs for CliDenoResolverFs { self.0.realpath_sync(path).map_err(|e| e.into_io_error()) } + fn exists_sync(&self, path: &Path) -> bool { + self.0.exists_sync(path) + } + fn is_dir_sync(&self, path: &Path) -> bool { self.0.is_dir_sync(path) } @@ -102,211 +104,6 @@ impl deno_resolver::fs::DenoResolverFs for CliDenoResolverFs { } } -#[derive(Debug)] -pub struct CliNodeResolver { - fs: Arc, - in_npm_pkg_checker: Arc, - node_resolver: Arc, - npm_resolver: Arc, -} - -impl CliNodeResolver { - pub fn new( - fs: Arc, - in_npm_pkg_checker: Arc, - node_resolver: Arc, - npm_resolver: Arc, - ) -> Self { - Self { - fs, - in_npm_pkg_checker, - node_resolver, - npm_resolver, - } - } - - pub fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool { - self.in_npm_pkg_checker.in_npm_package(specifier) - } - - pub fn resolve_if_for_npm_pkg( - &self, - specifier: &str, - referrer: &ModuleSpecifier, - referrer_kind: NodeModuleKind, - mode: NodeResolutionMode, - ) -> Result, AnyError> { - let resolution_result = - self.resolve(specifier, referrer, referrer_kind, mode); - match resolution_result { - Ok(res) => Ok(Some(res)), - Err(err) => { - let err = err.into_kind(); - match err { - NodeResolveErrorKind::RelativeJoin(_) - | NodeResolveErrorKind::PackageImportsResolve(_) - | NodeResolveErrorKind::UnsupportedEsmUrlScheme(_) - | NodeResolveErrorKind::DataUrlReferrer(_) - | NodeResolveErrorKind::TypesNotFound(_) - | NodeResolveErrorKind::FinalizeResolution(_) => Err(err.into()), - NodeResolveErrorKind::PackageResolve(err) => { - let err = err.into_kind(); - match err { - PackageResolveErrorKind::ClosestPkgJson(_) - | PackageResolveErrorKind::InvalidModuleSpecifier(_) - | PackageResolveErrorKind::ExportsResolve(_) - | PackageResolveErrorKind::SubpathResolve(_) => Err(err.into()), - PackageResolveErrorKind::PackageFolderResolve(err) => { - match err.as_kind() { - PackageFolderResolveErrorKind::Io( - PackageFolderResolveIoError { package_name, .. }, - ) - | PackageFolderResolveErrorKind::PackageNotFound( - PackageNotFoundError { package_name, .. }, - ) => { - if self.in_npm_package(referrer) { - return Err(err.into()); - } - if let Some(byonm_npm_resolver) = - self.npm_resolver.as_byonm() - { - if byonm_npm_resolver - .find_ancestor_package_json_with_dep( - package_name, - referrer, - ) - .is_some() - { - return Err(anyhow!( - concat!( - "Could not resolve \"{}\", but found it in a package.json. ", - "Deno expects the node_modules/ directory to be up to date. ", - "Did you forget to run `deno install`?" - ), - specifier - )); - } - } - Ok(None) - } - PackageFolderResolveErrorKind::ReferrerNotFound(_) => { - if self.in_npm_package(referrer) { - return Err(err.into()); - } - Ok(None) - } - } - } - } - } - } - } - } - } - - pub fn resolve( - &self, - specifier: &str, - referrer: &ModuleSpecifier, - referrer_kind: NodeModuleKind, - mode: NodeResolutionMode, - ) -> Result { - self - .node_resolver - .resolve(specifier, referrer, referrer_kind, mode) - } - - pub fn resolve_req_reference( - &self, - req_ref: &NpmPackageReqReference, - referrer: &ModuleSpecifier, - referrer_kind: NodeModuleKind, - mode: NodeResolutionMode, - ) -> Result { - self.resolve_req_with_sub_path( - req_ref.req(), - req_ref.sub_path(), - referrer, - referrer_kind, - mode, - ) - } - - pub fn resolve_req_with_sub_path( - &self, - req: &PackageReq, - sub_path: Option<&str>, - referrer: &ModuleSpecifier, - referrer_kind: NodeModuleKind, - mode: NodeResolutionMode, - ) -> Result { - let package_folder = self - .npm_resolver - .resolve_pkg_folder_from_deno_module_req(req, referrer)?; - let resolution_result = self.resolve_package_sub_path_from_deno_module( - &package_folder, - sub_path, - Some(referrer), - referrer_kind, - mode, - ); - match resolution_result { - Ok(url) => Ok(url), - Err(err) => { - if self.npm_resolver.as_byonm().is_some() { - let package_json_path = package_folder.join("package.json"); - if !self.fs.exists_sync(&package_json_path) { - return Err(anyhow!( - "Could not find '{}'. Deno expects the node_modules/ directory to be up to date. Did you forget to run `deno install`?", - package_json_path.display(), - )); - } - } - Err(err.into()) - } - } - } - - pub fn resolve_package_sub_path_from_deno_module( - &self, - package_folder: &Path, - sub_path: Option<&str>, - maybe_referrer: Option<&ModuleSpecifier>, - referrer_kind: NodeModuleKind, - mode: NodeResolutionMode, - ) -> Result { - self.node_resolver.resolve_package_subpath_from_deno_module( - package_folder, - sub_path, - maybe_referrer, - referrer_kind, - mode, - ) - } - - pub fn handle_if_in_node_modules( - &self, - specifier: &ModuleSpecifier, - ) -> Result, AnyError> { - // skip canonicalizing if we definitely know it's unnecessary - if specifier.scheme() == "file" - && specifier.path().contains("/node_modules/") - { - // Specifiers in the node_modules directory are canonicalized - // so canoncalize then check if it's in the node_modules directory. - // If so, check if we need to store this specifier as being a CJS - // resolution. - let specifier = crate::node::resolve_specifier_into_node_modules( - specifier, - self.fs.as_ref(), - ); - return Ok(Some(specifier)); - } - - Ok(None) - } -} - #[derive(Debug, Error)] #[error("{media_type} files are not supported in npm packages: {specifier}")] pub struct NotSupportedKindInNpmError { @@ -409,305 +206,36 @@ impl NpmModuleLoader { } } -/// Keeps track of what module specifiers were resolved as CJS. -/// -/// Modules that are `.js` or `.ts` are only known to be CJS or -/// ESM after they're loaded based on their contents. So these files -/// will be "maybe CJS" until they're loaded. -#[derive(Debug)] -pub struct CjsTracker { - is_cjs_resolver: IsCjsResolver, - known: DashMap, -} - -impl CjsTracker { - pub fn new( - in_npm_pkg_checker: Arc, - pkg_json_resolver: Arc, - options: IsCjsResolverOptions, - ) -> Self { - Self { - is_cjs_resolver: IsCjsResolver::new( - in_npm_pkg_checker, - pkg_json_resolver, - options, - ), - known: Default::default(), - } - } - - /// Checks whether the file might be treated as CJS, but it's not for sure - /// yet because the source hasn't been loaded to see whether it contains - /// imports or exports. - pub fn is_maybe_cjs( - &self, - specifier: &ModuleSpecifier, - media_type: MediaType, - ) -> Result { - self.treat_as_cjs_with_is_script(specifier, media_type, None) - } - - /// Gets whether the file is CJS. If true, this is for sure - /// cjs because `is_script` is provided. - /// - /// `is_script` should be `true` when the contents of the file at the - /// provided specifier are known to be a script and not an ES module. - pub fn is_cjs_with_known_is_script( - &self, - specifier: &ModuleSpecifier, - media_type: MediaType, - is_script: bool, - ) -> Result { - self.treat_as_cjs_with_is_script(specifier, media_type, Some(is_script)) - } - - fn treat_as_cjs_with_is_script( - &self, - specifier: &ModuleSpecifier, - media_type: MediaType, - is_script: Option, - ) -> Result { - let kind = match self - .get_known_kind_with_is_script(specifier, media_type, is_script) - { - Some(kind) => kind, - None => self.is_cjs_resolver.check_based_on_pkg_json(specifier)?, - }; - Ok(kind == NodeModuleKind::Cjs) - } - - pub fn get_known_kind( - &self, - specifier: &ModuleSpecifier, - media_type: MediaType, - ) -> Option { - self.get_known_kind_with_is_script(specifier, media_type, None) - } - - pub fn get_referrer_kind( - &self, - specifier: &ModuleSpecifier, - ) -> NodeModuleKind { - if specifier.scheme() != "file" { - return NodeModuleKind::Esm; - } - self - .get_known_kind(specifier, MediaType::from_specifier(specifier)) - .unwrap_or(NodeModuleKind::Esm) - } - - fn get_known_kind_with_is_script( - &self, - specifier: &ModuleSpecifier, - media_type: MediaType, - is_script: Option, - ) -> Option { - self.is_cjs_resolver.get_known_kind_with_is_script( - specifier, - media_type, - is_script, - &self.known, - ) - } -} - -#[derive(Debug)] -pub struct IsCjsResolverOptions { - pub detect_cjs: bool, - pub is_node_main: bool, -} - -#[derive(Debug)] -pub struct IsCjsResolver { - in_npm_pkg_checker: Arc, - pkg_json_resolver: Arc, - options: IsCjsResolverOptions, -} - -impl IsCjsResolver { - pub fn new( - in_npm_pkg_checker: Arc, - pkg_json_resolver: Arc, - options: IsCjsResolverOptions, - ) -> Self { - Self { - in_npm_pkg_checker, - pkg_json_resolver, - options, - } - } - - pub fn get_lsp_referrer_kind( - &self, - specifier: &ModuleSpecifier, - is_script: Option, - ) -> NodeModuleKind { - if specifier.scheme() != "file" { - return NodeModuleKind::Esm; - } - match MediaType::from_specifier(specifier) { - MediaType::Mts | MediaType::Mjs | MediaType::Dmts => NodeModuleKind::Esm, - MediaType::Cjs | MediaType::Cts | MediaType::Dcts => NodeModuleKind::Cjs, - MediaType::Dts => { - // dts files are always determined based on the package.json because - // they contain imports/exports even when considered CJS - self.check_based_on_pkg_json(specifier).unwrap_or(NodeModuleKind::Esm) - } - MediaType::Wasm | - MediaType::Json => NodeModuleKind::Esm, - MediaType::JavaScript - | MediaType::Jsx - | MediaType::TypeScript - | MediaType::Tsx - // treat these as unknown - | MediaType::Css - | MediaType::SourceMap - | MediaType::Unknown => { - match is_script { - Some(true) => self.check_based_on_pkg_json(specifier).unwrap_or(NodeModuleKind::Esm), - Some(false) | None => NodeModuleKind::Esm, - } - } - } - } - - fn get_known_kind_with_is_script( - &self, - specifier: &ModuleSpecifier, - media_type: MediaType, - is_script: Option, - known_cache: &DashMap, - ) -> Option { - if specifier.scheme() != "file" { - return Some(NodeModuleKind::Esm); - } - - match media_type { - MediaType::Mts | MediaType::Mjs | MediaType::Dmts => Some(NodeModuleKind::Esm), - MediaType::Cjs | MediaType::Cts | MediaType::Dcts => Some(NodeModuleKind::Cjs), - MediaType::Dts => { - // dts files are always determined based on the package.json because - // they contain imports/exports even when considered CJS - if let Some(value) = known_cache.get(specifier).map(|v| *v) { - Some(value) - } else { - let value = self.check_based_on_pkg_json(specifier).ok(); - if let Some(value) = value { - known_cache.insert(specifier.clone(), value); - } - Some(value.unwrap_or(NodeModuleKind::Esm)) - } - } - MediaType::Wasm | - MediaType::Json => Some(NodeModuleKind::Esm), - MediaType::JavaScript - | MediaType::Jsx - | MediaType::TypeScript - | MediaType::Tsx - // treat these as unknown - | MediaType::Css - | MediaType::SourceMap - | MediaType::Unknown => { - if let Some(value) = known_cache.get(specifier).map(|v| *v) { - if value == NodeModuleKind::Cjs && is_script == Some(false) { - // we now know this is actually esm - known_cache.insert(specifier.clone(), NodeModuleKind::Esm); - Some(NodeModuleKind::Esm) - } else { - Some(value) - } - } else if is_script == Some(false) { - // we know this is esm - known_cache.insert(specifier.clone(), NodeModuleKind::Esm); - Some(NodeModuleKind::Esm) - } else { - None - } - } - } - } - - fn check_based_on_pkg_json( - &self, - specifier: &ModuleSpecifier, - ) -> Result { - if self.in_npm_pkg_checker.in_npm_package(specifier) { - if let Some(pkg_json) = - self.pkg_json_resolver.get_closest_package_json(specifier)? - { - let is_file_location_cjs = pkg_json.typ != "module"; - Ok(if is_file_location_cjs { - NodeModuleKind::Cjs - } else { - NodeModuleKind::Esm - }) - } else { - Ok(NodeModuleKind::Cjs) - } - } else if self.options.detect_cjs || self.options.is_node_main { - if let Some(pkg_json) = - self.pkg_json_resolver.get_closest_package_json(specifier)? - { - let is_cjs_type = pkg_json.typ == "commonjs" - || self.options.is_node_main && pkg_json.typ == "none"; - Ok(if is_cjs_type { - NodeModuleKind::Cjs - } else { - NodeModuleKind::Esm - }) - } else if self.options.is_node_main { - Ok(NodeModuleKind::Cjs) - } else { - Ok(NodeModuleKind::Esm) - } - } else { - Ok(NodeModuleKind::Esm) - } - } +pub struct CliResolverOptions { + pub deno_resolver: Arc, + pub npm_resolver: Option>, + pub bare_node_builtins_enabled: bool, } -pub type CliSloppyImportsResolver = - SloppyImportsResolver; - /// A resolver that takes care of resolution, taking into account loaded /// import map, JSX settings. #[derive(Debug)] pub struct CliResolver { - node_resolver: Option>, + deno_resolver: Arc, npm_resolver: Option>, - sloppy_imports_resolver: Option>, - workspace_resolver: Arc, - maybe_vendor_specifier: Option, found_package_json_dep_flag: AtomicFlag, bare_node_builtins_enabled: bool, warned_pkgs: DashSet, } -pub struct CliResolverOptions<'a> { - pub node_resolver: Option>, - pub npm_resolver: Option>, - pub sloppy_imports_resolver: Option>, - pub workspace_resolver: Arc, - pub bare_node_builtins_enabled: bool, - pub maybe_vendor_dir: Option<&'a PathBuf>, -} - impl CliResolver { pub fn new(options: CliResolverOptions) -> Self { Self { - node_resolver: options.node_resolver, + deno_resolver: options.deno_resolver, npm_resolver: options.npm_resolver, - sloppy_imports_resolver: options.sloppy_imports_resolver, - workspace_resolver: options.workspace_resolver, - maybe_vendor_specifier: options - .maybe_vendor_dir - .and_then(|v| ModuleSpecifier::from_directory_path(v).ok()), found_package_json_dep_flag: Default::default(), bare_node_builtins_enabled: options.bare_node_builtins_enabled, warned_pkgs: Default::default(), } } + // todo(dsherret): move this off CliResolver as CliResolver is acting + // like a factory by doing this (it's beyond its responsibility) pub fn create_graph_npm_resolver(&self) -> WorkerCliNpmGraphResolver { WorkerCliNpmGraphResolver { npm_resolver: self.npm_resolver.as_ref(), @@ -730,223 +258,50 @@ impl CliResolver { } } - let referrer = &referrer_range.specifier; + let resolution = self + .deno_resolver + .resolve( + raw_specifier, + &referrer_range.specifier, + referrer_kind, + to_node_mode(mode), + ) + .map_err(|err| match err.into_kind() { + deno_resolver::DenoResolveErrorKind::MappedResolution( + mapped_resolution_error, + ) => match mapped_resolution_error { + MappedResolutionError::Specifier(e) => ResolveError::Specifier(e), + // deno_graph checks specifically for an ImportMapError + MappedResolutionError::ImportMap(e) => ResolveError::Other(e.into()), + err => ResolveError::Other(err.into()), + }, + err => ResolveError::Other(err.into()), + })?; - // Use node resolution if we're in an npm package - if let Some(node_resolver) = self.node_resolver.as_ref() { - if referrer.scheme() == "file" && node_resolver.in_npm_package(referrer) { - return node_resolver - .resolve(raw_specifier, referrer, referrer_kind, to_node_mode(mode)) - .map(|res| res.into_url()) - .map_err(|e| ResolveError::Other(e.into())); - } + if resolution.found_package_json_dep { + // mark that we need to do an "npm install" later + self.found_package_json_dep_flag.raise(); } - // Attempt to resolve with the workspace resolver - let result: Result<_, ResolveError> = self - .workspace_resolver - .resolve(raw_specifier, referrer) - .map_err(|err| match err { - MappedResolutionError::Specifier(err) => ResolveError::Specifier(err), - MappedResolutionError::ImportMap(err) => { - ResolveError::Other(err.into()) - } - MappedResolutionError::Workspace(err) => { - ResolveError::Other(err.into()) - } - }); - let result = match result { - Ok(resolution) => match resolution { - MappedResolution::Normal { - specifier, - maybe_diagnostic, - } - | MappedResolution::ImportMap { - specifier, - maybe_diagnostic, - } => { - if let Some(diagnostic) = maybe_diagnostic { - match &*diagnostic { - MappedResolutionDiagnostic::ConstraintNotMatchedLocalVersion { reference, .. } => { - if self.warned_pkgs.insert(reference.req().clone()) { - log::warn!("{} {}\n at {}", colors::yellow("Warning"), diagnostic, referrer_range); - } - } - } - } - // do sloppy imports resolution if enabled - if let Some(sloppy_imports_resolver) = &self.sloppy_imports_resolver { - Ok( - sloppy_imports_resolver - .resolve( - &specifier, - match mode { - ResolutionMode::Execution => { - SloppyImportsResolutionMode::Execution - } - ResolutionMode::Types => SloppyImportsResolutionMode::Types, - }, - ) - .map(|s| s.into_specifier()) - .unwrap_or(specifier), - ) - } else { - Ok(specifier) - } - } - MappedResolution::WorkspaceJsrPackage { specifier, .. } => { - Ok(specifier) - } - MappedResolution::WorkspaceNpmPackage { - target_pkg_json: pkg_json, - sub_path, - .. - } => self - .node_resolver - .as_ref() - .unwrap() - .resolve_package_sub_path_from_deno_module( - pkg_json.dir_path(), - sub_path.as_deref(), - Some(referrer), - referrer_kind, - to_node_mode(mode), - ) - .map_err(|e| ResolveError::Other(e.into())), - MappedResolution::PackageJson { - dep_result, - alias, - sub_path, + if let Some(diagnostic) = resolution.maybe_diagnostic { + match &*diagnostic { + MappedResolutionDiagnostic::ConstraintNotMatchedLocalVersion { + reference, .. } => { - // found a specifier in the package.json, so mark that - // we need to do an "npm install" later - self.found_package_json_dep_flag.raise(); - - dep_result - .as_ref() - .map_err(|e| ResolveError::Other(e.clone().into())) - .and_then(|dep| match dep { - PackageJsonDepValue::Req(req) => { - ModuleSpecifier::parse(&format!( - "npm:{}{}", - req, - sub_path.map(|s| format!("/{}", s)).unwrap_or_default() - )) - .map_err(|e| ResolveError::Other(e.into())) - } - PackageJsonDepValue::Workspace(version_req) => self - .workspace_resolver - .resolve_workspace_pkg_json_folder_for_pkg_json_dep( - alias, - version_req, - ) - .map_err(|e| ResolveError::Other(e.into())) - .and_then(|pkg_folder| { - self - .node_resolver - .as_ref() - .unwrap() - .resolve_package_sub_path_from_deno_module( - pkg_folder, - sub_path.as_deref(), - Some(referrer), - referrer_kind, - to_node_mode(mode), - ) - .map_err(|e| ResolveError::Other(e.into())) - }), - }) - } - }, - Err(err) => Err(err), - }; - - // When the user is vendoring, don't allow them to import directly from the vendor/ directory - // as it might cause them confusion or duplicate dependencies. Additionally, this folder has - // special treatment in the language server so it will definitely cause issues/confusion there - // if they do this. - if let Some(vendor_specifier) = &self.maybe_vendor_specifier { - if let Ok(specifier) = &result { - if specifier.as_str().starts_with(vendor_specifier.as_str()) { - return Err(ResolveError::Other(anyhow!("Importing from the vendor directory is not permitted. Use a remote specifier instead or disable vendoring."))); - } - } - } - - let Some(node_resolver) = &self.node_resolver else { - return result; - }; - - let is_byonm = self - .npm_resolver - .as_ref() - .is_some_and(|r| r.as_byonm().is_some()); - match result { - Ok(specifier) => { - if let Ok(npm_req_ref) = - NpmPackageReqReference::from_specifier(&specifier) - { - // check if the npm specifier resolves to a workspace member - if let Some(pkg_folder) = self - .workspace_resolver - .resolve_workspace_pkg_json_folder_for_npm_specifier( - npm_req_ref.req(), - ) - { - return node_resolver - .resolve_package_sub_path_from_deno_module( - pkg_folder, - npm_req_ref.sub_path(), - Some(referrer), - referrer_kind, - to_node_mode(mode), - ) - .map_err(|e| ResolveError::Other(e.into())); - } - - // do npm resolution for byonm - if is_byonm { - return node_resolver - .resolve_req_reference( - &npm_req_ref, - referrer, - referrer_kind, - to_node_mode(mode), - ) - .map_err(|err| err.into()); + if self.warned_pkgs.insert(reference.req().clone()) { + log::warn!( + "{} {}\n at {}", + colors::yellow("Warning"), + diagnostic, + referrer_range + ); } } - - Ok(match node_resolver.handle_if_in_node_modules(&specifier)? { - Some(specifier) => specifier, - None => specifier, - }) - } - Err(err) => { - // If byonm, check if the bare specifier resolves to an npm package - if is_byonm && referrer.scheme() == "file" { - let maybe_resolution = node_resolver - .resolve_if_for_npm_pkg( - raw_specifier, - referrer, - referrer_kind, - to_node_mode(mode), - ) - .map_err(ResolveError::Other)?; - if let Some(res) = maybe_resolution { - match res { - NodeResolution::Module(url) => return Ok(url), - NodeResolution::BuiltIn(_) => { - // don't resolve bare specifiers for built-in modules via node resolution - } - } - } - } - - Err(err) } } + + Ok(resolution.url) } } -- cgit v1.2.3