summaryrefslogtreecommitdiff
path: root/resolvers/node/resolution.rs
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2024-11-01 12:27:00 -0400
committerGitHub <noreply@github.com>2024-11-01 12:27:00 -0400
commit826e42a5b5880c974ae019a7a21aade6a718062c (patch)
treea46502ecc3c73e4f7fc3a4517d83c7b2f3d0c0d3 /resolvers/node/resolution.rs
parent4774eab64d5176e997b6431f03f075782321b3d9 (diff)
fix: improved support for cjs and cts modules (#26558)
* 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 <biwanczuk@gmail.com>
Diffstat (limited to 'resolvers/node/resolution.rs')
-rw-r--r--resolvers/node/resolution.rs196
1 files changed, 40 insertions, 156 deletions
diff --git a/resolvers/node/resolution.rs b/resolvers/node/resolution.rs
index 811583a5e..d44539e97 100644
--- a/resolvers/node/resolution.rs
+++ b/resolvers/node/resolution.rs
@@ -6,9 +6,6 @@ use std::path::PathBuf;
use anyhow::bail;
use anyhow::Error as AnyError;
-use deno_media_type::MediaType;
-use deno_package_json::PackageJsonRc;
-use deno_path_util::strip_unc_prefix;
use deno_path_util::url_from_file_path;
use serde_json::Map;
use serde_json::Value;
@@ -16,8 +13,6 @@ use url::Url;
use crate::env::NodeResolverEnv;
use crate::errors;
-use crate::errors::CanonicalizingPkgJsonDirError;
-use crate::errors::ClosestPkgJsonError;
use crate::errors::DataUrlReferrerError;
use crate::errors::FinalizeResolutionError;
use crate::errors::InvalidModuleSpecifierError;
@@ -32,7 +27,6 @@ use crate::errors::PackageExportsResolveError;
use crate::errors::PackageImportNotDefinedError;
use crate::errors::PackageImportsResolveError;
use crate::errors::PackageImportsResolveErrorKind;
-use crate::errors::PackageJsonLoadError;
use crate::errors::PackagePathNotExportedError;
use crate::errors::PackageResolveError;
use crate::errors::PackageSubpathResolveError;
@@ -42,14 +36,13 @@ use crate::errors::PackageTargetResolveError;
use crate::errors::PackageTargetResolveErrorKind;
use crate::errors::ResolveBinaryCommandsError;
use crate::errors::ResolvePkgJsonBinExportError;
-use crate::errors::ResolvePkgSubpathFromDenoModuleError;
-use crate::errors::TypeScriptNotSupportedInNpmError;
use crate::errors::TypesNotFoundError;
use crate::errors::TypesNotFoundErrorData;
use crate::errors::UnsupportedDirImportError;
use crate::errors::UnsupportedEsmUrlSchemeError;
-use crate::errors::UrlToNodeResolutionError;
+use crate::npm::InNpmPackageCheckerRc;
use crate::NpmResolverRc;
+use crate::PackageJsonResolverRc;
use crate::PathClean;
use deno_package_json::PackageJson;
@@ -73,16 +66,14 @@ impl NodeResolutionMode {
#[derive(Debug)]
pub enum NodeResolution {
- Esm(Url),
- CommonJs(Url),
+ Module(Url),
BuiltIn(String),
}
impl NodeResolution {
pub fn into_url(self) -> Url {
match self {
- Self::Esm(u) => u,
- Self::CommonJs(u) => u,
+ Self::Module(u) => u,
Self::BuiltIn(specifier) => {
if specifier.starts_with("node:") {
Url::parse(&specifier).unwrap()
@@ -92,42 +83,6 @@ impl NodeResolution {
}
}
}
-
- pub fn into_specifier_and_media_type(
- resolution: Option<Self>,
- ) -> (Url, MediaType) {
- match resolution {
- Some(NodeResolution::CommonJs(specifier)) => {
- let media_type = MediaType::from_specifier(&specifier);
- (
- specifier,
- match media_type {
- MediaType::JavaScript | MediaType::Jsx => MediaType::Cjs,
- MediaType::TypeScript | MediaType::Tsx => MediaType::Cts,
- MediaType::Dts => MediaType::Dcts,
- _ => media_type,
- },
- )
- }
- Some(NodeResolution::Esm(specifier)) => {
- let media_type = MediaType::from_specifier(&specifier);
- (
- specifier,
- match media_type {
- MediaType::JavaScript | MediaType::Jsx => MediaType::Mjs,
- MediaType::TypeScript | MediaType::Tsx => MediaType::Mts,
- MediaType::Dts => MediaType::Dmts,
- _ => media_type,
- },
- )
- }
- Some(resolution) => (resolution.into_url(), MediaType::Dts),
- None => (
- Url::parse("internal:///missing_dependency.d.ts").unwrap(),
- MediaType::Dts,
- ),
- }
- }
}
#[allow(clippy::disallowed_types)]
@@ -136,16 +91,28 @@ pub type NodeResolverRc<TEnv> = crate::sync::MaybeArc<NodeResolver<TEnv>>;
#[derive(Debug)]
pub struct NodeResolver<TEnv: NodeResolverEnv> {
env: TEnv,
+ in_npm_pkg_checker: InNpmPackageCheckerRc,
npm_resolver: NpmResolverRc,
+ pkg_json_resolver: PackageJsonResolverRc<TEnv>,
}
impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
- pub fn new(env: TEnv, npm_resolver: NpmResolverRc) -> Self {
- Self { env, npm_resolver }
+ pub fn new(
+ env: TEnv,
+ in_npm_pkg_checker: InNpmPackageCheckerRc,
+ npm_resolver: NpmResolverRc,
+ pkg_json_resolver: PackageJsonResolverRc<TEnv>,
+ ) -> Self {
+ Self {
+ env,
+ in_npm_pkg_checker,
+ npm_resolver,
+ pkg_json_resolver,
+ }
}
pub fn in_npm_package(&self, specifier: &Url) -> bool {
- self.npm_resolver.in_npm_package(specifier)
+ self.in_npm_pkg_checker.in_npm_package(specifier)
}
/// This function is an implementation of `defaultResolve` in
@@ -166,7 +133,7 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
if let Ok(url) = Url::parse(specifier) {
if url.scheme() == "data" {
- return Ok(NodeResolution::Esm(url));
+ return Ok(NodeResolution::Module(url));
}
if let Some(module_name) =
@@ -191,7 +158,7 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
let url = referrer
.join(specifier)
.map_err(|source| DataUrlReferrerError { source })?;
- return Ok(NodeResolution::Esm(url));
+ return Ok(NodeResolution::Module(url));
}
}
@@ -212,7 +179,7 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
};
let url = self.finalize_resolution(url, Some(referrer))?;
- let resolve_response = self.url_to_node_resolution(url)?;
+ let resolve_response = NodeResolution::Module(url);
// TODO(bartlomieju): skipped checking errors for commonJS resolution and
// "preserveSymlinksMain"/"preserveSymlinks" options.
Ok(resolve_response)
@@ -236,6 +203,7 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
})?)
} else if specifier.starts_with('#') {
let pkg_config = self
+ .pkg_json_resolver
.get_closest_package_json(referrer)
.map_err(PackageImportsResolveErrorKind::ClosestPkgJson)
.map_err(|err| PackageImportsResolveError(Box::new(err)))?;
@@ -332,7 +300,7 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
package_subpath: Option<&str>,
maybe_referrer: Option<&Url>,
mode: NodeResolutionMode,
- ) -> Result<NodeResolution, ResolvePkgSubpathFromDenoModuleError> {
+ ) -> Result<Url, PackageSubpathResolveError> {
let node_module_kind = NodeModuleKind::Esm;
let package_subpath = package_subpath
.map(|s| format!("./{s}"))
@@ -345,10 +313,9 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
DEFAULT_CONDITIONS,
mode,
)?;
- let resolve_response = self.url_to_node_resolution(resolved_url)?;
// TODO(bartlomieju): skipped checking errors for commonJS resolution and
// "preserveSymlinksMain"/"preserveSymlinks" options.
- Ok(resolve_response)
+ Ok(resolved_url)
}
pub fn resolve_binary_commands(
@@ -356,7 +323,9 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
package_folder: &Path,
) -> Result<Vec<String>, ResolveBinaryCommandsError> {
let pkg_json_path = package_folder.join("package.json");
- let Some(package_json) = self.load_package_json(&pkg_json_path)? else {
+ let Some(package_json) =
+ self.pkg_json_resolver.load_package_json(&pkg_json_path)?
+ else {
return Ok(Vec::new());
};
@@ -381,9 +350,11 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
&self,
package_folder: &Path,
sub_path: Option<&str>,
- ) -> Result<NodeResolution, ResolvePkgJsonBinExportError> {
+ ) -> Result<Url, ResolvePkgJsonBinExportError> {
let pkg_json_path = package_folder.join("package.json");
- let Some(package_json) = self.load_package_json(&pkg_json_path)? else {
+ let Some(package_json) =
+ self.pkg_json_resolver.load_package_json(&pkg_json_path)?
+ else {
return Err(ResolvePkgJsonBinExportError::MissingPkgJson {
pkg_json_path,
});
@@ -396,37 +367,9 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
})?;
let url = url_from_file_path(&package_folder.join(bin_entry)).unwrap();
- let resolve_response = self.url_to_node_resolution(url)?;
// TODO(bartlomieju): skipped checking errors for commonJS resolution and
// "preserveSymlinksMain"/"preserveSymlinks" options.
- Ok(resolve_response)
- }
-
- pub fn url_to_node_resolution(
- &self,
- url: Url,
- ) -> Result<NodeResolution, UrlToNodeResolutionError> {
- let url_str = url.as_str().to_lowercase();
- if url_str.starts_with("http") || url_str.ends_with(".json") {
- Ok(NodeResolution::Esm(url))
- } else if url_str.ends_with(".js") || url_str.ends_with(".d.ts") {
- let maybe_package_config = self.get_closest_package_json(&url)?;
- match maybe_package_config {
- Some(c) if c.typ == "module" => Ok(NodeResolution::Esm(url)),
- Some(_) => Ok(NodeResolution::CommonJs(url)),
- None => Ok(NodeResolution::Esm(url)),
- }
- } else if url_str.ends_with(".mjs") || url_str.ends_with(".d.mts") {
- Ok(NodeResolution::Esm(url))
- } else if url_str.ends_with(".ts") || url_str.ends_with(".mts") {
- if self.in_npm_package(&url) {
- Err(TypeScriptNotSupportedInNpmError { specifier: url }.into())
- } else {
- Ok(NodeResolution::Esm(url))
- }
- } else {
- Ok(NodeResolution::CommonJs(url))
- }
+ Ok(url)
}
/// Checks if the resolved file has a corresponding declaration file.
@@ -1101,7 +1044,9 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
let (package_name, package_subpath, _is_scoped) =
parse_npm_pkg_name(specifier, referrer)?;
- if let Some(package_config) = self.get_closest_package_json(referrer)? {
+ if let Some(package_config) =
+ self.pkg_json_resolver.get_closest_package_json(referrer)?
+ {
// ResolveSelf
if package_config.name.as_ref() == Some(&package_name) {
if let Some(exports) = &package_config.exports {
@@ -1216,7 +1161,10 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
mode: NodeResolutionMode,
) -> Result<Url, PackageSubpathResolveError> {
let package_json_path = package_dir_path.join("package.json");
- match self.load_package_json(&package_json_path)? {
+ match self
+ .pkg_json_resolver
+ .load_package_json(&package_json_path)?
+ {
Some(pkg_json) => self.resolve_package_subpath(
&pkg_json,
package_subpath,
@@ -1337,70 +1285,6 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
}
}
- pub fn get_closest_package_json(
- &self,
- url: &Url,
- ) -> Result<Option<PackageJsonRc>, ClosestPkgJsonError> {
- let Ok(file_path) = deno_path_util::url_to_file_path(url) else {
- return Ok(None);
- };
- self.get_closest_package_json_from_path(&file_path)
- }
-
- pub fn get_closest_package_json_from_path(
- &self,
- file_path: &Path,
- ) -> Result<Option<PackageJsonRc>, ClosestPkgJsonError> {
- // we use this for deno compile using byonm because the script paths
- // won't be in virtual file system, but the package.json paths will be
- fn canonicalize_first_ancestor_exists(
- dir_path: &Path,
- env: &dyn NodeResolverEnv,
- ) -> Result<Option<PathBuf>, std::io::Error> {
- for ancestor in dir_path.ancestors() {
- match env.realpath_sync(ancestor) {
- Ok(dir_path) => return Ok(Some(dir_path)),
- Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
- // keep searching
- }
- Err(err) => return Err(err),
- }
- }
- Ok(None)
- }
-
- let parent_dir = file_path.parent().unwrap();
- let Some(start_dir) = canonicalize_first_ancestor_exists(
- parent_dir, &self.env,
- )
- .map_err(|source| CanonicalizingPkgJsonDirError {
- dir_path: parent_dir.to_path_buf(),
- source,
- })?
- else {
- return Ok(None);
- };
- let start_dir = strip_unc_prefix(start_dir);
- for current_dir in start_dir.ancestors() {
- let package_json_path = current_dir.join("package.json");
- if let Some(pkg_json) = self.load_package_json(&package_json_path)? {
- return Ok(Some(pkg_json));
- }
- }
-
- Ok(None)
- }
-
- pub fn load_package_json(
- &self,
- package_json_path: &Path,
- ) -> Result<Option<PackageJsonRc>, PackageJsonLoadError> {
- crate::package_json::load_pkg_json(
- self.env.pkg_json_fs(),
- package_json_path,
- )
- }
-
pub(super) fn legacy_main_resolve(
&self,
package_json: &PackageJson,