From 3bf147fe287ac779b20d318daba56b336f356adf Mon Sep 17 00:00:00 2001 From: David Sherret Date: Thu, 25 Jul 2024 19:08:14 -0400 Subject: [PATCH] refactor: decouple node resolution from deno_core (#24724) --- Cargo.lock | 22 +++ Cargo.toml | 2 + cli/Cargo.toml | 1 + cli/args/mod.rs | 4 +- cli/factory.rs | 7 +- cli/lsp/analysis.rs | 2 +- cli/lsp/resolver.rs | 10 +- cli/module_loader.rs | 2 +- cli/node.rs | 12 +- cli/npm/byonm.rs | 71 ++++--- cli/npm/managed/mod.rs | 37 ++-- cli/npm/managed/resolvers/common.rs | 2 +- cli/npm/managed/resolvers/global.rs | 6 +- cli/npm/managed/resolvers/local.rs | 8 +- cli/npm/mod.rs | 8 +- cli/resolver.rs | 24 +-- cli/standalone/mod.rs | 75 +++---- cli/tools/registry/pm.rs | 2 +- cli/tsc/mod.rs | 10 +- cli/worker.rs | 23 ++- ext/fs/clippy.toml | 38 ++-- ext/fs/sync.rs | 62 ------ ext/node/Cargo.toml | 3 +- ext/node/global.rs | 2 +- ext/node/lib.rs | 161 ++++++++++----- ext/node/ops/require.rs | 17 +- ext/node/ops/worker_threads.rs | 14 +- ext/node/path.rs | 50 ----- ext/node/polyfill.rs | 14 -- ext/node_resolver/Cargo.toml | 32 +++ ext/node_resolver/README.md | 6 + ext/{node => node_resolver}/analyze.rs | 63 +++--- ext/node_resolver/clippy.toml | 48 +++++ ext/node_resolver/env.rs | 39 ++++ ext/{node => node_resolver}/errors.rs | 32 +-- ext/node_resolver/lib.rs | 26 +++ ext/node_resolver/npm.rs | 41 ++++ ext/{node => node_resolver}/package_json.rs | 25 +-- ext/node_resolver/path.rs | 142 ++++++++++++++ ext/{node => node_resolver}/resolution.rs | 207 ++++++++++---------- ext/node_resolver/sync.rs | 86 ++++++++ runtime/Cargo.toml | 1 + runtime/snapshot.rs | 2 +- runtime/web_worker.rs | 8 +- runtime/worker.rs | 10 +- 45 files changed, 939 insertions(+), 518 deletions(-) delete mode 100644 ext/node/path.rs create mode 100644 ext/node_resolver/Cargo.toml create mode 100644 ext/node_resolver/README.md rename ext/{node => node_resolver}/analyze.rs (92%) create mode 100644 ext/node_resolver/clippy.toml create mode 100644 ext/node_resolver/env.rs rename ext/{node => node_resolver}/errors.rs (97%) create mode 100644 ext/node_resolver/lib.rs create mode 100644 ext/node_resolver/npm.rs rename ext/{node => node_resolver}/package_json.rs (72%) create mode 100644 ext/node_resolver/path.rs rename ext/{node => node_resolver}/resolution.rs (92%) create mode 100644 ext/node_resolver/sync.rs diff --git a/Cargo.lock b/Cargo.lock index 0191662144..4ff45842d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1167,6 +1167,7 @@ dependencies = [ "monch", "napi_sym", "nix 0.26.2", + "node_resolver", "notify", "once_cell", "open", @@ -1767,6 +1768,7 @@ dependencies = [ "libz-sys", "md-5", "md4", + "node_resolver", "num-bigint", "num-bigint-dig", "num-integer", @@ -1908,6 +1910,7 @@ dependencies = [ "log", "netif", "nix 0.26.2", + "node_resolver", "notify", "ntapi", "once_cell", @@ -4341,6 +4344,25 @@ dependencies = [ "libc", ] +[[package]] +name = "node_resolver" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "deno_media_type", + "deno_package_json", + "futures", + "lazy-regex", + "once_cell", + "path-clean", + "regex", + "serde_json", + "thiserror", + "tokio", + "url", +] + [[package]] name = "nom" version = "5.1.3" diff --git a/Cargo.toml b/Cargo.toml index 1e9f53e466..3902a028a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ members = [ "ext/napi", "ext/net", "ext/node", + "ext/node_resolver", "ext/url", "ext/web", "ext/webgpu", @@ -83,6 +84,7 @@ deno_webgpu = { version = "0.129.0", path = "./ext/webgpu" } deno_webidl = { version = "0.162.0", path = "./ext/webidl" } deno_websocket = { version = "0.167.0", path = "./ext/websocket" } deno_webstorage = { version = "0.157.0", path = "./ext/webstorage" } +node_resolver = { version = "0.1.0", path = "./ext/node_resolver" } aes = "=0.8.3" anyhow = "1.0.57" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index a19dcbe3d1..cda410c631 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -80,6 +80,7 @@ deno_task_shell = "=0.17.0" deno_terminal.workspace = true eszip = "=0.72.2" napi_sym.workspace = true +node_resolver.workspace = true async-trait.workspace = true base32.workspace = true diff --git a/cli/args/mod.rs b/cli/args/mod.rs index aea6ed8a8d..ea79aaa464 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -820,9 +820,7 @@ impl CliOptions { WorkspaceDiscoverOptions { fs: Default::default(), // use real fs deno_json_cache: None, - pkg_json_cache: Some( - &deno_runtime::deno_node::PackageJsonThreadLocalCache, - ), + pkg_json_cache: Some(&node_resolver::PackageJsonThreadLocalCache), workspace_cache: None, config_parse_options, additional_config_file_names, diff --git a/cli/factory.rs b/cli/factory.rs index aeab3cbc45..3e618e2390 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -62,13 +62,14 @@ use deno_core::futures::FutureExt; use deno_core::FeatureChecker; use deno_runtime::deno_fs; -use deno_runtime::deno_node::analyze::NodeCodeTranslator; +use deno_runtime::deno_node::DenoFsNodeResolverEnv; use deno_runtime::deno_node::NodeResolver; use deno_runtime::deno_tls::rustls::RootCertStore; use deno_runtime::deno_tls::RootCertStoreProvider; use deno_runtime::deno_web::BlobStore; use deno_runtime::inspector_server::InspectorServer; use log::warn; +use node_resolver::analyze::NodeCodeTranslator; use once_cell::sync::OnceCell; use std::future::Future; use std::sync::Arc; @@ -553,7 +554,7 @@ impl CliFactory { .get_or_try_init_async( async { Ok(Arc::new(NodeResolver::new( - self.fs().clone(), + DenoFsNodeResolverEnv::new(self.fs().clone()), self.npm_resolver().await?.clone().into_npm_resolver(), ))) } @@ -577,7 +578,7 @@ impl CliFactory { Ok(Arc::new(NodeCodeTranslator::new( cjs_esm_analyzer, - self.fs().clone(), + DenoFsNodeResolverEnv::new(self.fs().clone()), self.node_resolver().await?.clone(), self.npm_resolver().await?.clone().into_npm_resolver(), ))) diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs index 97730ac7ea..ec8bd4a28f 100644 --- a/cli/lsp/analysis.rs +++ b/cli/lsp/analysis.rs @@ -23,7 +23,6 @@ use deno_core::serde::Serialize; use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::ModuleSpecifier; -use deno_runtime::deno_node::NpmResolver; use deno_runtime::deno_node::PathClean; use deno_semver::jsr::JsrPackageNvReference; use deno_semver::jsr::JsrPackageReqReference; @@ -34,6 +33,7 @@ use deno_semver::package::PackageReq; use deno_semver::package::PackageReqReference; use deno_semver::Version; use import_map::ImportMap; +use node_resolver::NpmResolver; use once_cell::sync::Lazy; use regex::Regex; use std::cmp::Ordering; diff --git a/cli/lsp/resolver.rs b/cli/lsp/resolver.rs index bdfd5fd3ee..d6fc3096c4 100644 --- a/cli/lsp/resolver.rs +++ b/cli/lsp/resolver.rs @@ -35,11 +35,7 @@ use deno_graph::GraphImport; use deno_graph::ModuleSpecifier; use deno_npm::NpmSystemInfo; use deno_runtime::deno_fs; -use deno_runtime::deno_node::errors::ClosestPkgJsonError; -use deno_runtime::deno_node::NodeResolution; -use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::deno_node::NodeResolver; -use deno_runtime::deno_node::NpmResolver; use deno_runtime::deno_node::PackageJson; use deno_runtime::fs_util::specifier_to_file_path; use deno_semver::jsr::JsrPackageReqReference; @@ -47,6 +43,10 @@ use deno_semver::npm::NpmPackageReqReference; use deno_semver::package::PackageNv; use deno_semver::package::PackageReq; use indexmap::IndexMap; +use node_resolver::errors::ClosestPkgJsonError; +use node_resolver::NodeResolution; +use node_resolver::NodeResolutionMode; +use node_resolver::NpmResolver; use std::borrow::Cow; use std::collections::BTreeMap; use std::collections::BTreeSet; @@ -496,7 +496,7 @@ fn create_node_resolver( let npm_resolver = npm_resolver?; let fs = Arc::new(deno_fs::RealFs); let node_resolver_inner = Arc::new(NodeResolver::new( - fs.clone(), + deno_runtime::deno_node::DenoFsNodeResolverEnv::new(fs.clone()), npm_resolver.clone().into_npm_resolver(), )); Some(Arc::new(CliNodeResolver::new( diff --git a/cli/module_loader.rs b/cli/module_loader.rs index 2e047d36d3..bda4e58d86 100644 --- a/cli/module_loader.rs +++ b/cli/module_loader.rs @@ -64,9 +64,9 @@ use deno_graph::Module; use deno_graph::ModuleGraph; use deno_graph::Resolution; use deno_runtime::code_cache; -use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::deno_permissions::PermissionsContainer; use deno_semver::npm::NpmPackageReqReference; +use node_resolver::NodeResolutionMode; pub async fn load_top_level_deps(factory: &CliFactory) -> Result<(), AnyError> { let npm_resolver = factory.npm_resolver().await?; diff --git a/cli/node.rs b/cli/node.rs index 5ecbacdc72..0fd18e2991 100644 --- a/cli/node.rs +++ b/cli/node.rs @@ -6,10 +6,11 @@ use deno_ast::MediaType; use deno_ast::ModuleSpecifier; use deno_core::error::AnyError; use deno_runtime::deno_fs; -use deno_runtime::deno_node::analyze::CjsAnalysis as ExtNodeCjsAnalysis; -use deno_runtime::deno_node::analyze::CjsAnalysisExports; -use deno_runtime::deno_node::analyze::CjsCodeAnalyzer; -use deno_runtime::deno_node::analyze::NodeCodeTranslator; +use deno_runtime::deno_node::DenoFsNodeResolverEnv; +use node_resolver::analyze::CjsAnalysis as ExtNodeCjsAnalysis; +use node_resolver::analyze::CjsAnalysisExports; +use node_resolver::analyze::CjsCodeAnalyzer; +use node_resolver::analyze::NodeCodeTranslator; use serde::Deserialize; use serde::Serialize; @@ -17,7 +18,8 @@ use crate::cache::CacheDBHash; use crate::cache::NodeAnalysisCache; use crate::util::fs::canonicalize_path_maybe_not_exists; -pub type CliNodeCodeTranslator = NodeCodeTranslator; +pub type CliNodeCodeTranslator = + NodeCodeTranslator; /// Resolves a specifier that is pointing into a node_modules folder. /// diff --git a/cli/npm/byonm.rs b/cli/npm/byonm.rs index 86c9badac0..a0f23fc66b 100644 --- a/cli/npm/byonm.rs +++ b/cli/npm/byonm.rs @@ -11,14 +11,18 @@ use deno_core::error::AnyError; use deno_core::serde_json; use deno_package_json::PackageJsonDepValue; use deno_runtime::deno_fs::FileSystem; -use deno_runtime::deno_node::errors::PackageFolderResolveError; -use deno_runtime::deno_node::errors::PackageFolderResolveIoError; -use deno_runtime::deno_node::errors::PackageNotFoundError; -use deno_runtime::deno_node::load_pkg_json; +use deno_runtime::deno_node::DenoPkgJsonFsAdapter; use deno_runtime::deno_node::NodePermissions; -use deno_runtime::deno_node::NpmResolver; +use deno_runtime::deno_node::NodeRequireResolver; +use deno_runtime::deno_node::NpmProcessStateProvider; use deno_runtime::deno_node::PackageJson; use deno_semver::package::PackageReq; +use node_resolver::errors::PackageFolderResolveError; +use node_resolver::errors::PackageFolderResolveIoError; +use node_resolver::errors::PackageJsonLoadError; +use node_resolver::errors::PackageNotFoundError; +use node_resolver::load_pkg_json; +use node_resolver::NpmResolver; use crate::args::NpmProcessState; use crate::args::NpmProcessStateKind; @@ -49,6 +53,15 @@ pub struct ByonmCliNpmResolver { root_node_modules_dir: Option, } +impl ByonmCliNpmResolver { + fn load_pkg_json( + &self, + path: &Path, + ) -> Result>, PackageJsonLoadError> { + load_pkg_json(&DenoPkgJsonFsAdapter(self.fs.as_ref()), path) + } +} + impl ByonmCliNpmResolver { /// Finds the ancestor package.json that contains the specified dependency. pub fn find_ancestor_package_json_with_dep( @@ -60,9 +73,7 @@ impl ByonmCliNpmResolver { let mut current_folder = referrer_path.parent()?; loop { let pkg_json_path = current_folder.join("package.json"); - if let Ok(Some(pkg_json)) = - load_pkg_json(self.fs.as_ref(), &pkg_json_path) - { + if let Ok(Some(pkg_json)) = self.load_pkg_json(&pkg_json_path) { if let Some(deps) = &pkg_json.dependencies { if deps.contains_key(dep_name) { return Some(pkg_json); @@ -119,9 +130,7 @@ impl ByonmCliNpmResolver { let mut current_path = file_path.as_path(); while let Some(dir_path) = current_path.parent() { let package_json_path = dir_path.join("package.json"); - if let Some(pkg_json) = - load_pkg_json(self.fs.as_ref(), &package_json_path)? - { + if let Some(pkg_json) = self.load_pkg_json(&package_json_path)? { if let Some(alias) = resolve_alias_from_pkg_json(req, pkg_json.as_ref()) { @@ -136,9 +145,7 @@ impl ByonmCliNpmResolver { if let Some(root_node_modules_dir) = &self.root_node_modules_dir { let root_pkg_json_path = root_node_modules_dir.parent().unwrap().join("package.json"); - if let Some(pkg_json) = - load_pkg_json(self.fs.as_ref(), &root_pkg_json_path)? - { + if let Some(pkg_json) = self.load_pkg_json(&root_pkg_json_path)? { if let Some(alias) = resolve_alias_from_pkg_json(req, pkg_json.as_ref()) { return Ok((pkg_json, alias)); @@ -158,17 +165,6 @@ impl ByonmCliNpmResolver { } impl NpmResolver for ByonmCliNpmResolver { - fn get_npm_process_state(&self) -> String { - serde_json::to_string(&NpmProcessState { - kind: NpmProcessStateKind::Byonm, - local_node_modules_path: self - .root_node_modules_dir - .as_ref() - .map(|p| p.to_string_lossy().to_string()), - }) - .unwrap() - } - fn resolve_package_folder_from_package( &self, name: &str, @@ -226,7 +222,9 @@ impl NpmResolver for ByonmCliNpmResolver { .to_ascii_lowercase() .contains("/node_modules/") } +} +impl NodeRequireResolver for ByonmCliNpmResolver { fn ensure_read_permission( &self, permissions: &mut dyn NodePermissions, @@ -242,11 +240,34 @@ impl NpmResolver for ByonmCliNpmResolver { } } +impl NpmProcessStateProvider for ByonmCliNpmResolver { + fn get_npm_process_state(&self) -> String { + serde_json::to_string(&NpmProcessState { + kind: NpmProcessStateKind::Byonm, + local_node_modules_path: self + .root_node_modules_dir + .as_ref() + .map(|p| p.to_string_lossy().to_string()), + }) + .unwrap() + } +} + impl CliNpmResolver for ByonmCliNpmResolver { fn into_npm_resolver(self: Arc) -> Arc { self } + fn into_require_resolver(self: Arc) -> Arc { + self + } + + fn into_process_state_provider( + self: Arc, + ) -> Arc { + self + } + fn clone_snapshotted(&self) -> Arc { Arc::new(Self { fs: self.fs.clone(), diff --git a/cli/npm/managed/mod.rs b/cli/npm/managed/mod.rs index 602733cabd..1561d39699 100644 --- a/cli/npm/managed/mod.rs +++ b/cli/npm/managed/mod.rs @@ -20,12 +20,14 @@ use deno_npm::NpmPackageId; use deno_npm::NpmResolutionPackage; use deno_npm::NpmSystemInfo; use deno_runtime::deno_fs::FileSystem; -use deno_runtime::deno_node::errors::PackageFolderResolveError; -use deno_runtime::deno_node::errors::PackageFolderResolveIoError; use deno_runtime::deno_node::NodePermissions; -use deno_runtime::deno_node::NpmResolver; +use deno_runtime::deno_node::NodeRequireResolver; +use deno_runtime::deno_node::NpmProcessStateProvider; use deno_semver::package::PackageNv; use deno_semver::package::PackageReq; +use node_resolver::errors::PackageFolderResolveError; +use node_resolver::errors::PackageFolderResolveIoError; +use node_resolver::NpmResolver; use resolution::AddPkgReqsResult; use crate::args::CliLockfile; @@ -531,14 +533,6 @@ fn npm_process_state( } impl NpmResolver for ManagedCliNpmResolver { - /// Gets the state of npm for the process. - fn get_npm_process_state(&self) -> String { - npm_process_state( - self.resolution.serialized_valid_snapshot(), - self.fs_resolver.node_modules_path().map(|p| p.as_path()), - ) - } - fn resolve_package_folder_from_package( &self, name: &str, @@ -563,7 +557,9 @@ impl NpmResolver for ManagedCliNpmResolver { debug_assert!(root_dir_url.as_str().ends_with('/')); specifier.as_ref().starts_with(root_dir_url.as_str()) } +} +impl NodeRequireResolver for ManagedCliNpmResolver { fn ensure_read_permission( &self, permissions: &mut dyn NodePermissions, @@ -573,11 +569,30 @@ impl NpmResolver for ManagedCliNpmResolver { } } +impl NpmProcessStateProvider for ManagedCliNpmResolver { + fn get_npm_process_state(&self) -> String { + npm_process_state( + self.resolution.serialized_valid_snapshot(), + self.fs_resolver.node_modules_path().map(|p| p.as_path()), + ) + } +} + impl CliNpmResolver for ManagedCliNpmResolver { fn into_npm_resolver(self: Arc) -> Arc { self } + fn into_require_resolver(self: Arc) -> Arc { + self + } + + fn into_process_state_provider( + self: Arc, + ) -> Arc { + self + } + fn clone_snapshotted(&self) -> Arc { // create a new snapshotted npm resolution and resolver let npm_resolution = Arc::new(NpmResolution::new( diff --git a/cli/npm/managed/resolvers/common.rs b/cli/npm/managed/resolvers/common.rs index dffa1b75c0..170dc2ae6b 100644 --- a/cli/npm/managed/resolvers/common.rs +++ b/cli/npm/managed/resolvers/common.rs @@ -18,8 +18,8 @@ use deno_npm::NpmPackageCacheFolderId; use deno_npm::NpmPackageId; use deno_npm::NpmResolutionPackage; use deno_runtime::deno_fs::FileSystem; -use deno_runtime::deno_node::errors::PackageFolderResolveError; use deno_runtime::deno_node::NodePermissions; +use node_resolver::errors::PackageFolderResolveError; use crate::npm::managed::cache::TarballCache; diff --git a/cli/npm/managed/resolvers/global.rs b/cli/npm/managed/resolvers/global.rs index e7a57fc23f..7f8f285f3e 100644 --- a/cli/npm/managed/resolvers/global.rs +++ b/cli/npm/managed/resolvers/global.rs @@ -14,10 +14,10 @@ use deno_npm::NpmPackageCacheFolderId; use deno_npm::NpmPackageId; use deno_npm::NpmSystemInfo; use deno_runtime::deno_fs::FileSystem; -use deno_runtime::deno_node::errors::PackageFolderResolveError; -use deno_runtime::deno_node::errors::PackageNotFoundError; -use deno_runtime::deno_node::errors::ReferrerNotFoundError; use deno_runtime::deno_node::NodePermissions; +use node_resolver::errors::PackageFolderResolveError; +use node_resolver::errors::PackageNotFoundError; +use node_resolver::errors::ReferrerNotFoundError; use super::super::cache::NpmCache; use super::super::cache::TarballCache; diff --git a/cli/npm/managed/resolvers/local.rs b/cli/npm/managed/resolvers/local.rs index cda78548b2..b741fd15da 100644 --- a/cli/npm/managed/resolvers/local.rs +++ b/cli/npm/managed/resolvers/local.rs @@ -32,12 +32,12 @@ use deno_npm::NpmPackageId; use deno_npm::NpmResolutionPackage; use deno_npm::NpmSystemInfo; use deno_runtime::deno_fs; -use deno_runtime::deno_node::errors::PackageFolderResolveError; -use deno_runtime::deno_node::errors::PackageFolderResolveIoError; -use deno_runtime::deno_node::errors::PackageNotFoundError; -use deno_runtime::deno_node::errors::ReferrerNotFoundError; use deno_runtime::deno_node::NodePermissions; use deno_semver::package::PackageNv; +use node_resolver::errors::PackageFolderResolveError; +use node_resolver::errors::PackageFolderResolveIoError; +use node_resolver::errors::PackageNotFoundError; +use node_resolver::errors::ReferrerNotFoundError; use serde::Deserialize; use serde::Serialize; diff --git a/cli/npm/mod.rs b/cli/npm/mod.rs index 8ae81de246..f883883aad 100644 --- a/cli/npm/mod.rs +++ b/cli/npm/mod.rs @@ -13,10 +13,12 @@ use deno_ast::ModuleSpecifier; use deno_core::error::AnyError; use deno_core::serde_json; use deno_npm::registry::NpmPackageInfo; -use deno_runtime::deno_node::NpmResolver; +use deno_runtime::deno_node::NodeRequireResolver; +use deno_runtime::deno_node::NpmProcessStateProvider; use deno_runtime::deno_permissions::PermissionsContainer; use deno_semver::package::PackageNv; use deno_semver::package::PackageReq; +use node_resolver::NpmResolver; use crate::args::npm_registry_url; use crate::file_fetcher::FileFetcher; @@ -63,6 +65,10 @@ pub enum InnerCliNpmResolverRef<'a> { pub trait CliNpmResolver: NpmResolver { fn into_npm_resolver(self: Arc) -> Arc; + fn into_require_resolver(self: Arc) -> Arc; + fn into_process_state_provider( + self: Arc, + ) -> Arc; fn clone_snapshotted(&self) -> Arc; diff --git a/cli/resolver.rs b/cli/resolver.rs index 5296b42b8d..18804c0253 100644 --- a/cli/resolver.rs +++ b/cli/resolver.rs @@ -23,23 +23,23 @@ use deno_npm::resolution::NpmResolutionError; use deno_package_json::PackageJsonDepValue; use deno_runtime::deno_fs; use deno_runtime::deno_fs::FileSystem; -use deno_runtime::deno_node::errors::ClosestPkgJsonError; -use deno_runtime::deno_node::errors::NodeResolveError; -use deno_runtime::deno_node::errors::NodeResolveErrorKind; -use deno_runtime::deno_node::errors::PackageFolderResolveErrorKind; -use deno_runtime::deno_node::errors::PackageFolderResolveIoError; -use deno_runtime::deno_node::errors::PackageNotFoundError; -use deno_runtime::deno_node::errors::PackageResolveErrorKind; -use deno_runtime::deno_node::errors::UrlToNodeResolutionError; use deno_runtime::deno_node::is_builtin_node_module; -use deno_runtime::deno_node::NodeModuleKind; -use deno_runtime::deno_node::NodeResolution; -use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::deno_node::NodeResolver; -use deno_runtime::deno_node::PackageJson; use deno_runtime::fs_util::specifier_to_file_path; use deno_semver::npm::NpmPackageReqReference; 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::UrlToNodeResolutionError; +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; diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index c91f3bec90..1538807f1e 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -5,6 +5,42 @@ #![allow(dead_code)] #![allow(unused_imports)] +use deno_ast::MediaType; +use deno_config::workspace::MappedResolution; +use deno_config::workspace::MappedResolutionError; +use deno_config::workspace::WorkspaceResolver; +use deno_core::anyhow::Context; +use deno_core::error::generic_error; +use deno_core::error::type_error; +use deno_core::error::AnyError; +use deno_core::futures::FutureExt; +use deno_core::v8_set_flags; +use deno_core::FeatureChecker; +use deno_core::ModuleLoader; +use deno_core::ModuleSourceCode; +use deno_core::ModuleSpecifier; +use deno_core::ModuleType; +use deno_core::RequestedModuleType; +use deno_core::ResolutionKind; +use deno_npm::npm_rc::ResolvedNpmRc; +use deno_package_json::PackageJsonDepValue; +use deno_runtime::deno_fs; +use deno_runtime::deno_node::NodeResolver; +use deno_runtime::deno_permissions::Permissions; +use deno_runtime::deno_permissions::PermissionsContainer; +use deno_runtime::deno_tls::rustls::RootCertStore; +use deno_runtime::deno_tls::RootCertStoreProvider; +use deno_runtime::WorkerExecutionMode; +use deno_runtime::WorkerLogLevel; +use deno_semver::npm::NpmPackageReqReference; +use eszip::EszipRelativeFileBaseUrl; +use import_map::parse_from_json; +use node_resolver::analyze::NodeCodeTranslator; +use node_resolver::NodeResolutionMode; +use std::borrow::Cow; +use std::rc::Rc; +use std::sync::Arc; + use crate::args::create_default_npmrc; use crate::args::get_root_cert_store; use crate::args::npm_pkg_req_ref_to_binary_command; @@ -33,41 +69,6 @@ use crate::worker::CliMainWorkerFactory; use crate::worker::CliMainWorkerOptions; use crate::worker::ModuleLoaderAndSourceMapGetter; use crate::worker::ModuleLoaderFactory; -use deno_ast::MediaType; -use deno_config::workspace::MappedResolution; -use deno_config::workspace::MappedResolutionError; -use deno_config::workspace::WorkspaceResolver; -use deno_core::anyhow::Context; -use deno_core::error::generic_error; -use deno_core::error::type_error; -use deno_core::error::AnyError; -use deno_core::futures::FutureExt; -use deno_core::v8_set_flags; -use deno_core::FeatureChecker; -use deno_core::ModuleLoader; -use deno_core::ModuleSourceCode; -use deno_core::ModuleSpecifier; -use deno_core::ModuleType; -use deno_core::RequestedModuleType; -use deno_core::ResolutionKind; -use deno_npm::npm_rc::ResolvedNpmRc; -use deno_package_json::PackageJsonDepValue; -use deno_runtime::deno_fs; -use deno_runtime::deno_node::analyze::NodeCodeTranslator; -use deno_runtime::deno_node::NodeResolutionMode; -use deno_runtime::deno_node::NodeResolver; -use deno_runtime::deno_permissions::Permissions; -use deno_runtime::deno_permissions::PermissionsContainer; -use deno_runtime::deno_tls::rustls::RootCertStore; -use deno_runtime::deno_tls::RootCertStoreProvider; -use deno_runtime::WorkerExecutionMode; -use deno_runtime::WorkerLogLevel; -use deno_semver::npm::NpmPackageReqReference; -use eszip::EszipRelativeFileBaseUrl; -use import_map::parse_from_json; -use std::borrow::Cow; -use std::rc::Rc; -use std::sync::Arc; pub mod binary; mod file_system; @@ -549,7 +550,7 @@ pub async fn run( let has_node_modules_dir = npm_resolver.root_node_modules_path().is_some(); let node_resolver = Arc::new(NodeResolver::new( - fs.clone(), + deno_runtime::deno_node::DenoFsNodeResolverEnv::new(fs.clone()), npm_resolver.clone().into_npm_resolver(), )); let cjs_resolutions = Arc::new(CjsResolutionStore::default()); @@ -559,7 +560,7 @@ pub async fn run( CliCjsCodeAnalyzer::new(node_analysis_cache, fs.clone()); let node_code_translator = Arc::new(NodeCodeTranslator::new( cjs_esm_code_analyzer, - fs.clone(), + deno_runtime::deno_node::DenoFsNodeResolverEnv::new(fs.clone()), node_resolver.clone(), npm_resolver.clone().into_npm_resolver(), )); diff --git a/cli/tools/registry/pm.rs b/cli/tools/registry/pm.rs index 233e682408..2986c1c2a3 100644 --- a/cli/tools/registry/pm.rs +++ b/cli/tools/registry/pm.rs @@ -308,7 +308,7 @@ pub async fn add( .context("Failed to update configuration file")?; // clear the previously cached package.json from memory before reloading it - deno_node::PackageJsonThreadLocalCache::clear(); + node_resolver::PackageJsonThreadLocalCache::clear(); // make a new CliFactory to pick up the updated config file let cli_factory = CliFactory::from_flags(flags); // cache deps diff --git a/cli/tsc/mod.rs b/cli/tsc/mod.rs index 424b5c3d31..ac7fc48e37 100644 --- a/cli/tsc/mod.rs +++ b/cli/tsc/mod.rs @@ -30,14 +30,14 @@ use deno_graph::GraphKind; use deno_graph::Module; use deno_graph::ModuleGraph; use deno_graph::ResolutionResolved; -use deno_runtime::deno_node::errors::NodeJsErrorCode; -use deno_runtime::deno_node::errors::NodeJsErrorCoded; -use deno_runtime::deno_node::NodeModuleKind; -use deno_runtime::deno_node::NodeResolution; -use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::deno_node::NodeResolver; use deno_semver::npm::NpmPackageReqReference; use lsp_types::Url; +use node_resolver::errors::NodeJsErrorCode; +use node_resolver::errors::NodeJsErrorCoded; +use node_resolver::NodeModuleKind; +use node_resolver::NodeResolution; +use node_resolver::NodeResolutionMode; use once_cell::sync::Lazy; use std::borrow::Cow; use std::collections::HashMap; diff --git a/cli/worker.rs b/cli/worker.rs index 0d7e61c50e..8673804ab0 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -22,8 +22,7 @@ use deno_runtime::code_cache; use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel; use deno_runtime::deno_fs; use deno_runtime::deno_node; -use deno_runtime::deno_node::NodeResolution; -use deno_runtime::deno_node::NodeResolutionMode; +use deno_runtime::deno_node::NodeExtInitServices; use deno_runtime::deno_node::NodeResolver; use deno_runtime::deno_permissions::PermissionsContainer; use deno_runtime::deno_tls::RootCertStoreProvider; @@ -40,6 +39,8 @@ use deno_runtime::WorkerExecutionMode; use deno_runtime::WorkerLogLevel; use deno_semver::npm::NpmPackageReqReference; use deno_terminal::colors; +use node_resolver::NodeResolution; +use node_resolver::NodeResolutionMode; use tokio::select; use crate::args::CliLockfile; @@ -144,7 +145,17 @@ struct SharedWorkerState { } impl SharedWorkerState { - // Currently empty + pub fn create_node_init_services(&self) -> NodeExtInitServices { + NodeExtInitServices { + node_require_resolver: self.npm_resolver.clone().into_require_resolver(), + node_resolver: self.node_resolver.clone(), + npm_process_state_provider: self + .npm_resolver + .clone() + .into_process_state_provider(), + npm_resolver: self.npm_resolver.clone().into_npm_resolver(), + } + } } pub struct CliMainWorker { @@ -599,8 +610,7 @@ impl CliMainWorkerFactory { strace_ops: shared.options.strace_ops.clone(), module_loader, fs: shared.fs.clone(), - node_resolver: Some(shared.node_resolver.clone()), - npm_resolver: Some(shared.npm_resolver.clone().into_npm_resolver()), + node_services: Some(shared.create_node_init_services()), get_error_class_fn: Some(&errors::get_error_class_name), cache_storage_dir, origin_storage_dir, @@ -793,8 +803,7 @@ fn create_web_worker_callback( format_js_error_fn: Some(Arc::new(format_js_error)), module_loader, fs: shared.fs.clone(), - node_resolver: Some(shared.node_resolver.clone()), - npm_resolver: Some(shared.npm_resolver.clone().into_npm_resolver()), + node_services: Some(shared.create_node_init_services()), worker_type: args.worker_type, maybe_inspector_server, get_error_class_fn: Some(&errors::get_error_class_name), diff --git a/ext/fs/clippy.toml b/ext/fs/clippy.toml index 023769214b..943d28c6df 100644 --- a/ext/fs/clippy.toml +++ b/ext/fs/clippy.toml @@ -1,24 +1,24 @@ disallowed-methods = [ { path = "std::env::current_dir", reason = "File system operations should be done using FileSystem trait" }, - { path = "std::path::Path::canonicalize", reason = "File system operations should be done using NodeFs trait" }, - { path = "std::path::Path::is_dir", reason = "File system operations should be done using NodeFs trait" }, - { path = "std::path::Path::is_file", reason = "File system operations should be done using NodeFs trait" }, - { path = "std::path::Path::is_symlink", reason = "File system operations should be done using NodeFs trait" }, - { path = "std::path::Path::metadata", reason = "File system operations should be done using NodeFs trait" }, - { path = "std::path::Path::read_dir", reason = "File system operations should be done using NodeFs trait" }, - { path = "std::path::Path::read_link", reason = "File system operations should be done using NodeFs trait" }, - { path = "std::path::Path::symlink_metadata", reason = "File system operations should be done using NodeFs trait" }, - { path = "std::path::Path::try_exists", reason = "File system operations should be done using NodeFs trait" }, - { path = "std::path::PathBuf::exists", reason = "File system operations should be done using NodeFs trait" }, - { path = "std::path::PathBuf::canonicalize", reason = "File system operations should be done using NodeFs trait" }, - { path = "std::path::PathBuf::is_dir", reason = "File system operations should be done using NodeFs trait" }, - { path = "std::path::PathBuf::is_file", reason = "File system operations should be done using NodeFs trait" }, - { path = "std::path::PathBuf::is_symlink", reason = "File system operations should be done using NodeFs trait" }, - { path = "std::path::PathBuf::metadata", reason = "File system operations should be done using NodeFs trait" }, - { path = "std::path::PathBuf::read_dir", reason = "File system operations should be done using NodeFs trait" }, - { path = "std::path::PathBuf::read_link", reason = "File system operations should be done using NodeFs trait" }, - { path = "std::path::PathBuf::symlink_metadata", reason = "File system operations should be done using NodeFs trait" }, - { path = "std::path::PathBuf::try_exists", reason = "File system operations should be done using NodeFs trait" }, + { path = "std::path::Path::canonicalize", reason = "File system operations should be done using FileSystem trait" }, + { path = "std::path::Path::is_dir", reason = "File system operations should be done using FileSystem trait" }, + { path = "std::path::Path::is_file", reason = "File system operations should be done using FileSystem trait" }, + { path = "std::path::Path::is_symlink", reason = "File system operations should be done using FileSystem trait" }, + { path = "std::path::Path::metadata", reason = "File system operations should be done using FileSystem trait" }, + { path = "std::path::Path::read_dir", reason = "File system operations should be done using FileSystem trait" }, + { path = "std::path::Path::read_link", reason = "File system operations should be done using FileSystem trait" }, + { path = "std::path::Path::symlink_metadata", reason = "File system operations should be done using FileSystem trait" }, + { path = "std::path::Path::try_exists", reason = "File system operations should be done using FileSystem trait" }, + { path = "std::path::PathBuf::exists", reason = "File system operations should be done using FileSystem trait" }, + { path = "std::path::PathBuf::canonicalize", reason = "File system operations should be done using FileSystem trait" }, + { path = "std::path::PathBuf::is_dir", reason = "File system operations should be done using FileSystem trait" }, + { path = "std::path::PathBuf::is_file", reason = "File system operations should be done using FileSystem trait" }, + { path = "std::path::PathBuf::is_symlink", reason = "File system operations should be done using FileSystem trait" }, + { path = "std::path::PathBuf::metadata", reason = "File system operations should be done using FileSystem trait" }, + { path = "std::path::PathBuf::read_dir", reason = "File system operations should be done using FileSystem trait" }, + { path = "std::path::PathBuf::read_link", reason = "File system operations should be done using FileSystem trait" }, + { path = "std::path::PathBuf::symlink_metadata", reason = "File system operations should be done using FileSystem trait" }, + { path = "std::path::PathBuf::try_exists", reason = "File system operations should be done using FileSystem trait" }, { path = "std::env::set_current_dir", reason = "File system operations should be done using FileSystem trait" }, { path = "std::env::temp_dir", reason = "File system operations should be done using FileSystem trait" }, { path = "std::fs::canonicalize", reason = "File system operations should be done using FileSystem trait" }, diff --git a/ext/fs/sync.rs b/ext/fs/sync.rs index 83f1f8bc30..6a913f658a 100644 --- a/ext/fs/sync.rs +++ b/ext/fs/sync.rs @@ -6,80 +6,18 @@ pub use inner::*; mod inner { #![allow(clippy::disallowed_types)] - use std::ops::Deref; - use std::ops::DerefMut; pub use std::sync::Arc as MaybeArc; pub use core::marker::Send as MaybeSend; pub use core::marker::Sync as MaybeSync; - - pub struct MaybeArcMutexGuard<'lock, T>(std::sync::MutexGuard<'lock, T>); - - impl<'lock, T> Deref for MaybeArcMutexGuard<'lock, T> { - type Target = std::sync::MutexGuard<'lock, T>; - fn deref(&self) -> &std::sync::MutexGuard<'lock, T> { - &self.0 - } - } - - impl<'lock, T> DerefMut for MaybeArcMutexGuard<'lock, T> { - fn deref_mut(&mut self) -> &mut std::sync::MutexGuard<'lock, T> { - &mut self.0 - } - } - - #[derive(Debug)] - pub struct MaybeArcMutex(std::sync::Arc>); - impl MaybeArcMutex { - pub fn new(val: T) -> Self { - Self(std::sync::Arc::new(std::sync::Mutex::new(val))) - } - } - - impl<'lock, T> MaybeArcMutex { - pub fn lock(&'lock self) -> MaybeArcMutexGuard<'lock, T> { - MaybeArcMutexGuard(self.0.lock().unwrap()) - } - } } #[cfg(not(feature = "sync_fs"))] mod inner { - use std::ops::Deref; - use std::ops::DerefMut; pub use std::rc::Rc as MaybeArc; pub trait MaybeSync {} impl MaybeSync for T where T: ?Sized {} pub trait MaybeSend {} impl MaybeSend for T where T: ?Sized {} - - pub struct MaybeArcMutexGuard<'lock, T>(std::cell::RefMut<'lock, T>); - - impl<'lock, T> Deref for MaybeArcMutexGuard<'lock, T> { - type Target = std::cell::RefMut<'lock, T>; - fn deref(&self) -> &std::cell::RefMut<'lock, T> { - &self.0 - } - } - - impl<'lock, T> DerefMut for MaybeArcMutexGuard<'lock, T> { - fn deref_mut(&mut self) -> &mut std::cell::RefMut<'lock, T> { - &mut self.0 - } - } - - #[derive(Debug)] - pub struct MaybeArcMutex(std::rc::Rc>); - impl MaybeArcMutex { - pub fn new(val: T) -> Self { - Self(std::rc::Rc::new(std::cell::RefCell::new(val))) - } - } - - impl<'lock, T> MaybeArcMutex { - pub fn lock(&'lock self) -> MaybeArcMutexGuard<'lock, T> { - MaybeArcMutexGuard(self.0.borrow_mut()) - } - } } diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml index ed168eace3..00afb64eb8 100644 --- a/ext/node/Cargo.toml +++ b/ext/node/Cargo.toml @@ -14,7 +14,7 @@ description = "Node compatibility for Deno" path = "lib.rs" [features] -sync_fs = ["deno_package_json/sync"] +sync_fs = ["deno_package_json/sync", "node_resolver/sync"] [dependencies] aead-gcm-stream = "0.1" @@ -55,6 +55,7 @@ libc.workspace = true libz-sys.workspace = true md-5 = { version = "0.10.5", features = ["oid"] } md4 = "0.10.2" +node_resolver.workspace = true num-bigint.workspace = true num-bigint-dig = "0.8.2" num-integer = "0.1.45" diff --git a/ext/node/global.rs b/ext/node/global.rs index 7f901fd035..618e684948 100644 --- a/ext/node/global.rs +++ b/ext/node/global.rs @@ -6,7 +6,7 @@ use deno_core::v8; use deno_core::v8::GetPropertyNamesArgs; use deno_core::v8::MapFnTo; -use crate::resolution::NodeResolverRc; +use crate::NodeResolverRc; // NOTE(bartlomieju): somehow calling `.map_fn_to()` multiple times on a function // returns two different pointers. That shouldn't be the case as `.map_fn_to()` diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 21af5a094f..2c86505779 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -5,7 +5,6 @@ use std::collections::HashSet; use std::path::Path; -use std::path::PathBuf; use deno_core::error::AnyError; use deno_core::located_script_name; @@ -15,24 +14,20 @@ use deno_core::url::Url; use deno_core::v8; use deno_core::v8::ExternalReference; use deno_core::JsRuntime; -use deno_core::ModuleSpecifier; use deno_core::OpState; use deno_fs::sync::MaybeSend; use deno_fs::sync::MaybeSync; +use node_resolver::NpmResolverRc; use once_cell::sync::Lazy; extern crate libz_sys as zlib; -pub mod analyze; -pub mod errors; mod global; mod ops; -mod package_json; -mod path; mod polyfill; -mod resolution; pub use deno_package_json::PackageJson; +pub use node_resolver::PathClean; pub use ops::ipc::ChildPipeFd; pub use ops::ipc::IpcJsonStreamResource; use ops::vm; @@ -40,17 +35,9 @@ pub use ops::vm::create_v8_context; pub use ops::vm::init_global_template; pub use ops::vm::ContextInitMode; pub use ops::vm::VM_CONTEXT_INDEX; -pub use package_json::load_pkg_json; -pub use package_json::PackageJsonThreadLocalCache; -pub use path::PathClean; pub use polyfill::is_builtin_node_module; pub use polyfill::SUPPORTED_BUILTIN_NODE_MODULES; pub use polyfill::SUPPORTED_BUILTIN_NODE_MODULES_WITH_PREFIX; -pub use resolution::NodeModuleKind; -pub use resolution::NodeResolution; -pub use resolution::NodeResolutionMode; -pub use resolution::NodeResolver; -use resolution::NodeResolverRc; use crate::global::global_object_middleware; use crate::global::global_template_middleware; @@ -149,9 +136,12 @@ impl NodePermissions for deno_permissions::PermissionsContainer { } #[allow(clippy::disallowed_types)] -pub type NpmResolverRc = deno_fs::sync::MaybeArc; +pub type NpmProcessStateProviderRc = + deno_fs::sync::MaybeArc; -pub trait NpmResolver: std::fmt::Debug + MaybeSend + MaybeSync { +pub trait NpmProcessStateProvider: + std::fmt::Debug + MaybeSend + MaybeSync +{ /// Gets a string containing the serialized npm state of the process. /// /// This will be set on the `DENO_DONT_USE_INTERNAL_NODE_COMPAT_STATE` environment @@ -161,34 +151,13 @@ pub trait NpmResolver: std::fmt::Debug + MaybeSend + MaybeSync { // This method is only used in the CLI. String::new() } +} - /// Resolves an npm package folder path from an npm package referrer. - fn resolve_package_folder_from_package( - &self, - specifier: &str, - referrer: &ModuleSpecifier, - ) -> Result; - - fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool; - - fn in_npm_package_at_dir_path(&self, path: &Path) -> bool { - let specifier = - match ModuleSpecifier::from_directory_path(path.to_path_buf().clean()) { - Ok(p) => p, - Err(_) => return false, - }; - self.in_npm_package(&specifier) - } - - fn in_npm_package_at_file_path(&self, path: &Path) -> bool { - let specifier = - match ModuleSpecifier::from_file_path(path.to_path_buf().clean()) { - Ok(p) => p, - Err(_) => return false, - }; - self.in_npm_package(&specifier) - } +#[allow(clippy::disallowed_types)] +pub type NodeRequireResolverRc = + deno_fs::sync::MaybeArc; +pub trait NodeRequireResolver: std::fmt::Debug + MaybeSend + MaybeSync { fn ensure_read_permission( &self, permissions: &mut dyn NodePermissions, @@ -223,10 +192,17 @@ fn op_node_is_promise_rejected(value: v8::Local) -> bool { #[op2] #[string] fn op_npm_process_state(state: &mut OpState) -> Result { - let npm_resolver = state.borrow_mut::(); + let npm_resolver = state.borrow_mut::(); Ok(npm_resolver.get_npm_process_state()) } +pub struct NodeExtInitServices { + pub node_require_resolver: NodeRequireResolverRc, + pub node_resolver: NodeResolverRc, + pub npm_process_state_provider: NpmProcessStateProviderRc, + pub npm_resolver: NpmResolverRc, +} + deno_core::extension!(deno_node, deps = [ deno_io, deno_fs ], parameters = [P: NodePermissions], @@ -643,21 +619,17 @@ deno_core::extension!(deno_node, "node:zlib" = "zlib.ts", ], options = { - maybe_node_resolver: Option, - maybe_npm_resolver: Option, + maybe_init: Option, fs: deno_fs::FileSystemRc, }, state = |state, options| { - // you should provide both of these or neither - debug_assert_eq!(options.maybe_node_resolver.is_some(), options.maybe_npm_resolver.is_some()); - state.put(options.fs.clone()); - if let Some(node_resolver) = &options.maybe_node_resolver { - state.put(node_resolver.clone()); - } - if let Some(npm_resolver) = &options.maybe_npm_resolver { - state.put(npm_resolver.clone()); + if let Some(init) = &options.maybe_init { + state.put(init.node_require_resolver.clone()); + state.put(init.node_resolver.clone()); + state.put(init.npm_resolver.clone()); + state.put(init.npm_process_state_provider.clone()); } }, global_template_middleware = global_template_middleware, @@ -783,3 +755,84 @@ pub fn load_cjs_module( js_runtime.execute_script(located_script_name!(), source_code)?; Ok(()) } + +pub type NodeResolver = node_resolver::NodeResolver; +#[allow(clippy::disallowed_types)] +pub type NodeResolverRc = + deno_fs::sync::MaybeArc>; + +#[derive(Debug)] +pub struct DenoFsNodeResolverEnv { + fs: deno_fs::FileSystemRc, +} + +impl DenoFsNodeResolverEnv { + pub fn new(fs: deno_fs::FileSystemRc) -> Self { + Self { fs } + } +} + +impl node_resolver::env::NodeResolverEnv for DenoFsNodeResolverEnv { + fn is_builtin_node_module(&self, specifier: &str) -> bool { + is_builtin_node_module(specifier) + } + + fn realpath_sync( + &self, + path: &std::path::Path, + ) -> std::io::Result { + self + .fs + .realpath_sync(path) + .map_err(|err| err.into_io_error()) + } + + fn stat_sync( + &self, + path: &std::path::Path, + ) -> std::io::Result { + self + .fs + .stat_sync(path) + .map(|stat| node_resolver::env::NodeResolverFsStat { + is_file: stat.is_file, + is_dir: stat.is_directory, + is_symlink: stat.is_symlink, + }) + .map_err(|err| err.into_io_error()) + } + + fn exists_sync(&self, path: &std::path::Path) -> bool { + self.fs.exists_sync(path) + } + + fn pkg_json_fs(&self) -> &dyn deno_package_json::fs::DenoPkgJsonFs { + self + } +} + +impl deno_package_json::fs::DenoPkgJsonFs for DenoFsNodeResolverEnv { + fn read_to_string_lossy( + &self, + path: &std::path::Path, + ) -> Result { + self + .fs + .read_text_file_lossy_sync(path, None) + .map_err(|err| err.into_io_error()) + } +} + +pub struct DenoPkgJsonFsAdapter<'a>(pub &'a dyn deno_fs::FileSystem); + +impl<'a> deno_package_json::fs::DenoPkgJsonFs for DenoPkgJsonFsAdapter<'a> { + fn read_to_string_lossy( + &self, + path: &Path, + ) -> Result { + self + .0 + .read_text_file_lossy_sync(path, None) + .map_err(|err| err.into_io_error()) + } +} diff --git a/ext/node/ops/require.rs b/ext/node/ops/require.rs index d03b3dd9c4..d074234c39 100644 --- a/ext/node/ops/require.rs +++ b/ext/node/ops/require.rs @@ -10,16 +10,17 @@ use deno_core::JsRuntimeInspector; use deno_core::ModuleSpecifier; use deno_core::OpState; use deno_fs::FileSystemRc; +use node_resolver::NodeModuleKind; +use node_resolver::NodeResolutionMode; +use node_resolver::REQUIRE_CONDITIONS; use std::cell::RefCell; use std::path::Path; use std::path::PathBuf; use std::rc::Rc; -use crate::resolution; -use crate::resolution::NodeResolverRc; -use crate::NodeModuleKind; use crate::NodePermissions; -use crate::NodeResolutionMode; +use crate::NodeRequireResolverRc; +use crate::NodeResolverRc; use crate::NpmResolverRc; use crate::PackageJson; @@ -30,7 +31,7 @@ fn ensure_read_permission

( where P: NodePermissions + 'static, { - let resolver = state.borrow::().clone(); + let resolver = state.borrow::().clone(); let permissions = state.borrow_mut::

(); resolver.ensure_read_permission(permissions, file_path) } @@ -423,7 +424,7 @@ where exports, Some(&referrer), NodeModuleKind::Cjs, - resolution::REQUIRE_CONDITIONS, + REQUIRE_CONDITIONS, NodeResolutionMode::Execution, )?; Ok(Some(if r.scheme() == "file" { @@ -511,7 +512,7 @@ where exports, Some(&referrer), NodeModuleKind::Cjs, - resolution::REQUIRE_CONDITIONS, + REQUIRE_CONDITIONS, NodeResolutionMode::Execution, )?; Ok(Some(if r.scheme() == "file" { @@ -590,7 +591,7 @@ where Some(&referrer_url), NodeModuleKind::Cjs, Some(&pkg), - resolution::REQUIRE_CONDITIONS, + REQUIRE_CONDITIONS, NodeResolutionMode::Execution, )?; Ok(Some(url_to_file_path_string(&url)?)) diff --git a/ext/node/ops/worker_threads.rs b/ext/node/ops/worker_threads.rs index 182ba0118d..c7ea4c52c2 100644 --- a/ext/node/ops/worker_threads.rs +++ b/ext/node/ops/worker_threads.rs @@ -6,13 +6,13 @@ use deno_core::op2; use deno_core::url::Url; use deno_core::OpState; use deno_fs::FileSystemRc; +use node_resolver::NodeResolution; use std::path::Path; use std::path::PathBuf; -use crate::resolution; -use crate::resolution::NodeResolverRc; use crate::NodePermissions; -use crate::NpmResolverRc; +use crate::NodeRequireResolverRc; +use crate::NodeResolverRc; fn ensure_read_permission

( state: &mut OpState, @@ -21,7 +21,7 @@ fn ensure_read_permission

( where P: NodePermissions + 'static, { - let resolver = state.borrow::().clone(); + let resolver = state.borrow::().clone(); let permissions = state.borrow_mut::

(); resolver.ensure_read_permission(permissions, file_path) } @@ -64,9 +64,9 @@ where } let node_resolver = state.borrow::(); match node_resolver.url_to_node_resolution(url)? { - resolution::NodeResolution::Esm(u) => Ok(u.to_string()), - resolution::NodeResolution::CommonJs(u) => wrap_cjs(u), - _ => Err(generic_error("Neither ESM nor CJS")), + NodeResolution::Esm(u) => Ok(u.to_string()), + NodeResolution::CommonJs(u) => wrap_cjs(u), + NodeResolution::BuiltIn(_) => Err(generic_error("Neither ESM nor CJS")), } } diff --git a/ext/node/path.rs b/ext/node/path.rs deleted file mode 100644 index 0f151edafd..0000000000 --- a/ext/node/path.rs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use std::path::Component; -use std::path::Path; -use std::path::PathBuf; - -use deno_core::ModuleSpecifier; - -/// Extension to path_clean::PathClean -pub trait PathClean { - fn clean(&self) -> T; -} - -impl PathClean for PathBuf { - fn clean(&self) -> PathBuf { - let path = path_clean::PathClean::clean(self); - if cfg!(windows) && path.to_string_lossy().contains("..\\") { - // temporary workaround because path_clean::PathClean::clean is - // not good enough on windows - let mut components = Vec::new(); - - for component in path.components() { - match component { - Component::CurDir => { - // skip - } - Component::ParentDir => { - let maybe_last_component = components.pop(); - if !matches!(maybe_last_component, Some(Component::Normal(_))) { - panic!("Error normalizing: {}", path.display()); - } - } - Component::Normal(_) | Component::RootDir | Component::Prefix(_) => { - components.push(component); - } - } - } - components.into_iter().collect::() - } else { - path - } - } -} - -pub(crate) fn to_file_specifier(path: &Path) -> ModuleSpecifier { - match ModuleSpecifier::from_file_path(path) { - Ok(url) => url, - Err(_) => panic!("Invalid path: {}", path.display()), - } -} diff --git a/ext/node/polyfill.rs b/ext/node/polyfill.rs index 5847acc428..b4030a4913 100644 --- a/ext/node/polyfill.rs +++ b/ext/node/polyfill.rs @@ -1,7 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::ModuleSpecifier; - /// e.g. `is_builtin_node_module("assert")` pub fn is_builtin_node_module(module_name: &str) -> bool { SUPPORTED_BUILTIN_NODE_MODULES @@ -9,18 +7,6 @@ pub fn is_builtin_node_module(module_name: &str) -> bool { .any(|m| *m == module_name) } -/// Ex. returns `fs` for `node:fs` -pub fn get_module_name_from_builtin_node_module_specifier( - specifier: &ModuleSpecifier, -) -> Option<&str> { - if specifier.scheme() != "node" { - return None; - } - - let (_, specifier) = specifier.as_str().split_once(':')?; - Some(specifier) -} - macro_rules! generate_builtin_node_module_lists { ($( $module_name:literal ,)+) => { pub static SUPPORTED_BUILTIN_NODE_MODULES: &[&str] = &[ diff --git a/ext/node_resolver/Cargo.toml b/ext/node_resolver/Cargo.toml new file mode 100644 index 0000000000..a636eaf9fc --- /dev/null +++ b/ext/node_resolver/Cargo.toml @@ -0,0 +1,32 @@ +# Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +[package] +name = "node_resolver" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +readme = "README.md" +repository.workspace = true +description = "Node.js module resolution algorithm used in Deno" + +[lib] +path = "lib.rs" + +[features] +sync = ["deno_package_json/sync"] + +[dependencies] +anyhow.workspace = true +async-trait.workspace = true +deno_media_type.workspace = true +deno_package_json.workspace = true +futures.workspace = true +lazy-regex.workspace = true +once_cell.workspace = true +path-clean = "=0.1.0" +regex.workspace = true +serde_json.workspace = true +thiserror.workspace = true +tokio.workspace = true +url.workspace = true diff --git a/ext/node_resolver/README.md b/ext/node_resolver/README.md new file mode 100644 index 0000000000..8f2f63ca19 --- /dev/null +++ b/ext/node_resolver/README.md @@ -0,0 +1,6 @@ +# Node Resolver + +[![crates](https://img.shields.io/crates/v/node_resolver.svg)](https://crates.io/crates/node_resolver) +[![docs](https://docs.rs/node_resolver/badge.svg)](https://docs.rs/node_resolver) + +Provides Node.js compatible resolution for the Deno project. diff --git a/ext/node/analyze.rs b/ext/node_resolver/analyze.rs similarity index 92% rename from ext/node/analyze.rs rename to ext/node_resolver/analyze.rs index 3513a8105a..8d6a734241 100644 --- a/ext/node/analyze.rs +++ b/ext/node_resolver/analyze.rs @@ -5,17 +5,17 @@ use std::collections::HashSet; use std::path::Path; use std::path::PathBuf; -use deno_core::anyhow; -use deno_core::anyhow::Context; -use deno_core::futures::future::LocalBoxFuture; -use deno_core::futures::stream::FuturesUnordered; -use deno_core::futures::FutureExt; -use deno_core::futures::StreamExt; -use deno_core::ModuleSpecifier; +use futures::future::LocalBoxFuture; +use futures::stream::FuturesUnordered; +use futures::FutureExt; +use futures::StreamExt; use once_cell::sync::Lazy; -use deno_core::error::AnyError; +use anyhow::Context; +use anyhow::Error as AnyError; +use url::Url; +use crate::env::NodeResolverEnv; use crate::package_json::load_pkg_json; use crate::path::to_file_specifier; use crate::resolution::NodeResolverRc; @@ -50,28 +50,33 @@ pub trait CjsCodeAnalyzer { /// necessary. async fn analyze_cjs( &self, - specifier: &ModuleSpecifier, + specifier: &Url, maybe_source: Option, ) -> Result; } -pub struct NodeCodeTranslator { +pub struct NodeCodeTranslator< + TCjsCodeAnalyzer: CjsCodeAnalyzer, + TNodeResolverEnv: NodeResolverEnv, +> { cjs_code_analyzer: TCjsCodeAnalyzer, - fs: deno_fs::FileSystemRc, - node_resolver: NodeResolverRc, + env: TNodeResolverEnv, + node_resolver: NodeResolverRc, npm_resolver: NpmResolverRc, } -impl NodeCodeTranslator { +impl + NodeCodeTranslator +{ pub fn new( cjs_code_analyzer: TCjsCodeAnalyzer, - fs: deno_fs::FileSystemRc, - node_resolver: NodeResolverRc, + env: TNodeResolverEnv, + node_resolver: NodeResolverRc, npm_resolver: NpmResolverRc, ) -> Self { Self { cjs_code_analyzer, - fs, + env, node_resolver, npm_resolver, } @@ -85,7 +90,7 @@ impl NodeCodeTranslator { /// If successful a source code for equivalent ES module is returned. pub async fn translate_cjs_to_esm( &self, - entry_specifier: &ModuleSpecifier, + entry_specifier: &Url, source: Option, ) -> Result { let mut temp_var_count = 0; @@ -173,7 +178,7 @@ impl NodeCodeTranslator { type AnalysisFuture<'a> = LocalBoxFuture<'a, Result>; - let mut handled_reexports: HashSet = HashSet::default(); + let mut handled_reexports: HashSet = HashSet::default(); handled_reexports.insert(entry_specifier.clone()); let mut analyze_futures: FuturesUnordered> = FuturesUnordered::new(); @@ -282,10 +287,10 @@ impl NodeCodeTranslator { fn resolve( &self, specifier: &str, - referrer: &ModuleSpecifier, + referrer: &Url, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result { + ) -> Result { if specifier.starts_with('/') { todo!(); } @@ -305,14 +310,14 @@ impl NodeCodeTranslator { let (package_specifier, package_subpath) = parse_specifier(specifier).unwrap(); - // todo(dsherret): use not_found error on not found here let module_dir = self.npm_resolver.resolve_package_folder_from_package( package_specifier.as_str(), referrer, )?; let package_json_path = module_dir.join("package.json"); - let maybe_package_json = load_pkg_json(&*self.fs, &package_json_path)?; + let maybe_package_json = + load_pkg_json(self.env.pkg_json_fs(), &package_json_path)?; if let Some(package_json) = maybe_package_json { if let Some(exports) = &package_json.exports { return self @@ -332,11 +337,11 @@ impl NodeCodeTranslator { // old school if package_subpath != "." { let d = module_dir.join(package_subpath); - if self.fs.is_dir_sync(&d) { + 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.fs, &package_json_path)?; + load_pkg_json(self.env.pkg_json_fs(), &package_json_path)?; if let Some(package_json) = maybe_package_json { if let Some(main) = package_json.main(NodeModuleKind::Cjs) { return Ok(to_file_specifier(&d.join(main).clean())); @@ -381,13 +386,13 @@ impl NodeCodeTranslator { referrer: &Path, ) -> Result { let p = p.clean(); - if self.fs.exists_sync(&p) { + if self.env.exists_sync(&p) { let file_name = p.file_name().unwrap(); let p_js = p.with_file_name(format!("{}.js", file_name.to_str().unwrap())); - if self.fs.is_file_sync(&p_js) { + if self.env.is_file_sync(&p_js) { return Ok(p_js); - } else if self.fs.is_dir_sync(&p) { + } else if self.env.is_dir_sync(&p) { return Ok(p.join("index.js")); } else { return Ok(p); @@ -396,14 +401,14 @@ impl NodeCodeTranslator { { let p_js = p.with_file_name(format!("{}.js", file_name.to_str().unwrap())); - if self.fs.is_file_sync(&p_js) { + if self.env.is_file_sync(&p_js) { return Ok(p_js); } } { let p_json = p.with_file_name(format!("{}.json", file_name.to_str().unwrap())); - if self.fs.is_file_sync(&p_json) { + if self.env.is_file_sync(&p_json) { return Ok(p_json); } } diff --git a/ext/node_resolver/clippy.toml b/ext/node_resolver/clippy.toml new file mode 100644 index 0000000000..86150781bb --- /dev/null +++ b/ext/node_resolver/clippy.toml @@ -0,0 +1,48 @@ +disallowed-methods = [ + { path = "std::env::current_dir", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::Path::canonicalize", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::Path::is_dir", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::Path::is_file", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::Path::is_symlink", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::Path::metadata", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::Path::read_dir", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::Path::read_link", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::Path::symlink_metadata", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::Path::try_exists", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::PathBuf::exists", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::PathBuf::canonicalize", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::PathBuf::is_dir", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::PathBuf::is_file", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::PathBuf::is_symlink", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::PathBuf::metadata", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::PathBuf::read_dir", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::PathBuf::read_link", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::PathBuf::symlink_metadata", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::PathBuf::try_exists", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::env::set_current_dir", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::env::temp_dir", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::fs::canonicalize", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::fs::copy", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::fs::create_dir_all", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::fs::create_dir", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::fs::DirBuilder::new", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::fs::hard_link", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::fs::metadata", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::fs::OpenOptions::new", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::fs::read_dir", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::fs::read_link", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::fs::read_to_string", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::fs::read", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::fs::remove_dir_all", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::fs::remove_dir", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::fs::remove_file", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::fs::rename", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::fs::set_permissions", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::fs::symlink_metadata", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::fs::write", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::Path::canonicalize", reason = "File system operations should be done using NodeResolverFs trait" }, + { path = "std::path::Path::exists", reason = "File system operations should be done using NodeResolverFs trait" }, +] +disallowed-types = [ + { path = "std::sync::Arc", reason = "use crate::sync::MaybeArc instead" }, +] diff --git a/ext/node_resolver/env.rs b/ext/node_resolver/env.rs new file mode 100644 index 0000000000..b520ece0f8 --- /dev/null +++ b/ext/node_resolver/env.rs @@ -0,0 +1,39 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use std::path::Path; +use std::path::PathBuf; + +use crate::sync::MaybeSend; +use crate::sync::MaybeSync; + +pub struct NodeResolverFsStat { + pub is_file: bool, + pub is_dir: bool, + pub is_symlink: bool, +} + +pub trait NodeResolverEnv: std::fmt::Debug + MaybeSend + MaybeSync { + fn is_builtin_node_module(&self, specifier: &str) -> bool; + + fn realpath_sync(&self, path: &Path) -> std::io::Result; + + fn stat_sync(&self, path: &Path) -> std::io::Result; + + fn exists_sync(&self, path: &Path) -> bool; + + fn is_file_sync(&self, path: &Path) -> bool { + self + .stat_sync(path) + .map(|stat| stat.is_file) + .unwrap_or(false) + } + + fn is_dir_sync(&self, path: &Path) -> bool { + self + .stat_sync(path) + .map(|stat| stat.is_dir) + .unwrap_or(false) + } + + fn pkg_json_fs(&self) -> &dyn deno_package_json::fs::DenoPkgJsonFs; +} diff --git a/ext/node/errors.rs b/ext/node_resolver/errors.rs similarity index 97% rename from ext/node/errors.rs rename to ext/node_resolver/errors.rs index 64625d32f4..4ba829eda5 100644 --- a/ext/node/errors.rs +++ b/ext/node_resolver/errors.rs @@ -4,8 +4,8 @@ use std::borrow::Cow; use std::fmt::Write; use std::path::PathBuf; -use deno_core::ModuleSpecifier; use thiserror::Error; +use url::Url; use crate::NodeModuleKind; use crate::NodeResolutionMode; @@ -155,7 +155,7 @@ kinded_err!(PackageFolderResolveError, PackageFolderResolveErrorKind); )] pub struct PackageNotFoundError { pub package_name: String, - pub referrer: ModuleSpecifier, + pub referrer: Url, /// Extra information about the referrer. pub referrer_extra: Option, } @@ -173,7 +173,7 @@ impl NodeJsErrorCoded for PackageNotFoundError { referrer_extra.as_ref().map(|r| format!(" ({})", r)).unwrap_or_default() )] pub struct ReferrerNotFoundError { - pub referrer: ModuleSpecifier, + pub referrer: Url, /// Extra information about the referrer. pub referrer_extra: Option, } @@ -188,7 +188,7 @@ impl NodeJsErrorCoded for ReferrerNotFoundError { #[error("Failed resolving '{package_name}' from referrer '{referrer}'.")] pub struct PackageFolderResolveIoError { pub package_name: String, - pub referrer: ModuleSpecifier, + pub referrer: Url, #[source] pub source: std::io::Error, } @@ -264,7 +264,7 @@ pub enum PackageSubpathResolveErrorKind { pub struct PackageTargetNotFoundError { pub pkg_json_path: PathBuf, pub target: String, - pub maybe_referrer: Option, + pub maybe_referrer: Option, pub referrer_kind: NodeModuleKind, pub mode: NodeResolutionMode, } @@ -333,8 +333,8 @@ pub struct TypesNotFoundError(pub Box); #[derive(Debug)] pub struct TypesNotFoundErrorData { - pub code_specifier: ModuleSpecifier, - pub maybe_referrer: Option, + pub code_specifier: Url, + pub maybe_referrer: Option, } impl NodeJsErrorCoded for TypesNotFoundError { @@ -397,7 +397,7 @@ impl NodeJsErrorCoded for CanonicalizingPkgJsonDirError { #[derive(Debug, Error)] #[error("TypeScript files are not supported in npm packages: {specifier}")] pub struct TypeScriptNotSupportedInNpmError { - pub specifier: ModuleSpecifier, + pub specifier: Url, } impl NodeJsErrorCoded for TypeScriptNotSupportedInNpmError { @@ -437,7 +437,7 @@ pub enum UrlToNodeResolutionErrorKind { pub struct PackageImportNotDefinedError { pub name: String, pub package_json_path: Option, - pub maybe_referrer: Option, + pub maybe_referrer: Option, } impl NodeJsErrorCoded for PackageImportNotDefinedError { @@ -503,7 +503,7 @@ pub enum PackageResolveErrorKind { #[error("Failed joining '{path}' from '{base}'.")] pub struct NodeResolveRelativeJoinError { pub path: String, - pub base: ModuleSpecifier, + pub base: Url, #[source] pub source: url::ParseError, } @@ -568,8 +568,8 @@ impl NodeJsErrorCoded for FinalizeResolutionError { maybe_referrer.as_ref().map(|referrer| format!(" imported from '{}'", referrer)).unwrap_or_default() )] pub struct ModuleNotFoundError { - pub specifier: ModuleSpecifier, - pub maybe_referrer: Option, + pub specifier: Url, + pub maybe_referrer: Option, pub typ: &'static str, } @@ -587,8 +587,8 @@ impl NodeJsErrorCoded for ModuleNotFoundError { maybe_referrer.as_ref().map(|referrer| format!(" imported from '{}'", referrer)).unwrap_or_default(), )] pub struct UnsupportedDirImportError { - pub dir_url: ModuleSpecifier, - pub maybe_referrer: Option, + pub dir_url: Url, + pub maybe_referrer: Option, } impl NodeJsErrorCoded for UnsupportedDirImportError { @@ -603,7 +603,7 @@ pub struct InvalidPackageTargetError { pub sub_path: String, pub target: String, pub is_import: bool, - pub maybe_referrer: Option, + pub maybe_referrer: Option, } impl std::error::Error for InvalidPackageTargetError {} @@ -657,7 +657,7 @@ impl NodeJsErrorCoded for InvalidPackageTargetError { pub struct PackagePathNotExportedError { pub pkg_json_path: PathBuf, pub subpath: String, - pub maybe_referrer: Option, + pub maybe_referrer: Option, pub mode: NodeResolutionMode, } diff --git a/ext/node_resolver/lib.rs b/ext/node_resolver/lib.rs new file mode 100644 index 0000000000..1ab972ccfd --- /dev/null +++ b/ext/node_resolver/lib.rs @@ -0,0 +1,26 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +#![deny(clippy::print_stderr)] +#![deny(clippy::print_stdout)] + +pub mod analyze; +pub mod env; +pub mod errors; +mod npm; +mod package_json; +mod path; +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 package_json::PackageJsonThreadLocalCache; +pub use path::PathClean; +pub use resolution::NodeModuleKind; +pub use resolution::NodeResolution; +pub use resolution::NodeResolutionMode; +pub use resolution::NodeResolver; +pub use resolution::DEFAULT_CONDITIONS; +pub use resolution::REQUIRE_CONDITIONS; diff --git a/ext/node_resolver/npm.rs b/ext/node_resolver/npm.rs new file mode 100644 index 0000000000..77df57c489 --- /dev/null +++ b/ext/node_resolver/npm.rs @@ -0,0 +1,41 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use std::path::Path; +use std::path::PathBuf; + +use url::Url; + +use crate::errors; +use crate::path::PathClean; +use crate::sync::MaybeSend; +use crate::sync::MaybeSync; + +#[allow(clippy::disallowed_types)] +pub type NpmResolverRc = crate::sync::MaybeArc; + +pub trait NpmResolver: std::fmt::Debug + MaybeSend + MaybeSync { + /// Resolves an npm package folder path from an npm package referrer. + fn resolve_package_folder_from_package( + &self, + specifier: &str, + referrer: &Url, + ) -> Result; + + fn in_npm_package(&self, specifier: &Url) -> bool; + + fn in_npm_package_at_dir_path(&self, path: &Path) -> bool { + let specifier = match Url::from_directory_path(path.to_path_buf().clean()) { + Ok(p) => p, + Err(_) => return false, + }; + self.in_npm_package(&specifier) + } + + fn in_npm_package_at_file_path(&self, path: &Path) -> bool { + let specifier = match Url::from_file_path(path.to_path_buf().clean()) { + Ok(p) => p, + Err(_) => return false, + }; + self.in_npm_package(&specifier) + } +} diff --git a/ext/node/package_json.rs b/ext/node_resolver/package_json.rs similarity index 72% rename from ext/node/package_json.rs rename to ext/node_resolver/package_json.rs index 877acfc7a7..de750f1d7e 100644 --- a/ext/node/package_json.rs +++ b/ext/node_resolver/package_json.rs @@ -33,31 +33,14 @@ impl deno_package_json::PackageJsonCache for PackageJsonThreadLocalCache { } } -pub struct DenoPkgJsonFsAdapter<'a>(pub &'a dyn deno_fs::FileSystem); - -impl<'a> deno_package_json::fs::DenoPkgJsonFs for DenoPkgJsonFsAdapter<'a> { - fn read_to_string_lossy( - &self, - path: &Path, - ) -> Result { - self - .0 - .read_text_file_lossy_sync(path, None) - .map_err(|err| err.into_io_error()) - } -} - /// Helper to load a package.json file using the thread local cache -/// in deno_node. +/// in node_resolver. pub fn load_pkg_json( - fs: &dyn deno_fs::FileSystem, + fs: &dyn deno_package_json::fs::DenoPkgJsonFs, path: &Path, ) -> Result, PackageJsonLoadError> { - let result = PackageJson::load_from_path( - path, - &DenoPkgJsonFsAdapter(fs), - Some(&PackageJsonThreadLocalCache), - ); + 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, .. }) diff --git a/ext/node_resolver/path.rs b/ext/node_resolver/path.rs new file mode 100644 index 0000000000..8c33285db1 --- /dev/null +++ b/ext/node_resolver/path.rs @@ -0,0 +1,142 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use std::path::Component; +use std::path::Path; +use std::path::PathBuf; + +use url::Url; + +/// Extension to path_clean::PathClean +pub trait PathClean { + fn clean(&self) -> T; +} + +impl PathClean for PathBuf { + fn clean(&self) -> PathBuf { + let path = path_clean::PathClean::clean(self); + if cfg!(windows) && path.to_string_lossy().contains("..\\") { + // temporary workaround because path_clean::PathClean::clean is + // not good enough on windows + let mut components = Vec::new(); + + for component in path.components() { + match component { + Component::CurDir => { + // skip + } + Component::ParentDir => { + let maybe_last_component = components.pop(); + if !matches!(maybe_last_component, Some(Component::Normal(_))) { + panic!("Error normalizing: {}", path.display()); + } + } + Component::Normal(_) | Component::RootDir | Component::Prefix(_) => { + components.push(component); + } + } + } + components.into_iter().collect::() + } else { + path + } + } +} + +pub(crate) fn to_file_specifier(path: &Path) -> Url { + match Url::from_file_path(path) { + Ok(url) => url, + Err(_) => panic!("Invalid path: {}", path.display()), + } +} + +// todo(dsherret): we have the below code also in deno_core and it +// would be good to somehow re-use it in both places (we don't want +// to create a dependency on deno_core here) + +#[cfg(not(windows))] +#[inline] +pub fn strip_unc_prefix(path: PathBuf) -> PathBuf { + path +} + +/// Strips the unc prefix (ex. \\?\) from Windows paths. +#[cfg(windows)] +pub fn strip_unc_prefix(path: PathBuf) -> PathBuf { + use std::path::Component; + use std::path::Prefix; + + let mut components = path.components(); + match components.next() { + Some(Component::Prefix(prefix)) => { + match prefix.kind() { + // \\?\device + Prefix::Verbatim(device) => { + let mut path = PathBuf::new(); + path.push(format!(r"\\{}\", device.to_string_lossy())); + path.extend(components.filter(|c| !matches!(c, Component::RootDir))); + path + } + // \\?\c:\path + Prefix::VerbatimDisk(_) => { + let mut path = PathBuf::new(); + path.push(prefix.as_os_str().to_string_lossy().replace(r"\\?\", "")); + path.extend(components); + path + } + // \\?\UNC\hostname\share_name\path + Prefix::VerbatimUNC(hostname, share_name) => { + let mut path = PathBuf::new(); + path.push(format!( + r"\\{}\{}\", + hostname.to_string_lossy(), + share_name.to_string_lossy() + )); + path.extend(components.filter(|c| !matches!(c, Component::RootDir))); + path + } + _ => path, + } + } + _ => path, + } +} + +#[cfg(test)] +mod test { + #[cfg(windows)] + #[test] + fn test_strip_unc_prefix() { + use std::path::PathBuf; + + run_test(r"C:\", r"C:\"); + run_test(r"C:\test\file.txt", r"C:\test\file.txt"); + + run_test(r"\\?\C:\", r"C:\"); + run_test(r"\\?\C:\test\file.txt", r"C:\test\file.txt"); + + run_test(r"\\.\C:\", r"\\.\C:\"); + run_test(r"\\.\C:\Test\file.txt", r"\\.\C:\Test\file.txt"); + + run_test(r"\\?\UNC\localhost\", r"\\localhost"); + run_test(r"\\?\UNC\localhost\c$\", r"\\localhost\c$"); + run_test( + r"\\?\UNC\localhost\c$\Windows\file.txt", + r"\\localhost\c$\Windows\file.txt", + ); + run_test(r"\\?\UNC\wsl$\deno.json", r"\\wsl$\deno.json"); + + run_test(r"\\?\server1", r"\\server1"); + run_test(r"\\?\server1\e$\", r"\\server1\e$\"); + run_test( + r"\\?\server1\e$\test\file.txt", + r"\\server1\e$\test\file.txt", + ); + + fn run_test(input: &str, expected: &str) { + assert_eq!( + super::strip_unc_prefix(PathBuf::from(input)), + PathBuf::from(expected) + ); + } + } +} diff --git a/ext/node/resolution.rs b/ext/node_resolver/resolution.rs similarity index 92% rename from ext/node/resolution.rs rename to ext/node_resolver/resolution.rs index 6417835a28..d7918c75c6 100644 --- a/ext/node/resolution.rs +++ b/ext/node_resolver/resolution.rs @@ -5,16 +5,15 @@ use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; -use deno_core::anyhow::bail; -use deno_core::error::AnyError; -use deno_core::serde_json::Map; -use deno_core::serde_json::Value; -use deno_core::url::Url; -use deno_core::ModuleSpecifier; -use deno_fs::FileSystemRc; +use anyhow::bail; +use anyhow::Error as AnyError; use deno_media_type::MediaType; use deno_package_json::PackageJsonRc; +use serde_json::Map; +use serde_json::Value; +use url::Url; +use crate::env::NodeResolverEnv; use crate::errors; use crate::errors::CanonicalizingPkgJsonDirError; use crate::errors::ClosestPkgJsonError; @@ -49,12 +48,11 @@ use crate::errors::TypesNotFoundErrorData; use crate::errors::UnsupportedDirImportError; use crate::errors::UnsupportedEsmUrlSchemeError; use crate::errors::UrlToNodeResolutionError; -use crate::is_builtin_node_module; +use crate::path::strip_unc_prefix; use crate::path::to_file_specifier; -use crate::polyfill::get_module_name_from_builtin_node_module_specifier; use crate::NpmResolverRc; -use crate::PackageJson; use crate::PathClean; +use deno_package_json::PackageJson; pub static DEFAULT_CONDITIONS: &[&str] = &["deno", "node", "import"]; pub static REQUIRE_CONDITIONS: &[&str] = &["require", "node"]; @@ -76,21 +74,21 @@ impl NodeResolutionMode { #[derive(Debug)] pub enum NodeResolution { - Esm(ModuleSpecifier), - CommonJs(ModuleSpecifier), + Esm(Url), + CommonJs(Url), BuiltIn(String), } impl NodeResolution { - pub fn into_url(self) -> ModuleSpecifier { + pub fn into_url(self) -> Url { match self { Self::Esm(u) => u, Self::CommonJs(u) => u, Self::BuiltIn(specifier) => { if specifier.starts_with("node:") { - ModuleSpecifier::parse(&specifier).unwrap() + Url::parse(&specifier).unwrap() } else { - ModuleSpecifier::parse(&format!("node:{specifier}")).unwrap() + Url::parse(&format!("node:{specifier}")).unwrap() } } } @@ -98,7 +96,7 @@ impl NodeResolution { pub fn into_specifier_and_media_type( resolution: Option, - ) -> (ModuleSpecifier, MediaType) { + ) -> (Url, MediaType) { match resolution { Some(NodeResolution::CommonJs(specifier)) => { let media_type = MediaType::from_specifier(&specifier); @@ -126,7 +124,7 @@ impl NodeResolution { } Some(resolution) => (resolution.into_url(), MediaType::Dts), None => ( - ModuleSpecifier::parse("internal:///missing_dependency.d.ts").unwrap(), + Url::parse("internal:///missing_dependency.d.ts").unwrap(), MediaType::Dts, ), } @@ -134,25 +132,25 @@ impl NodeResolution { } #[allow(clippy::disallowed_types)] -pub type NodeResolverRc = deno_fs::sync::MaybeArc; +pub type NodeResolverRc = crate::sync::MaybeArc>; #[derive(Debug)] -pub struct NodeResolver { - fs: FileSystemRc, +pub struct NodeResolver { + env: TEnv, npm_resolver: NpmResolverRc, - in_npm_package_cache: deno_fs::sync::MaybeArcMutex>, + in_npm_package_cache: crate::sync::MaybeArcMutex>, } -impl NodeResolver { - pub fn new(fs: FileSystemRc, npm_resolver: NpmResolverRc) -> Self { +impl NodeResolver { + pub fn new(env: TEnv, npm_resolver: NpmResolverRc) -> Self { Self { - fs, + env, npm_resolver, - in_npm_package_cache: deno_fs::sync::MaybeArcMutex::new(HashMap::new()), + in_npm_package_cache: crate::sync::MaybeArcMutex::new(HashMap::new()), } } - pub fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool { + pub fn in_npm_package(&self, specifier: &Url) -> bool { self.npm_resolver.in_npm_package(specifier) } @@ -163,12 +161,11 @@ impl NodeResolver { return *result; } - let result = - if let Ok(specifier) = deno_core::ModuleSpecifier::parse(&specifier) { - self.npm_resolver.in_npm_package(&specifier) - } else { - false - }; + let result = if let Ok(specifier) = Url::parse(&specifier) { + self.npm_resolver.in_npm_package(&specifier) + } else { + false + }; cache.insert(specifier.into_owned(), result); result } @@ -178,14 +175,14 @@ impl NodeResolver { pub fn resolve( &self, specifier: &str, - referrer: &ModuleSpecifier, + referrer: &Url, referrer_kind: NodeModuleKind, mode: NodeResolutionMode, ) -> Result { // Note: if we are here, then the referrer is an esm module // TODO(bartlomieju): skipped "policy" part as we don't plan to support it - if crate::is_builtin_node_module(specifier) { + if self.env.is_builtin_node_module(specifier) { return Ok(NodeResolution::BuiltIn(specifier.to_string())); } @@ -248,11 +245,11 @@ impl NodeResolver { fn module_resolve( &self, specifier: &str, - referrer: &ModuleSpecifier, + referrer: &Url, referrer_kind: NodeModuleKind, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result { + ) -> Result { if should_be_treated_as_relative_or_absolute_path(specifier) { Ok(referrer.join(specifier).map_err(|err| { NodeResolveRelativeJoinError { @@ -289,9 +286,9 @@ impl NodeResolver { fn finalize_resolution( &self, - resolved: ModuleSpecifier, - maybe_referrer: Option<&ModuleSpecifier>, - ) -> Result { + resolved: Url, + maybe_referrer: Option<&Url>, + ) -> Result { let encoded_sep_re = lazy_regex::regex!(r"%2F|%2C"); if encoded_sep_re.is_match(resolved.path()) { @@ -325,9 +322,9 @@ impl NodeResolver { p_str.to_string() }; - let (is_dir, is_file) = if let Ok(stats) = self.fs.stat_sync(Path::new(&p)) + let (is_dir, is_file) = if let Ok(stats) = self.env.stat_sync(Path::new(&p)) { - (stats.is_directory, stats.is_file) + (stats.is_dir, stats.is_file) } else { (false, false) }; @@ -357,7 +354,7 @@ impl NodeResolver { &self, package_dir: &Path, package_subpath: Option<&str>, - maybe_referrer: Option<&ModuleSpecifier>, + maybe_referrer: Option<&Url>, mode: NodeResolutionMode, ) -> Result { let node_module_kind = NodeModuleKind::Esm; @@ -430,7 +427,7 @@ impl NodeResolver { pub fn url_to_node_resolution( &self, - url: ModuleSpecifier, + url: Url, ) -> Result { let url_str = url.as_str().to_lowercase(); if url_str.starts_with("http") || url_str.ends_with(".json") { @@ -459,11 +456,11 @@ impl NodeResolver { fn path_to_declaration_url( &self, path: &Path, - maybe_referrer: Option<&ModuleSpecifier>, + maybe_referrer: Option<&Url>, referrer_kind: NodeModuleKind, - ) -> Result { - fn probe_extensions( - fs: &dyn deno_fs::FileSystem, + ) -> Result { + fn probe_extensions( + fs: &TEnv, path: &Path, lowercase_path: &str, referrer_kind: NodeModuleKind, @@ -514,11 +511,11 @@ impl NodeResolver { return Ok(to_file_specifier(path)); } if let Some(path) = - probe_extensions(&*self.fs, path, &lowercase_path, referrer_kind) + probe_extensions(&self.env, path, &lowercase_path, referrer_kind) { return Ok(to_file_specifier(&path)); } - if self.fs.is_dir_sync(path) { + if self.env.is_dir_sync(path) { let resolution_result = self.resolve_package_dir_subpath( path, /* sub path */ ".", @@ -535,7 +532,7 @@ impl NodeResolver { } let index_path = path.join("index.js"); if let Some(path) = probe_extensions( - &*self.fs, + &self.env, &index_path, &index_path.to_string_lossy().to_lowercase(), referrer_kind, @@ -554,15 +551,15 @@ impl NodeResolver { } #[allow(clippy::too_many_arguments)] - pub(super) fn package_imports_resolve( + pub fn package_imports_resolve( &self, name: &str, - maybe_referrer: Option<&ModuleSpecifier>, + maybe_referrer: Option<&Url>, referrer_kind: NodeModuleKind, referrer_pkg_json: Option<&PackageJson>, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result { + ) -> Result { if name == "#" || name.starts_with("#/") || name.ends_with('/') { let reason = "is not a valid internal imports specifier name"; return Err( @@ -659,13 +656,13 @@ impl NodeResolver { subpath: &str, match_: &str, package_json_path: &Path, - maybe_referrer: Option<&ModuleSpecifier>, + maybe_referrer: Option<&Url>, referrer_kind: NodeModuleKind, pattern: bool, internal: bool, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result { + ) -> Result { if !subpath.is_empty() && !pattern && !target.ends_with('/') { return Err( InvalidPackageTargetError { @@ -739,11 +736,8 @@ impl NodeResolver { return match result { Ok(url) => Ok(url), Err(err) => { - if is_builtin_node_module(target) { - Ok( - ModuleSpecifier::parse(&format!("node:{}", target)) - .unwrap(), - ) + if self.env.is_builtin_node_module(target) { + Ok(Url::parse(&format!("node:{}", target)).unwrap()) } else { Err(err) } @@ -824,13 +818,13 @@ impl NodeResolver { target: &Value, subpath: &str, package_subpath: &str, - maybe_referrer: Option<&ModuleSpecifier>, + maybe_referrer: Option<&Url>, referrer_kind: NodeModuleKind, pattern: bool, internal: bool, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result, PackageTargetResolveError> { + ) -> Result, PackageTargetResolveError> { let result = self.resolve_package_target_inner( package_json_path, target, @@ -880,13 +874,13 @@ impl NodeResolver { target: &Value, subpath: &str, package_subpath: &str, - maybe_referrer: Option<&ModuleSpecifier>, + maybe_referrer: Option<&Url>, referrer_kind: NodeModuleKind, pattern: bool, internal: bool, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result, PackageTargetResolveError> { + ) -> Result, PackageTargetResolveError> { if let Some(target) = target.as_str() { let url = self.resolve_package_target_string( target, @@ -1007,11 +1001,11 @@ impl NodeResolver { package_json_path: &Path, package_subpath: &str, package_exports: &Map, - maybe_referrer: Option<&ModuleSpecifier>, + maybe_referrer: Option<&Url>, referrer_kind: NodeModuleKind, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result { + ) -> Result { if package_exports.contains_key(package_subpath) && package_subpath.find('*').is_none() && !package_subpath.ends_with('/') @@ -1120,11 +1114,11 @@ impl NodeResolver { pub(super) fn package_resolve( &self, specifier: &str, - referrer: &ModuleSpecifier, + referrer: &Url, referrer_kind: NodeModuleKind, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result { + ) -> Result { let (package_name, package_subpath, _is_scoped) = parse_npm_pkg_name(specifier, referrer)?; @@ -1162,11 +1156,11 @@ impl NodeResolver { &self, package_name: &str, package_subpath: &str, - referrer: &ModuleSpecifier, + referrer: &Url, referrer_kind: NodeModuleKind, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result { + ) -> Result { let result = self.resolve_package_subpath_for_package_inner( package_name, package_subpath, @@ -1175,7 +1169,7 @@ impl NodeResolver { conditions, mode, ); - if mode.is_types() && !matches!(result, Ok(ModuleSpecifier { .. })) { + if mode.is_types() && !matches!(result, Ok(Url { .. })) { // try to resolve with the @types package let package_name = types_package_name(package_name); if let Ok(result) = self.resolve_package_subpath_for_package_inner( @@ -1197,11 +1191,11 @@ impl NodeResolver { &self, package_name: &str, package_subpath: &str, - referrer: &ModuleSpecifier, + referrer: &Url, referrer_kind: NodeModuleKind, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result { + ) -> Result { let package_dir_path = self .npm_resolver .resolve_package_folder_from_package(package_name, referrer)?; @@ -1237,11 +1231,11 @@ impl NodeResolver { &self, package_dir_path: &Path, package_subpath: &str, - maybe_referrer: Option<&ModuleSpecifier>, + maybe_referrer: Option<&Url>, referrer_kind: NodeModuleKind, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result { + ) -> Result { let package_json_path = package_dir_path.join("package.json"); match self.load_package_json(&package_json_path)? { Some(pkg_json) => self.resolve_package_subpath( @@ -1271,11 +1265,11 @@ impl NodeResolver { &self, package_json: &PackageJson, package_subpath: &str, - referrer: Option<&ModuleSpecifier>, + referrer: Option<&Url>, referrer_kind: NodeModuleKind, conditions: &[&str], mode: NodeResolutionMode, - ) -> Result { + ) -> Result { if let Some(exports) = &package_json.exports { let result = self.package_exports_resolve( &package_json.path, @@ -1328,10 +1322,10 @@ impl NodeResolver { &self, directory: &Path, package_subpath: &str, - referrer: Option<&ModuleSpecifier>, + referrer: Option<&Url>, referrer_kind: NodeModuleKind, mode: NodeResolutionMode, - ) -> Result { + ) -> Result { assert_ne!(package_subpath, "."); let file_path = directory.join(package_subpath); if mode.is_types() { @@ -1345,10 +1339,10 @@ impl NodeResolver { &self, directory: &Path, package_subpath: &str, - maybe_referrer: Option<&ModuleSpecifier>, + maybe_referrer: Option<&Url>, referrer_kind: NodeModuleKind, mode: NodeResolutionMode, - ) -> Result { + ) -> Result { if package_subpath == "." { self.legacy_index_resolve(directory, maybe_referrer, referrer_kind, mode) } else { @@ -1366,7 +1360,7 @@ impl NodeResolver { pub fn get_closest_package_json( &self, - url: &ModuleSpecifier, + url: &Url, ) -> Result, ClosestPkgJsonError> { let Ok(file_path) = url.to_file_path() else { return Ok(None); @@ -1380,10 +1374,10 @@ impl NodeResolver { ) -> Result, ClosestPkgJsonError> { let parent_dir = file_path.parent().unwrap(); let current_dir = - deno_core::strip_unc_prefix(self.fs.realpath_sync(parent_dir).map_err( + strip_unc_prefix(self.env.realpath_sync(parent_dir).map_err( |source| CanonicalizingPkgJsonDirError { dir_path: parent_dir.to_path_buf(), - source: source.into_io_error(), + source, }, )?); for current_dir in current_dir.ancestors() { @@ -1396,20 +1390,23 @@ impl NodeResolver { Ok(None) } - pub(super) fn load_package_json( + pub fn load_package_json( &self, package_json_path: &Path, ) -> Result, PackageJsonLoadError> { - crate::package_json::load_pkg_json(&*self.fs, package_json_path) + crate::package_json::load_pkg_json( + self.env.pkg_json_fs(), + package_json_path, + ) } pub(super) fn legacy_main_resolve( &self, package_json: &PackageJson, - maybe_referrer: Option<&ModuleSpecifier>, + maybe_referrer: Option<&Url>, referrer_kind: NodeModuleKind, mode: NodeResolutionMode, - ) -> Result { + ) -> Result { let maybe_main = if mode.is_types() { match package_json.types.as_ref() { Some(types) => Some(types.as_str()), @@ -1437,7 +1434,7 @@ impl NodeResolver { if let Some(main) = maybe_main { let guess = package_json.path.parent().unwrap().join(main).clean(); - if self.fs.is_file_sync(&guess) { + if self.env.is_file_sync(&guess) { return Ok(to_file_specifier(&guess)); } @@ -1466,7 +1463,7 @@ impl NodeResolver { .unwrap() .join(format!("{main}{ending}")) .clean(); - if self.fs.is_file_sync(&guess) { + if self.env.is_file_sync(&guess) { // TODO(bartlomieju): emitLegacyIndexDeprecation() return Ok(to_file_specifier(&guess)); } @@ -1484,10 +1481,10 @@ impl NodeResolver { fn legacy_index_resolve( &self, directory: &Path, - maybe_referrer: Option<&ModuleSpecifier>, + maybe_referrer: Option<&Url>, referrer_kind: NodeModuleKind, mode: NodeResolutionMode, - ) -> Result { + ) -> Result { let index_file_names = if mode.is_types() { // todo(dsherret): investigate exactly how typescript does this match referrer_kind { @@ -1499,7 +1496,7 @@ impl NodeResolver { }; for index_file_name in index_file_names { let guess = directory.join(index_file_name).clean(); - if self.fs.is_file_sync(&guess) { + if self.env.is_file_sync(&guess) { // TODO(bartlomieju): emitLegacyIndexDeprecation() return Ok(to_file_specifier(&guess)); } @@ -1615,13 +1612,13 @@ fn resolve_bin_entry_value<'a>( } } -fn to_file_path(url: &ModuleSpecifier) -> PathBuf { +fn to_file_path(url: &Url) -> PathBuf { url .to_file_path() .unwrap_or_else(|_| panic!("Provided URL was not file:// URL: {url}")) } -fn to_file_path_string(url: &ModuleSpecifier) -> String { +fn to_file_path_string(url: &Url) -> String { to_file_path(url).display().to_string() } @@ -1696,7 +1693,7 @@ fn with_known_extension(path: &Path, ext: &str) -> PathBuf { path.with_file_name(format!("{file_name}.{ext}")) } -fn to_specifier_display_string(url: &ModuleSpecifier) -> String { +fn to_specifier_display_string(url: &Url) -> String { if let Ok(path) = url.to_file_path() { path.display().to_string() } else { @@ -1708,7 +1705,7 @@ fn throw_invalid_subpath( subpath: String, package_json_path: &Path, internal: bool, - maybe_referrer: Option<&ModuleSpecifier>, + maybe_referrer: Option<&Url>, ) -> InvalidModuleSpecifierError { let ie = if internal { "imports" } else { "exports" }; let reason = format!( @@ -1725,7 +1722,7 @@ fn throw_invalid_subpath( pub fn parse_npm_pkg_name( specifier: &str, - referrer: &ModuleSpecifier, + referrer: &Url, ) -> Result<(String, String, bool), InvalidModuleSpecifierError> { let mut separator_index = specifier.find('/'); let mut valid_package_name = true; @@ -1824,9 +1821,21 @@ fn types_package_name(package_name: &str) -> String { format!("@types/{}", package_name.replace('/', "__")) } +/// Ex. returns `fs` for `node:fs` +fn get_module_name_from_builtin_node_module_specifier( + specifier: &Url, +) -> Option<&str> { + if specifier.scheme() != "node" { + return None; + } + + let (_, specifier) = specifier.as_str().split_once(':')?; + Some(specifier) +} + #[cfg(test)] mod tests { - use deno_core::serde_json::json; + use serde_json::json; use super::*; diff --git a/ext/node_resolver/sync.rs b/ext/node_resolver/sync.rs new file mode 100644 index 0000000000..f6689a56ab --- /dev/null +++ b/ext/node_resolver/sync.rs @@ -0,0 +1,86 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +pub use inner::*; + +#[cfg(feature = "sync")] +mod inner { + #![allow(clippy::disallowed_types)] + + use std::ops::Deref; + use std::ops::DerefMut; + pub use std::sync::Arc as MaybeArc; + + pub struct MaybeArcMutexGuard<'lock, T>(std::sync::MutexGuard<'lock, T>); + + impl<'lock, T> Deref for MaybeArcMutexGuard<'lock, T> { + type Target = std::sync::MutexGuard<'lock, T>; + fn deref(&self) -> &std::sync::MutexGuard<'lock, T> { + &self.0 + } + } + + impl<'lock, T> DerefMut for MaybeArcMutexGuard<'lock, T> { + fn deref_mut(&mut self) -> &mut std::sync::MutexGuard<'lock, T> { + &mut self.0 + } + } + + #[derive(Debug)] + pub struct MaybeArcMutex(std::sync::Arc>); + impl MaybeArcMutex { + pub fn new(val: T) -> Self { + Self(std::sync::Arc::new(std::sync::Mutex::new(val))) + } + } + + impl<'lock, T> MaybeArcMutex { + pub fn lock(&'lock self) -> MaybeArcMutexGuard<'lock, T> { + MaybeArcMutexGuard(self.0.lock().unwrap()) + } + } + + pub use core::marker::Send as MaybeSend; + pub use core::marker::Sync as MaybeSync; +} + +#[cfg(not(feature = "sync"))] +mod inner { + use std::ops::Deref; + use std::ops::DerefMut; + + pub use std::rc::Rc as MaybeArc; + + pub struct MaybeArcMutexGuard<'lock, T>(std::cell::RefMut<'lock, T>); + + impl<'lock, T> Deref for MaybeArcMutexGuard<'lock, T> { + type Target = std::cell::RefMut<'lock, T>; + fn deref(&self) -> &std::cell::RefMut<'lock, T> { + &self.0 + } + } + + impl<'lock, T> DerefMut for MaybeArcMutexGuard<'lock, T> { + fn deref_mut(&mut self) -> &mut std::cell::RefMut<'lock, T> { + &mut self.0 + } + } + + #[derive(Debug)] + pub struct MaybeArcMutex(std::rc::Rc>); + impl MaybeArcMutex { + pub fn new(val: T) -> Self { + Self(std::rc::Rc::new(std::cell::RefCell::new(val))) + } + } + + impl<'lock, T> MaybeArcMutex { + pub fn lock(&'lock self) -> MaybeArcMutexGuard<'lock, T> { + MaybeArcMutexGuard(self.0.borrow_mut()) + } + } + + pub trait MaybeSync {} + impl MaybeSync for T where T: ?Sized {} + pub trait MaybeSend {} + impl MaybeSend for T where T: ?Sized {} +} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 7772b017b7..9980df2946 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -94,6 +94,7 @@ deno_webgpu.workspace = true deno_webidl.workspace = true deno_websocket.workspace = true deno_webstorage.workspace = true +node_resolver = { workspace = true, features = ["sync"] } dlopen2.workspace = true encoding_rs.workspace = true diff --git a/runtime/snapshot.rs b/runtime/snapshot.rs index 2144ff07a2..da66bff5eb 100644 --- a/runtime/snapshot.rs +++ b/runtime/snapshot.rs @@ -254,7 +254,7 @@ pub fn create_runtime_snapshot( deno_http::deno_http::init_ops_and_esm::(), deno_io::deno_io::init_ops_and_esm(Default::default()), deno_fs::deno_fs::init_ops_and_esm::(fs.clone()), - deno_node::deno_node::init_ops_and_esm::(None, None, fs), + deno_node::deno_node::init_ops_and_esm::(None, fs), runtime::init_ops_and_esm(), ops::runtime::deno_runtime::init_ops("deno:runtime".parse().unwrap()), ops::worker_host::deno_worker_host::init_ops( diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs index cf03841962..2611b6f345 100644 --- a/runtime/web_worker.rs +++ b/runtime/web_worker.rs @@ -43,7 +43,7 @@ use deno_fs::FileSystem; use deno_http::DefaultHttpPropertyExtractor; use deno_io::Stdio; use deno_kv::dynamic::MultiBackendDbHandler; -use deno_node::NodeResolver; +use deno_node::NodeExtInitServices; use deno_permissions::PermissionsContainer; use deno_terminal::colors; use deno_tls::RootCertStoreProvider; @@ -364,8 +364,7 @@ pub struct WebWorkerOptions { pub seed: Option, pub fs: Arc, pub module_loader: Rc, - pub node_resolver: Option>, - pub npm_resolver: Option>, + pub node_services: Option, pub create_web_worker_cb: Arc, pub format_js_error_fn: Option>, pub worker_type: WebWorkerType, @@ -490,8 +489,7 @@ impl WebWorker { options.fs.clone(), ), deno_node::deno_node::init_ops_and_esm::( - options.node_resolver, - options.npm_resolver, + options.node_services, options.fs, ), // Runtime ops that are always initialized for WebWorkers diff --git a/runtime/worker.rs b/runtime/worker.rs index fc11be5824..bd67c87067 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -38,6 +38,7 @@ use deno_fs::FileSystem; use deno_http::DefaultHttpPropertyExtractor; use deno_io::Stdio; use deno_kv::dynamic::MultiBackendDbHandler; +use deno_node::NodeExtInitServices; use deno_permissions::PermissionsContainer; use deno_tls::RootCertStoreProvider; use deno_tls::TlsKeys; @@ -155,8 +156,7 @@ pub struct WorkerOptions { /// If not provided runtime will error if code being /// executed tries to load modules. pub module_loader: Rc, - pub node_resolver: Option>, - pub npm_resolver: Option>, + pub node_services: Option, // Callbacks invoked when creating new instance of WebWorker pub create_web_worker_cb: Arc, pub format_js_error_fn: Option>, @@ -224,8 +224,7 @@ impl Default for WorkerOptions { cache_storage_dir: Default::default(), broadcast_channel: Default::default(), root_cert_store_provider: Default::default(), - node_resolver: Default::default(), - npm_resolver: Default::default(), + node_services: Default::default(), blob_store: Default::default(), extensions: Default::default(), startup_snapshot: Default::default(), @@ -414,8 +413,7 @@ impl MainWorker { options.fs.clone(), ), deno_node::deno_node::init_ops_and_esm::( - options.node_resolver, - options.npm_resolver, + options.node_services, options.fs, ), // Ops from this crate