1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

refactor: decouple node resolution from deno_core (#24724)

This commit is contained in:
David Sherret 2024-07-25 19:08:14 -04:00 committed by GitHub
parent 0cf7f268a7
commit 3bf147fe28
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 939 additions and 518 deletions

22
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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

View file

@ -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,

View file

@ -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(),
)))

View file

@ -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;

View file

@ -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(

View file

@ -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?;

View file

@ -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<CliCjsCodeAnalyzer>;
pub type CliNodeCodeTranslator =
NodeCodeTranslator<CliCjsCodeAnalyzer, DenoFsNodeResolverEnv>;
/// Resolves a specifier that is pointing into a node_modules folder.
///

View file

@ -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<PathBuf>,
}
impl ByonmCliNpmResolver {
fn load_pkg_json(
&self,
path: &Path,
) -> Result<Option<Arc<PackageJson>>, 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<Self>) -> Arc<dyn NpmResolver> {
self
}
fn into_require_resolver(self: Arc<Self>) -> Arc<dyn NodeRequireResolver> {
self
}
fn into_process_state_provider(
self: Arc<Self>,
) -> Arc<dyn NpmProcessStateProvider> {
self
}
fn clone_snapshotted(&self) -> Arc<dyn CliNpmResolver> {
Arc::new(Self {
fs: self.fs.clone(),

View file

@ -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<Self>) -> Arc<dyn NpmResolver> {
self
}
fn into_require_resolver(self: Arc<Self>) -> Arc<dyn NodeRequireResolver> {
self
}
fn into_process_state_provider(
self: Arc<Self>,
) -> Arc<dyn NpmProcessStateProvider> {
self
}
fn clone_snapshotted(&self) -> Arc<dyn CliNpmResolver> {
// create a new snapshotted npm resolution and resolver
let npm_resolution = Arc::new(NpmResolution::new(

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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<Self>) -> Arc<dyn NpmResolver>;
fn into_require_resolver(self: Arc<Self>) -> Arc<dyn NodeRequireResolver>;
fn into_process_state_provider(
self: Arc<Self>,
) -> Arc<dyn NpmProcessStateProvider>;
fn clone_snapshotted(&self) -> Arc<dyn CliNpmResolver>;

View file

@ -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;

View file

@ -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(),
));

View file

@ -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

View file

@ -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;

View file

@ -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),

View file

@ -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" },

View file

@ -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<T>(std::sync::Arc<std::sync::Mutex<T>>);
impl<T> MaybeArcMutex<T> {
pub fn new(val: T) -> Self {
Self(std::sync::Arc::new(std::sync::Mutex::new(val)))
}
}
impl<'lock, T> MaybeArcMutex<T> {
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<T> MaybeSync for T where T: ?Sized {}
pub trait MaybeSend {}
impl<T> 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<T>(std::rc::Rc<std::cell::RefCell<T>>);
impl<T> MaybeArcMutex<T> {
pub fn new(val: T) -> Self {
Self(std::rc::Rc::new(std::cell::RefCell::new(val)))
}
}
impl<'lock, T> MaybeArcMutex<T> {
pub fn lock(&'lock self) -> MaybeArcMutexGuard<'lock, T> {
MaybeArcMutexGuard(self.0.borrow_mut())
}
}
}

View file

@ -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"

View file

