summaryrefslogtreecommitdiff
path: root/resolvers/node
diff options
context:
space:
mode:
Diffstat (limited to 'resolvers/node')
-rw-r--r--resolvers/node/Cargo.toml3
-rw-r--r--resolvers/node/analyze.rs56
-rw-r--r--resolvers/node/errors.rs128
-rw-r--r--resolvers/node/lib.rs10
-rw-r--r--resolvers/node/npm.rs15
-rw-r--r--resolvers/node/package_json.rs106
-rw-r--r--resolvers/node/resolution.rs279
7 files changed, 284 insertions, 313 deletions
diff --git a/resolvers/node/Cargo.toml b/resolvers/node/Cargo.toml
index f05b209fd..eeac79c25 100644
--- a/resolvers/node/Cargo.toml
+++ b/resolvers/node/Cargo.toml
@@ -2,7 +2,7 @@
[package]
name = "node_resolver"
-version = "0.10.0"
+version = "0.16.0"
authors.workspace = true
edition.workspace = true
license.workspace = true
@@ -19,6 +19,7 @@ sync = ["deno_package_json/sync"]
[dependencies]
anyhow.workspace = true
async-trait.workspace = true
+boxed_error.workspace = true
deno_media_type.workspace = true
deno_package_json.workspace = true
deno_path_util.workspace = true
diff --git a/resolvers/node/analyze.rs b/resolvers/node/analyze.rs
index 009296006..912689080 100644
--- a/resolvers/node/analyze.rs
+++ b/resolvers/node/analyze.rs
@@ -19,18 +19,19 @@ use anyhow::Error as AnyError;
use url::Url;
use crate::env::NodeResolverEnv;
-use crate::package_json::load_pkg_json;
+use crate::npm::InNpmPackageCheckerRc;
use crate::resolution::NodeResolverRc;
use crate::NodeModuleKind;
use crate::NodeResolutionMode;
-use crate::NpmResolverRc;
+use crate::NpmPackageFolderResolverRc;
+use crate::PackageJsonResolverRc;
use crate::PathClean;
#[derive(Debug, Clone)]
-pub enum CjsAnalysis {
+pub enum CjsAnalysis<'a> {
/// File was found to be an ES module and the translator should
/// load the code as ESM.
- Esm(String),
+ Esm(Cow<'a, str>),
Cjs(CjsAnalysisExports),
}
@@ -50,11 +51,11 @@ pub trait CjsCodeAnalyzer {
/// already has it. If the source is needed by the implementation,
/// then it can use the provided source, or otherwise load it if
/// necessary.
- async fn analyze_cjs(
+ async fn analyze_cjs<'a>(
&self,
specifier: &Url,
- maybe_source: Option<String>,
- ) -> Result<CjsAnalysis, AnyError>;
+ maybe_source: Option<Cow<'a, str>>,
+ ) -> Result<CjsAnalysis<'a>, AnyError>;
}
pub struct NodeCodeTranslator<
@@ -63,8 +64,10 @@ pub struct NodeCodeTranslator<
> {
cjs_code_analyzer: TCjsCodeAnalyzer,
env: TNodeResolverEnv,
+ in_npm_pkg_checker: InNpmPackageCheckerRc,
node_resolver: NodeResolverRc<TNodeResolverEnv>,
- npm_resolver: NpmResolverRc,
+ npm_resolver: NpmPackageFolderResolverRc,
+ pkg_json_resolver: PackageJsonResolverRc<TNodeResolverEnv>,
}
impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
@@ -73,14 +76,18 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
pub fn new(
cjs_code_analyzer: TCjsCodeAnalyzer,
env: TNodeResolverEnv,
+ in_npm_pkg_checker: InNpmPackageCheckerRc,
node_resolver: NodeResolverRc<TNodeResolverEnv>,
- npm_resolver: NpmResolverRc,
+ npm_resolver: NpmPackageFolderResolverRc,
+ pkg_json_resolver: PackageJsonResolverRc<TNodeResolverEnv>,
) -> Self {
Self {
cjs_code_analyzer,
env,
+ in_npm_pkg_checker,
node_resolver,
npm_resolver,
+ pkg_json_resolver,
}
}
@@ -90,11 +97,11 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
/// For all discovered reexports the analysis will be performed recursively.
///
/// If successful a source code for equivalent ES module is returned.
- pub async fn translate_cjs_to_esm(
+ pub async fn translate_cjs_to_esm<'a>(
&self,
entry_specifier: &Url,
- source: Option<String>,
- ) -> Result<String, AnyError> {
+ source: Option<Cow<'a, str>>,
+ ) -> Result<Cow<'a, str>, AnyError> {
let mut temp_var_count = 0;
let analysis = self
@@ -108,7 +115,7 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
};
let mut source = vec![
- r#"import {createRequire as __internalCreateRequire} from "node:module";
+ r#"import {createRequire as __internalCreateRequire, Module as __internalModule } from "node:module";
const require = __internalCreateRequire(import.meta.url);"#
.to_string(),
];
@@ -135,7 +142,12 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
}
source.push(format!(
- "const mod = require(\"{}\");",
+ r#"let mod;
+ if (import.meta.main) {{
+ mod = __internalModule._load("{0}", null, true)
+ }} else {{
+ mod = require("{0}");
+ }}"#,
url_to_file_path(entry_specifier)
.unwrap()
.to_str()
@@ -159,7 +171,7 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
source.push("export default mod;".to_string());
let translated_source = source.join("\n");
- Ok(translated_source)
+ Ok(Cow::Owned(translated_source))
}
async fn analyze_reexports<'a>(
@@ -174,7 +186,7 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
struct Analysis {
reexport_specifier: url::Url,
referrer: url::Url,
- analysis: CjsAnalysis,
+ analysis: CjsAnalysis<'static>,
}
type AnalysisFuture<'a> = LocalBoxFuture<'a, Result<Analysis, AnyError>>;
@@ -329,8 +341,9 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
}?;
let package_json_path = module_dir.join("package.json");
- let maybe_package_json =
- load_pkg_json(self.env.pkg_json_fs(), &package_json_path)?;
+ let maybe_package_json = self
+ .pkg_json_resolver
+ .load_package_json(&package_json_path)?;
if let Some(package_json) = maybe_package_json {
if let Some(exports) = &package_json.exports {
return Some(
@@ -356,8 +369,9 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
if self.env.is_dir_sync(&d) {
// subdir might have a package.json that specifies the entrypoint
let package_json_path = d.join("package.json");
- let maybe_package_json =
- load_pkg_json(self.env.pkg_json_fs(), &package_json_path)?;
+ let maybe_package_json = self
+ .pkg_json_resolver
+ .load_package_json(&package_json_path)?;
if let Some(package_json) = maybe_package_json {
if let Some(main) = package_json.main(NodeModuleKind::Cjs) {
return Ok(Some(url_from_file_path(&d.join(main).clean())?));
@@ -382,7 +396,7 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
// as a fallback, attempt to resolve it via the ancestor directories
let mut last = referrer_path.as_path();
while let Some(parent) = last.parent() {
- if !self.npm_resolver.in_npm_package_at_dir_path(parent) {
+ if !self.in_npm_pkg_checker.in_npm_package_at_dir_path(parent) {
break;
}
let path = if parent.ends_with("node_modules") {
diff --git a/resolvers/node/errors.rs b/resolvers/node/errors.rs
index 4ba829eda..0f332d2c9 100644
--- a/resolvers/node/errors.rs
+++ b/resolvers/node/errors.rs
@@ -4,39 +4,13 @@ use std::borrow::Cow;
use std::fmt::Write;
use std::path::PathBuf;
+use boxed_error::Boxed;
use thiserror::Error;
use url::Url;
use crate::NodeModuleKind;
use crate::NodeResolutionMode;
-macro_rules! kinded_err {
- ($name:ident, $kind_name:ident) => {
- #[derive(Error, Debug)]
- #[error(transparent)]
- pub struct $name(pub Box<$kind_name>);
-
- impl $name {
- pub fn as_kind(&self) -> &$kind_name {
- &self.0
- }
-
- pub fn into_kind(self) -> $kind_name {
- *self.0
- }
- }
-
- impl<E> From<E> for $name
- where
- $kind_name: From<E>,
- {
- fn from(err: E) -> Self {
- $name(Box::new($kind_name::from(err)))
- }
- }
- };
-}
-
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[allow(non_camel_case_types)]
pub enum NodeJsErrorCode {
@@ -81,29 +55,6 @@ pub trait NodeJsErrorCoded {
fn code(&self) -> NodeJsErrorCode;
}
-kinded_err!(
- ResolvePkgSubpathFromDenoModuleError,
- ResolvePkgSubpathFromDenoModuleErrorKind
-);
-
-impl NodeJsErrorCoded for ResolvePkgSubpathFromDenoModuleError {
- fn code(&self) -> NodeJsErrorCode {
- use ResolvePkgSubpathFromDenoModuleErrorKind::*;
- match self.as_kind() {
- PackageSubpathResolve(e) => e.code(),
- UrlToNodeResolution(e) => e.code(),
- }
- }
-}
-
-#[derive(Debug, Error)]
-pub enum ResolvePkgSubpathFromDenoModuleErrorKind {
- #[error(transparent)]
- PackageSubpathResolve(#[from] PackageSubpathResolveError),
- #[error(transparent)]
- UrlToNodeResolution(#[from] UrlToNodeResolutionError),
-}
-
// todo(https://github.com/denoland/deno_core/issues/810): make this a TypeError
#[derive(Debug, Clone, Error)]
#[error(
@@ -125,7 +76,8 @@ impl NodeJsErrorCoded for InvalidModuleSpecifierError {
}
}
-kinded_err!(LegacyResolveError, LegacyResolveErrorKind);
+#[derive(Debug, Boxed)]
+pub struct LegacyResolveError(pub Box<LegacyResolveErrorKind>);
#[derive(Debug, Error)]
pub enum LegacyResolveErrorKind {
@@ -144,8 +96,6 @@ impl NodeJsErrorCoded for LegacyResolveError {
}
}
-kinded_err!(PackageFolderResolveError, PackageFolderResolveErrorKind);
-
#[derive(Debug, Error)]
#[error(
"Could not find package '{}' from referrer '{}'{}.",
@@ -209,6 +159,9 @@ impl NodeJsErrorCoded for PackageFolderResolveError {
}
}
+#[derive(Debug, Boxed)]
+pub struct PackageFolderResolveError(pub Box<PackageFolderResolveErrorKind>);
+
#[derive(Debug, Error)]
pub enum PackageFolderResolveErrorKind {
#[error(transparent)]
@@ -219,8 +172,6 @@ pub enum PackageFolderResolveErrorKind {
Io(#[from] PackageFolderResolveIoError),
}
-kinded_err!(PackageSubpathResolveError, PackageSubpathResolveErrorKind);
-
impl NodeJsErrorCoded for PackageSubpathResolveError {
fn code(&self) -> NodeJsErrorCode {
match self.as_kind() {
@@ -231,6 +182,9 @@ impl NodeJsErrorCoded for PackageSubpathResolveError {
}
}
+#[derive(Debug, Boxed)]
+pub struct PackageSubpathResolveError(pub Box<PackageSubpathResolveErrorKind>);
+
#[derive(Debug, Error)]
pub enum PackageSubpathResolveErrorKind {
#[error(transparent)]
@@ -275,8 +229,6 @@ impl NodeJsErrorCoded for PackageTargetNotFoundError {
}
}
-kinded_err!(PackageTargetResolveError, PackageTargetResolveErrorKind);
-
impl NodeJsErrorCoded for PackageTargetResolveError {
fn code(&self) -> NodeJsErrorCode {
match self.as_kind() {
@@ -289,6 +241,9 @@ impl NodeJsErrorCoded for PackageTargetResolveError {
}
}
+#[derive(Debug, Boxed)]
+pub struct PackageTargetResolveError(pub Box<PackageTargetResolveErrorKind>);
+
#[derive(Debug, Error)]
pub enum PackageTargetResolveErrorKind {
#[error(transparent)]
@@ -303,8 +258,6 @@ pub enum PackageTargetResolveErrorKind {
TypesNotFound(#[from] TypesNotFoundError),
}
-kinded_err!(PackageExportsResolveError, PackageExportsResolveErrorKind);
-
impl NodeJsErrorCoded for PackageExportsResolveError {
fn code(&self) -> NodeJsErrorCode {
match self.as_kind() {
@@ -314,6 +267,9 @@ impl NodeJsErrorCoded for PackageExportsResolveError {
}
}
+#[derive(Debug, Boxed)]
+pub struct PackageExportsResolveError(pub Box<PackageExportsResolveErrorKind>);
+
#[derive(Debug, Error)]
pub enum PackageExportsResolveErrorKind {
#[error(transparent)]
@@ -361,8 +317,6 @@ impl NodeJsErrorCoded for PackageJsonLoadError {
}
}
-kinded_err!(ClosestPkgJsonError, ClosestPkgJsonErrorKind);
-
impl NodeJsErrorCoded for ClosestPkgJsonError {
fn code(&self) -> NodeJsErrorCode {
match self.as_kind() {
@@ -372,6 +326,9 @@ impl NodeJsErrorCoded for ClosestPkgJsonError {
}
}
+#[derive(Debug, Boxed)]
+pub struct ClosestPkgJsonError(pub Box<ClosestPkgJsonErrorKind>);
+
#[derive(Debug, Error)]
pub enum ClosestPkgJsonErrorKind {
#[error(transparent)]
@@ -394,37 +351,6 @@ impl NodeJsErrorCoded for CanonicalizingPkgJsonDirError {
}
}
-#[derive(Debug, Error)]
-#[error("TypeScript files are not supported in npm packages: {specifier}")]
-pub struct TypeScriptNotSupportedInNpmError {
- pub specifier: Url,
-}
-
-impl NodeJsErrorCoded for TypeScriptNotSupportedInNpmError {
- fn code(&self) -> NodeJsErrorCode {
- NodeJsErrorCode::ERR_UNKNOWN_FILE_EXTENSION
- }
-}
-
-kinded_err!(UrlToNodeResolutionError, UrlToNodeResolutionErrorKind);
-
-impl NodeJsErrorCoded for UrlToNodeResolutionError {
- fn code(&self) -> NodeJsErrorCode {
- match self.as_kind() {
- UrlToNodeResolutionErrorKind::TypeScriptNotSupported(e) => e.code(),
- UrlToNodeResolutionErrorKind::ClosestPkgJson(e) => e.code(),
- }
- }
-}
-
-#[derive(Debug, Error)]
-pub enum UrlToNodeResolutionErrorKind {
- #[error(transparent)]
- TypeScriptNotSupported(#[from] TypeScriptNotSupportedInNpmError),
- #[error(transparent)]
- ClosestPkgJson(#[from] ClosestPkgJsonError),
-}
-
// todo(https://github.com/denoland/deno_core/issues/810): make this a TypeError
#[derive(Debug, Error)]
#[error(
@@ -446,7 +372,8 @@ impl NodeJsErrorCoded for PackageImportNotDefinedError {
}
}
-kinded_err!(PackageImportsResolveError, PackageImportsResolveErrorKind);
+#[derive(Debug, Boxed)]
+pub struct PackageImportsResolveError(pub Box<PackageImportsResolveErrorKind>);
#[derive(Debug, Error)]
pub enum PackageImportsResolveErrorKind {
@@ -471,8 +398,6 @@ impl NodeJsErrorCoded for PackageImportsResolveErrorKind {
}
}
-kinded_err!(PackageResolveError, PackageResolveErrorKind);
-
impl NodeJsErrorCoded for PackageResolveError {
fn code(&self) -> NodeJsErrorCode {
match self.as_kind() {
@@ -485,6 +410,9 @@ impl NodeJsErrorCoded for PackageResolveError {
}
}
+#[derive(Debug, Boxed)]
+pub struct PackageResolveError(pub Box<PackageResolveErrorKind>);
+
#[derive(Debug, Error)]
pub enum PackageResolveErrorKind {
#[error(transparent)]
@@ -515,7 +443,8 @@ pub struct DataUrlReferrerError {
pub source: url::ParseError,
}
-kinded_err!(NodeResolveError, NodeResolveErrorKind);
+#[derive(Debug, Boxed)]
+pub struct NodeResolveError(pub Box<NodeResolveErrorKind>);
#[derive(Debug, Error)]
pub enum NodeResolveErrorKind {
@@ -533,11 +462,10 @@ pub enum NodeResolveErrorKind {
TypesNotFound(#[from] TypesNotFoundError),
#[error(transparent)]
FinalizeResolution(#[from] FinalizeResolutionError),
- #[error(transparent)]
- UrlToNodeResolution(#[from] UrlToNodeResolutionError),
}
-kinded_err!(FinalizeResolutionError, FinalizeResolutionErrorKind);
+#[derive(Debug, Boxed)]
+pub struct FinalizeResolutionError(pub Box<FinalizeResolutionErrorKind>);
#[derive(Debug, Error)]
pub enum FinalizeResolutionErrorKind {
@@ -728,8 +656,6 @@ pub enum ResolvePkgJsonBinExportError {
MissingPkgJson { pkg_json_path: PathBuf },
#[error("Failed resolving binary export. {message}")]
InvalidBinProperty { message: String },
- #[error(transparent)]
- UrlToNodeResolution(#[from] UrlToNodeResolutionError),
}
#[derive(Debug, Error)]
diff --git a/resolvers/node/lib.rs b/resolvers/node/lib.rs
index f03f77048..87bd62994 100644
--- a/resolvers/node/lib.rs
+++ b/resolvers/node/lib.rs
@@ -13,12 +13,16 @@ mod resolution;
mod sync;
pub use deno_package_json::PackageJson;
-pub use npm::NpmResolver;
-pub use npm::NpmResolverRc;
-pub use package_json::load_pkg_json;
+pub use npm::InNpmPackageChecker;
+pub use npm::InNpmPackageCheckerRc;
+pub use npm::NpmPackageFolderResolver;
+pub use npm::NpmPackageFolderResolverRc;
+pub use package_json::PackageJsonResolver;
+pub use package_json::PackageJsonResolverRc;
pub use package_json::PackageJsonThreadLocalCache;
pub use path::PathClean;
pub use resolution::parse_npm_pkg_name;
+pub use resolution::resolve_specifier_into_node_modules;
pub use resolution::NodeModuleKind;
pub use resolution::NodeResolution;
pub use resolution::NodeResolutionMode;
diff --git a/resolvers/node/npm.rs b/resolvers/node/npm.rs
index 6b5f21db6..ab3a17942 100644
--- a/resolvers/node/npm.rs
+++ b/resolvers/node/npm.rs
@@ -13,16 +13,25 @@ use crate::sync::MaybeSend;
use crate::sync::MaybeSync;
#[allow(clippy::disallowed_types)]
-pub type NpmResolverRc = crate::sync::MaybeArc<dyn NpmResolver>;
+pub type NpmPackageFolderResolverRc =
+ crate::sync::MaybeArc<dyn NpmPackageFolderResolver>;
-pub trait NpmResolver: std::fmt::Debug + MaybeSend + MaybeSync {
- /// Resolves an npm package folder path from an npm package referrer.
+pub trait NpmPackageFolderResolver:
+ std::fmt::Debug + MaybeSend + MaybeSync
+{
+ /// Resolves an npm package folder path from the specified referrer.
fn resolve_package_folder_from_package(
&self,
specifier: &str,
referrer: &Url,
) -> Result<PathBuf, errors::PackageFolderResolveError>;
+}
+
+#[allow(clippy::disallowed_types)]
+pub type InNpmPackageCheckerRc = crate::sync::MaybeArc<dyn InNpmPackageChecker>;
+/// Checks if a provided specifier is in an npm package.
+pub trait InNpmPackageChecker: std::fmt::Debug + MaybeSend + MaybeSync {
fn in_npm_package(&self, specifier: &Url) -> bool;
fn in_npm_package_at_dir_path(&self, path: &Path) -> bool {
diff --git a/resolvers/node/package_json.rs b/resolvers/node/package_json.rs
index de750f1d7..ae016ebe3 100644
--- a/resolvers/node/package_json.rs
+++ b/resolvers/node/package_json.rs
@@ -2,15 +2,21 @@
use deno_package_json::PackageJson;
use deno_package_json::PackageJsonRc;
+use deno_path_util::strip_unc_prefix;
use std::cell::RefCell;
use std::collections::HashMap;
use std::io::ErrorKind;
use std::path::Path;
use std::path::PathBuf;
+use url::Url;
+use crate::env::NodeResolverEnv;
+use crate::errors::CanonicalizingPkgJsonDirError;
+use crate::errors::ClosestPkgJsonError;
use crate::errors::PackageJsonLoadError;
-// use a thread local cache so that workers have their own distinct cache
+// it would be nice if this was passed down as a ctor arg to the package.json resolver,
+// but it's a little bit complicated to do that, so we just maintain a thread local cache
thread_local! {
static CACHE: RefCell<HashMap<PathBuf, PackageJsonRc>> = RefCell::new(HashMap::new());
}
@@ -33,21 +39,91 @@ impl deno_package_json::PackageJsonCache for PackageJsonThreadLocalCache {
}
}
-/// Helper to load a package.json file using the thread local cache
-/// in node_resolver.
-pub fn load_pkg_json(
- fs: &dyn deno_package_json::fs::DenoPkgJsonFs,
- path: &Path,
-) -> Result<Option<PackageJsonRc>, PackageJsonLoadError> {
- let result =
- PackageJson::load_from_path(path, fs, Some(&PackageJsonThreadLocalCache));
- match result {
- Ok(pkg_json) => Ok(Some(pkg_json)),
- Err(deno_package_json::PackageJsonLoadError::Io { source, .. })
- if source.kind() == ErrorKind::NotFound =>
- {
+#[allow(clippy::disallowed_types)]
+pub type PackageJsonResolverRc<TEnv> =
+ crate::sync::MaybeArc<PackageJsonResolver<TEnv>>;
+
+#[derive(Debug)]
+pub struct PackageJsonResolver<TEnv: NodeResolverEnv> {
+ env: TEnv,
+}
+
+impl<TEnv: NodeResolverEnv> PackageJsonResolver<TEnv> {
+ pub fn new(env: TEnv) -> Self {
+ Self { env }
+ }
+
+ 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<TEnv: NodeResolverEnv>(
+ dir_path: &Path,
+ env: &TEnv,
+ ) -> 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)
}
- Err(err) => Err(PackageJsonLoadError(err)),
+
+ 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,
+ path: &Path,
+ ) -> Result<Option<PackageJsonRc>, PackageJsonLoadError> {
+ let result = PackageJson::load_from_path(
+ path,
+ self.env.pkg_json_fs(),
+ Some(&PackageJsonThreadLocalCache),
+ );
+ match result {
+ Ok(pkg_json) => Ok(Some(pkg_json)),
+ Err(deno_package_json::PackageJsonLoadError::Io { source, .. })
+ if source.kind() == ErrorKind::NotFound =>
+ {
+ Ok(None)
+ }
+ Err(err) => Err(PackageJsonLoadError(err)),
+ }
}
}
diff --git a/resolvers/node/resolution.rs b/resolvers/node/resolution.rs
index 811583a5e..c2ec25aca 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::NpmResolverRc;
+use crate::npm::InNpmPackageCheckerRc;
+use crate::NpmPackageFolderResolverRc;
+use crate::PackageJsonResolverRc;
use crate::PathClean;
use deno_package_json::PackageJson;
@@ -57,6 +50,15 @@ pub static DEFAULT_CONDITIONS: &[&str] = &["deno", "node", "import"];
pub static REQUIRE_CONDITIONS: &[&str] = &["require", "node"];
static TYPES_ONLY_CONDITIONS: &[&str] = &["types"];
+fn conditions_from_module_kind(
+ kind: NodeModuleKind,
+) -> &'static [&'static str] {
+ match kind {
+ NodeModuleKind::Esm => DEFAULT_CONDITIONS,
+ NodeModuleKind::Cjs => REQUIRE_CONDITIONS,
+ }
+}
+
pub type NodeModuleKind = deno_package_json::NodeModuleKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -73,16 +75,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 +92,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 +100,28 @@ pub type NodeResolverRc<TEnv> = crate::sync::MaybeArc<NodeResolver<TEnv>>;
#[derive(Debug)]
pub struct NodeResolver<TEnv: NodeResolverEnv> {
env: TEnv,
- npm_resolver: NpmResolverRc,
+ in_npm_pkg_checker: InNpmPackageCheckerRc,
+ npm_pkg_folder_resolver: NpmPackageFolderResolverRc,
+ 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_pkg_folder_resolver: NpmPackageFolderResolverRc,
+ pkg_json_resolver: PackageJsonResolverRc<TEnv>,
+ ) -> Self {
+ Self {
+ env,
+ in_npm_pkg_checker,
+ npm_pkg_folder_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 +142,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 +167,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));
}
}
@@ -199,8 +175,7 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
specifier,
referrer,
referrer_kind,
- // even though the referrer may be CJS, if we're here that means we're doing ESM resolution
- DEFAULT_CONDITIONS,
+ conditions_from_module_kind(referrer_kind),
mode,
)?;
@@ -212,7 +187,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)
@@ -227,7 +202,7 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
mode: NodeResolutionMode,
) -> Result<Url, NodeResolveError> {
if should_be_treated_as_relative_or_absolute_path(specifier) {
- Ok(referrer.join(specifier).map_err(|err| {
+ Ok(node_join_url(referrer, specifier).map_err(|err| {
NodeResolveRelativeJoinError {
path: specifier.to_string(),
base: referrer.clone(),
@@ -236,6 +211,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)))?;
@@ -331,9 +307,9 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
package_dir: &Path,
package_subpath: Option<&str>,
maybe_referrer: Option<&Url>,
+ referrer_kind: NodeModuleKind,
mode: NodeResolutionMode,
- ) -> Result<NodeResolution, ResolvePkgSubpathFromDenoModuleError> {
- let node_module_kind = NodeModuleKind::Esm;
+ ) -> Result<Url, PackageSubpathResolveError> {
let package_subpath = package_subpath
.map(|s| format!("./{s}"))
.unwrap_or_else(|| ".".to_string());
@@ -341,14 +317,13 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
package_dir,
&package_subpath,
maybe_referrer,
- node_module_kind,
- DEFAULT_CONDITIONS,
+ referrer_kind,
+ conditions_from_module_kind(referrer_kind),
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 +331,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 +358,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 +375,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.
@@ -498,10 +449,7 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
/* sub path */ ".",
maybe_referrer,
referrer_kind,
- match referrer_kind {
- NodeModuleKind::Esm => DEFAULT_CONDITIONS,
- NodeModuleKind::Cjs => REQUIRE_CONDITIONS,
- },
+ conditions_from_module_kind(referrer_kind),
NodeResolutionMode::Types,
);
if let Ok(resolution) = resolution_result {
@@ -1101,7 +1049,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 {
@@ -1176,7 +1126,7 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
mode: NodeResolutionMode,
) -> Result<Url, PackageResolveError> {
let package_dir_path = self
- .npm_resolver
+ .npm_pkg_folder_resolver
.resolve_package_folder_from_package(package_name, referrer)?;
// todo: error with this instead when can't find package
@@ -1216,7 +1166,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 +1290,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,
@@ -1523,6 +1412,25 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
)
}
}
+
+ /// Resolves a specifier that is pointing into a node_modules folder by canonicalizing it.
+ ///
+ /// Returns `None` when the specifier is not in a node_modules folder.
+ pub fn handle_if_in_node_modules(&self, specifier: &Url) -> Option<Url> {
+ // 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.
+ let specifier = resolve_specifier_into_node_modules(specifier, &|path| {
+ self.env.realpath_sync(path)
+ });
+ return Some(specifier);
+ }
+
+ None
+ }
}
fn resolve_bin_entry_value<'a>(
@@ -1771,6 +1679,28 @@ pub fn parse_npm_pkg_name(
Ok((package_name, package_subpath, is_scoped))
}
+/// Resolves a specifier that is pointing into a node_modules folder.
+///
+/// Note: This should be called whenever getting the specifier from
+/// a Module::External(module) reference because that module might
+/// not be fully resolved at the time deno_graph is analyzing it
+/// because the node_modules folder might not exist at that time.
+pub fn resolve_specifier_into_node_modules(
+ specifier: &Url,
+ canonicalize: &impl Fn(&Path) -> std::io::Result<PathBuf>,
+) -> Url {
+ deno_path_util::url_to_file_path(specifier)
+ .ok()
+ // this path might not exist at the time the graph is being created
+ // because the node_modules folder might not yet exist
+ .and_then(|path| {
+ deno_path_util::canonicalize_path_maybe_not_exists(&path, canonicalize)
+ .ok()
+ })
+ .and_then(|path| deno_path_util::url_from_file_path(&path).ok())
+ .unwrap_or_else(|| specifier.clone())
+}
+
fn pattern_key_compare(a: &str, b: &str) -> i32 {
let a_pattern_index = a.find('*');
let b_pattern_index = b.find('*');
@@ -1833,6 +1763,17 @@ fn get_module_name_from_builtin_node_module_specifier(
Some(specifier)
}
+/// Node is more lenient joining paths than the url crate is,
+/// so this function handles that.
+fn node_join_url(url: &Url, path: &str) -> Result<Url, url::ParseError> {
+ if let Some(suffix) = path.strip_prefix(".//") {
+ // specifier had two leading slashes
+ url.join(&format!("./{}", suffix))
+ } else {
+ url.join(path)
+ }
+}
+
#[cfg(test)]
mod tests {
use serde_json::json;