diff options
Diffstat (limited to 'resolvers/deno/cjs.rs')
-rw-r--r-- | resolvers/deno/cjs.rs | 272 |
1 files changed, 272 insertions, 0 deletions
diff --git a/resolvers/deno/cjs.rs b/resolvers/deno/cjs.rs new file mode 100644 index 000000000..dbcbd8b6b --- /dev/null +++ b/resolvers/deno/cjs.rs @@ -0,0 +1,272 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use std::sync::Arc; + +use dashmap::DashMap; +use deno_media_type::MediaType; +use node_resolver::env::NodeResolverEnv; +use node_resolver::errors::ClosestPkgJsonError; +use node_resolver::InNpmPackageChecker; +use node_resolver::NodeModuleKind; +use node_resolver::PackageJsonResolver; +use url::Url; + +/// Keeps track of what module specifiers were resolved as CJS. +/// +/// Modules that are `.js`, `.ts`, `.jsx`, and `tsx` 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<TEnv: NodeResolverEnv> { + is_cjs_resolver: IsCjsResolver<TEnv>, + known: DashMap<Url, NodeModuleKind>, +} + +impl<TEnv: NodeResolverEnv> CjsTracker<TEnv> { + pub fn new( + in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>, + pkg_json_resolver: Arc<PackageJsonResolver<TEnv>>, + 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: &Url, + media_type: MediaType, + ) -> Result<bool, ClosestPkgJsonError> { + 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: &Url, + media_type: MediaType, + is_script: bool, + ) -> Result<bool, ClosestPkgJsonError> { + self.treat_as_cjs_with_is_script(specifier, media_type, Some(is_script)) + } + + fn treat_as_cjs_with_is_script( + &self, + specifier: &Url, + media_type: MediaType, + is_script: Option<bool>, + ) -> Result<bool, ClosestPkgJsonError> { + 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) + } + + /// Gets the referrer for the specified module specifier. + /// + /// Generally the referrer should already be tracked by calling + /// `is_cjs_with_known_is_script` before calling this method. + pub fn get_referrer_kind(&self, specifier: &Url) -> 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( + &self, + specifier: &Url, + media_type: MediaType, + ) -> Option<NodeModuleKind> { + self.get_known_kind_with_is_script(specifier, media_type, None) + } + + fn get_known_kind_with_is_script( + &self, + specifier: &Url, + media_type: MediaType, + is_script: Option<bool>, + ) -> Option<NodeModuleKind> { + 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, +} + +/// Resolves whether a module is CJS or ESM. +#[derive(Debug)] +pub struct IsCjsResolver<TEnv: NodeResolverEnv> { + in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>, + pkg_json_resolver: Arc<PackageJsonResolver<TEnv>>, + options: IsCjsResolverOptions, +} + +impl<TEnv: NodeResolverEnv> IsCjsResolver<TEnv> { + pub fn new( + in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>, + pkg_json_resolver: Arc<PackageJsonResolver<TEnv>>, + options: IsCjsResolverOptions, + ) -> Self { + Self { + in_npm_pkg_checker, + pkg_json_resolver, + options, + } + } + + /// Gets the referrer kind for a script in the LSP. + pub fn get_lsp_referrer_kind( + &self, + specifier: &Url, + is_script: Option<bool>, + ) -> 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: &Url, + media_type: MediaType, + is_script: Option<bool>, + known_cache: &DashMap<Url, NodeModuleKind>, + ) -> Option<NodeModuleKind> { + 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: &Url, + ) -> Result<NodeModuleKind, ClosestPkgJsonError> { + 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) + } + } +} |