@ -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()`

View file

@ -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<dyn NpmResolver>;
pub type NpmProcessStateProviderRc =
deno_fs::sync::MaybeArc<dyn NpmProcessStateProvider>;
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<PathBuf, errors::PackageFolderResolveError>;
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<dyn NodeRequireResolver>;
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<v8::Value>) -> bool {
#[op2]
#[string]
fn op_npm_process_state(state: &mut OpState) -> Result<String, AnyError> {
let npm_resolver = state.borrow_mut::<NpmResolverRc>();
let npm_resolver = state.borrow_mut::<NpmProcessStateProviderRc>();
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<NodeResolverRc>,
maybe_npm_resolver: Option<NpmResolverRc>,
maybe_init: Option<NodeExtInitServices>,
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<DenoFsNodeResolverEnv>;
#[allow(clippy::disallowed_types)]
pub type NodeResolverRc =
deno_fs::sync::MaybeArc<node_resolver::NodeResolver<DenoFsNodeResolverEnv>>;
#[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<std::path::PathBuf> {
self
.fs
.realpath_sync(path)
.map_err(|err| err.into_io_error())
}
fn stat_sync(
&self,
path: &std::path::Path,
) -> std::io::Result<node_resolver::env::NodeResolverFsStat> {
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<String, std::io::Error> {
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<String, std::io::Error> {
self
.0
.read_text_file_lossy_sync(path, None)
.map_err(|err| err.into_io_error())
}
}

View file

@ -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<P>(
where
P: NodePermissions + 'static,
{
let resolver = state.borrow::<NpmResolverRc>().clone();
let resolver = state.borrow::<NodeRequireResolverRc>().clone();
let permissions = state.borrow_mut::<P>();
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)?))

View file

@ -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<P>(
state: &mut OpState,
@ -21,7 +21,7 @@ fn ensure_read_permission<P>(
where
P: NodePermissions + 'static,
{
let resolver = state.borrow::<NpmResolverRc>().clone();
let resolver = state.borrow::<NodeRequireResolverRc>().clone();
let permissions = state.borrow_mut::<P>();
resolver.ensure_read_permission(permissions, file_path)
}
@ -64,9 +64,9 @@ where
}
let node_resolver = state.borrow::<NodeResolverRc>();
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")),
}
}

View file

@ -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<T> {
fn clean(&self) -> T;
}
impl PathClean<PathBuf> 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::<PathBuf>()
} 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()),
}
}

View file

@ -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] = &[

View file

@ -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

View file

@ -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.

View file

@ -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<String>,
) -> Result<CjsAnalysis, AnyError>;
}
pub struct NodeCodeTranslator<TCjsCodeAnalyzer: CjsCodeAnalyzer> {
pub struct NodeCodeTranslator<
TCjsCodeAnalyzer: CjsCodeAnalyzer,
TNodeResolverEnv: NodeResolverEnv,
> {
cjs_code_analyzer: TCjsCodeAnalyzer,
fs: deno_fs::FileSystemRc,
node_resolver: NodeResolverRc,
env: TNodeResolverEnv,
node_resolver: NodeResolverRc<TNodeResolverEnv>,
npm_resolver: NpmResolverRc,
}
impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
NodeCodeTranslator<TCjsCodeAnalyzer, TNodeResolverEnv>
{
pub fn new(
cjs_code_analyzer: TCjsCodeAnalyzer,
fs: deno_fs::FileSystemRc,
node_resolver: NodeResolverRc,
env: TNodeResolverEnv,
node_resolver: NodeResolverRc<TNodeResolverEnv>,
npm_resolver: NpmResolverRc,
) -> Self {
Self {
cjs_code_analyzer,
fs,
env,
node_resolver,
npm_resolver,
}
@ -85,7 +90,7 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
/// 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<String>,
) -> Result<String, AnyError> {
let mut temp_var_count = 0;
@ -173,7 +178,7 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
type AnalysisFuture<'a> = LocalBoxFuture<'a, Result<Analysis, AnyError>>;
let mut handled_reexports: HashSet<ModuleSpecifier> = HashSet::default();
let mut handled_reexports: HashSet<Url> = HashSet::default();
handled_reexports.insert(entry_specifier.clone());
let mut analyze_futures: FuturesUnordered<AnalysisFuture<'a>> =
FuturesUnordered::new();
@ -282,10 +287,10 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
fn resolve(
&self,
specifier: &str,
referrer: &ModuleSpecifier,
referrer: &Url,
conditions: &[&str],
mode: NodeResolutionMode,
) -> Result<ModuleSpecifier, AnyError> {
) -> Result<Url, AnyError> {
if specifier.starts_with('/') {
todo!();
}
@ -305,14 +310,14 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
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<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
// 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<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
referrer: &Path,
) -> Result<PathBuf, AnyError> {
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<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
{
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);
}
}

View file

@ -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" },
]

39
ext/node_resolver/env.rs Normal file
View file

@ -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<PathBuf>;
fn stat_sync(&self, path: &Path) -> std::io::Result<NodeResolverFsStat>;
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;
}

View file

@ -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<String>,
}
@ -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<String>,
}
@ -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<ModuleSpecifier>,
pub maybe_referrer: Option<Url>,
pub referrer_kind: NodeModuleKind,
pub mode: NodeResolutionMode,
}
@ -333,8 +333,8 @@ pub struct TypesNotFoundError(pub Box<TypesNotFoundErrorData>);
#[derive(Debug)]
pub struct TypesNotFoundErrorData {
pub code_specifier: ModuleSpecifier,
pub maybe_referrer: Option<ModuleSpecifier>,
pub code_specifier: Url,
pub maybe_referrer: Option<Url>,
}
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<PathBuf>,
pub maybe_referrer: Option<ModuleSpecifier>,
pub maybe_referrer: Option<Url>,
}
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<ModuleSpecifier>,
pub specifier: Url,
pub maybe_referrer: Option<Url>,
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<ModuleSpecifier>,
pub dir_url: Url,
pub maybe_referrer: Option<Url>,
}
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<ModuleSpecifier>,
pub maybe_referrer: Option<Url>,
}
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<ModuleSpecifier>,
pub maybe_referrer: Option<Url>,
pub mode: NodeResolutionMode,
}

26
ext/node_resolver/lib.rs Normal file
View file

@ -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;

41
ext/node_resolver/npm.rs Normal file
View file

@ -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<dyn NpmResolver>;
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<PathBuf, errors::PackageFolderResolveError>;
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)
}
}

View file

@ -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<String, std::io::Error> {
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<Option<PackageJsonRc>, 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, .. })

142
ext/node_resolver/path.rs Normal file
View file

@ -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<T> {
fn clean(&self) -> T;
}
impl PathClean<PathBuf> 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::<PathBuf>()
} 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)
);
}
}
}

View file

@ -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<Self>,
) -> (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<NodeResolver>;
pub type NodeResolverRc<TEnv> = crate::sync::MaybeArc<NodeResolver<TEnv>>;
#[derive(Debug)]
pub struct NodeResolver {
fs: FileSystemRc,
pub struct NodeResolver<TEnv: NodeResolverEnv> {
env: TEnv,
npm_resolver: NpmResolverRc,
in_npm_package_cache: deno_fs::sync::MaybeArcMutex<HashMap<String, bool>>,
in_npm_package_cache: crate::sync::MaybeArcMutex<HashMap<String, bool>>,
}
impl NodeResolver {
pub fn new(fs: FileSystemRc, npm_resolver: NpmResolverRc) -> Self {
impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
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<NodeResolution, NodeResolveError> {
// 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<ModuleSpecifier, NodeResolveError> {
) -> Result<Url, NodeResolveError> {
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<ModuleSpecifier, FinalizeResolutionError> {
resolved: Url,
maybe_referrer: Option<&Url>,
) -> Result<Url, FinalizeResolutionError> {
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<NodeResolution, ResolvePkgSubpathFromDenoModuleError> {
let node_module_kind = NodeModuleKind::Esm;
@ -430,7 +427,7 @@ impl NodeResolver {
pub fn url_to_node_resolution(
&self,
url: ModuleSpecifier,
url: Url,
) -> Result<NodeResolution, UrlToNodeResolutionError> {
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<ModuleSpecifier, TypesNotFoundError> {
fn probe_extensions(
fs: &dyn deno_fs::FileSystem,
) -> Result<Url, TypesNotFoundError> {
fn probe_extensions<TEnv: NodeResolverEnv>(
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<ModuleSpecifier, PackageImportsResolveError> {
) -> Result<Url, PackageImportsResolveError> {
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<ModuleSpecifier, PackageTargetResolveError> {
) -> Result<Url, PackageTargetResolveError> {
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<Option<ModuleSpecifier>, PackageTargetResolveError> {
) -> Result<Option<Url>, 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<Option<ModuleSpecifier>, PackageTargetResolveError> {
) -> Result<Option<Url>, 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<String, Value>,
maybe_referrer: Option<&ModuleSpecifier>,
maybe_referrer: Option<&Url>,
referrer_kind: NodeModuleKind,
conditions: &[&str],
mode: NodeResolutionMode,
) -> Result<ModuleSpecifier, PackageExportsResolveError> {
) -> Result<Url, PackageExportsResolveError> {
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<ModuleSpecifier, PackageResolveError> {
) -> Result<Url, PackageResolveError> {
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<ModuleSpecifier, PackageResolveError> {
) -> Result<Url, PackageResolveError> {
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<ModuleSpecifier, PackageResolveError> {
) -> Result<Url, PackageResolveError> {
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<ModuleSpecifier, PackageSubpathResolveError> {
) -> Result<Url, PackageSubpathResolveError> {
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<ModuleSpecifier, PackageSubpathResolveError> {
) -> Result<Url, PackageSubpathResolveError> {
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<ModuleSpecifier, TypesNotFoundError> {
) -> Result<Url, TypesNotFoundError> {
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<ModuleSpecifier, LegacyResolveError> {
) -> Result<Url, LegacyResolveError> {
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<Option<PackageJsonRc>, ClosestPkgJsonError> {
let Ok(file_path) = url.to_file_path() else {
return Ok(None);
@ -1380,10 +1374,10 @@ impl NodeResolver {
) -> Result<Option<PackageJsonRc>, 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<Option<PackageJsonRc>, 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<ModuleSpecifier, LegacyResolveError> {
) -> Result<Url, LegacyResolveError> {
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<ModuleSpecifier, LegacyResolveError> {
) -> Result<Url, LegacyResolveError> {
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::*;

86
ext/node_resolver/sync.rs Normal file
View file

@ -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<T>(std::sync::Arc<std::sync::Mutex<T>>);
impl<T> MaybeArcMutex<T> {
pub fn new(val: T) -> Self {
Self(std::sync::Arc::new(std::sync::Mutex::new(val)))
}
}
impl<'lock, T> MaybeArcMutex<T> {
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<T>(std::rc::Rc<std::cell::RefCell<T>>);
impl<T> MaybeArcMutex<T> {
pub fn new(val: T) -> Self {
Self(std::rc::Rc::new(std::cell::RefCell::new(val)))
}
}
impl<'lock, T> MaybeArcMutex<T> {
pub fn lock(&'lock self) -> MaybeArcMutexGuard<'lock, T> {
MaybeArcMutexGuard(self.0.borrow_mut())
}
}
pub trait MaybeSync {}
impl<T> MaybeSync for T where T: ?Sized {}
pub trait MaybeSend {}
impl<T> MaybeSend for T where T: ?Sized {}
}

View file

@ -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

View file

@ -254,7 +254,7 @@ pub fn create_runtime_snapshot(
deno_http::deno_http::init_ops_and_esm::<DefaultHttpPropertyExtractor>(),
deno_io::deno_io::init_ops_and_esm(Default::default()),
deno_fs::deno_fs::init_ops_and_esm::<Permissions>(fs.clone()),
deno_node::deno_node::init_ops_and_esm::<Permissions>(None, None, fs),
deno_node::deno_node::init_ops_and_esm::<Permissions>(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(

View file

@ -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<u64>,
pub fs: Arc<dyn FileSystem>,
pub module_loader: Rc<dyn ModuleLoader>,
pub node_resolver: Option<Arc<NodeResolver>>,
pub npm_resolver: Option<Arc<dyn deno_node::NpmResolver>>,
pub node_services: Option<NodeExtInitServices>,
pub create_web_worker_cb: Arc<ops::worker_host::CreateWebWorkerCb>,
pub format_js_error_fn: Option<Arc<FormatJsErrorFn>>,
pub worker_type: WebWorkerType,
@ -490,8 +489,7 @@ impl WebWorker {
options.fs.clone(),
),
deno_node::deno_node::init_ops_and_esm::<PermissionsContainer>(
options.node_resolver,
options.npm_resolver,
options.node_services,
options.fs,
),
// Runtime ops that are always initialized for WebWorkers

View file

@ -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<dyn ModuleLoader>,
pub node_resolver: Option<Arc<deno_node::NodeResolver>>,
pub npm_resolver: Option<Arc<dyn deno_node::NpmResolver>>,
pub node_services: Option<NodeExtInitServices>,
// Callbacks invoked when creating new instance of WebWorker
pub create_web_worker_cb: Arc<ops::worker_host::CreateWebWorkerCb>,
pub format_js_error_fn: Option<Arc<FormatJsErrorFn>>,
@ -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::<PermissionsContainer>(
options.node_resolver,
options.npm_resolver,
options.node_services,
options.fs,
),
// Ops from this crate