1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-20 22:34:46 -05:00
denoland-deno/resolvers/deno/npm/mod.rs
David Sherret 115a306656
fix(node): correct resolution of dynamic import of esm from cjs (#27071)
Ensures a dynamic import in a CJS file will consider the referrer as an import for node resolution.

Also adds fixes (adds) support for `"resolution-mode"` in TypeScript.
2024-11-26 14:38:24 -05:00

276 lines
9.2 KiB
Rust

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::fmt::Debug;
use std::path::PathBuf;
use std::sync::Arc;
use boxed_error::Boxed;
use deno_semver::npm::NpmPackageReqReference;
use deno_semver::package::PackageReq;
use node_resolver::env::NodeResolverEnv;
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::PackageSubpathResolveError;
use node_resolver::InNpmPackageChecker;
use node_resolver::NodeResolution;
use node_resolver::NodeResolutionKind;
use node_resolver::NodeResolver;
use node_resolver::ResolutionMode;
use thiserror::Error;
use url::Url;
use crate::fs::DenoResolverFs;
pub use byonm::ByonmInNpmPackageChecker;
pub use byonm::ByonmNpmResolver;
pub use byonm::ByonmNpmResolverCreateOptions;
pub use byonm::ByonmResolvePkgFolderFromDenoReqError;
pub use local::normalize_pkg_name_for_node_modules_deno_folder;
mod byonm;
mod local;
#[derive(Debug, Error)]
#[error("Could not resolve \"{}\", but found it in a package.json. Deno expects the node_modules/ directory to be up to date. Did you forget to run `deno install`?", specifier)]
pub struct NodeModulesOutOfDateError {
pub specifier: String,
}
#[derive(Debug, Error)]
#[error("Could not find '{}'. Deno expects the node_modules/ directory to be up to date. Did you forget to run `deno install`?", package_json_path.display())]
pub struct MissingPackageNodeModulesFolderError {
pub package_json_path: PathBuf,
}
#[derive(Debug, Boxed)]
pub struct ResolveIfForNpmPackageError(
pub Box<ResolveIfForNpmPackageErrorKind>,
);
#[derive(Debug, Error)]
pub enum ResolveIfForNpmPackageErrorKind {
#[error(transparent)]
NodeResolve(#[from] NodeResolveError),
#[error(transparent)]
NodeModulesOutOfDate(#[from] NodeModulesOutOfDateError),
}
#[derive(Debug, Boxed)]
pub struct ResolveReqWithSubPathError(pub Box<ResolveReqWithSubPathErrorKind>);
#[derive(Debug, Error)]
pub enum ResolveReqWithSubPathErrorKind {
#[error(transparent)]
MissingPackageNodeModulesFolder(#[from] MissingPackageNodeModulesFolderError),
#[error(transparent)]
ResolvePkgFolderFromDenoReq(#[from] ResolvePkgFolderFromDenoReqError),
#[error(transparent)]
PackageSubpathResolve(#[from] PackageSubpathResolveError),
}
#[derive(Debug, Error)]
pub enum ResolvePkgFolderFromDenoReqError {
// todo(dsherret): don't use anyhow here
#[error(transparent)]
Managed(anyhow::Error),
#[error(transparent)]
Byonm(#[from] ByonmResolvePkgFolderFromDenoReqError),
}
// todo(dsherret): a temporary trait until we extract
// out the CLI npm resolver into here
pub trait CliNpmReqResolver: Debug + Send + Sync {
fn resolve_pkg_folder_from_deno_module_req(
&self,
req: &PackageReq,
referrer: &Url,
) -> Result<PathBuf, ResolvePkgFolderFromDenoReqError>;
}
pub struct NpmReqResolverOptions<
Fs: DenoResolverFs,
TNodeResolverEnv: NodeResolverEnv,
> {
/// The resolver when "bring your own node_modules" is enabled where Deno
/// does not setup the node_modules directories automatically, but instead
/// uses what already exists on the file system.
pub byonm_resolver: Option<Arc<ByonmNpmResolver<Fs, TNodeResolverEnv>>>,
pub fs: Fs,
pub in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>,
pub node_resolver: Arc<NodeResolver<TNodeResolverEnv>>,
pub npm_req_resolver: Arc<dyn CliNpmReqResolver>,
}
#[derive(Debug)]
pub struct NpmReqResolver<Fs: DenoResolverFs, TNodeResolverEnv: NodeResolverEnv>
{
byonm_resolver: Option<Arc<ByonmNpmResolver<Fs, TNodeResolverEnv>>>,
fs: Fs,
in_npm_pkg_checker: Arc<dyn InNpmPackageChecker>,
node_resolver: Arc<NodeResolver<TNodeResolverEnv>>,
npm_resolver: Arc<dyn CliNpmReqResolver>,
}
impl<Fs: DenoResolverFs, TNodeResolverEnv: NodeResolverEnv>
NpmReqResolver<Fs, TNodeResolverEnv>
{
pub fn new(options: NpmReqResolverOptions<Fs, TNodeResolverEnv>) -> Self {
Self {
byonm_resolver: options.byonm_resolver,
fs: options.fs,
in_npm_pkg_checker: options.in_npm_pkg_checker,
node_resolver: options.node_resolver,
npm_resolver: options.npm_req_resolver,
}
}
pub fn resolve_req_reference(
&self,
req_ref: &NpmPackageReqReference,
referrer: &Url,
resolution_mode: ResolutionMode,
resolution_kind: NodeResolutionKind,
) -> Result<Url, ResolveReqWithSubPathError> {
self.resolve_req_with_sub_path(
req_ref.req(),
req_ref.sub_path(),
referrer,
resolution_mode,
resolution_kind,
)
}
pub fn resolve_req_with_sub_path(
&self,
req: &PackageReq,
sub_path: Option<&str>,
referrer: &Url,
resolution_mode: ResolutionMode,
resolution_kind: NodeResolutionKind,
) -> Result<Url, ResolveReqWithSubPathError> {
let package_folder = self
.npm_resolver
.resolve_pkg_folder_from_deno_module_req(req, referrer)?;
let resolution_result =
self.node_resolver.resolve_package_subpath_from_deno_module(
&package_folder,
sub_path,
Some(referrer),
resolution_mode,
resolution_kind,
);
match resolution_result {
Ok(url) => Ok(url),
Err(err) => {
if self.byonm_resolver.is_some() {
let package_json_path = package_folder.join("package.json");
if !self.fs.exists_sync(&package_json_path) {
return Err(
MissingPackageNodeModulesFolderError { package_json_path }.into(),
);
}
}
Err(err.into())
}
}
}
pub fn resolve_if_for_npm_pkg(
&self,
specifier: &str,
referrer: &Url,
resolution_mode: ResolutionMode,
resolution_kind: NodeResolutionKind,
) -> Result<Option<NodeResolution>, ResolveIfForNpmPackageError> {
let resolution_result = self.node_resolver.resolve(
specifier,
referrer,
resolution_mode,
resolution_kind,
);
match resolution_result {
Ok(res) => Ok(Some(res)),
Err(err) => {
let err = err.into_kind();
match err {
NodeResolveErrorKind::RelativeJoin(_)
| NodeResolveErrorKind::PackageImportsResolve(_)
| NodeResolveErrorKind::UnsupportedEsmUrlScheme(_)
| NodeResolveErrorKind::DataUrlReferrer(_)
| NodeResolveErrorKind::TypesNotFound(_)
| NodeResolveErrorKind::FinalizeResolution(_) => Err(
ResolveIfForNpmPackageErrorKind::NodeResolve(err.into()).into_box(),
),
NodeResolveErrorKind::PackageResolve(err) => {
let err = err.into_kind();
match err {
PackageResolveErrorKind::ClosestPkgJson(_)
| PackageResolveErrorKind::InvalidModuleSpecifier(_)
| PackageResolveErrorKind::ExportsResolve(_)
| PackageResolveErrorKind::SubpathResolve(_) => Err(
ResolveIfForNpmPackageErrorKind::NodeResolve(
NodeResolveErrorKind::PackageResolve(err.into()).into(),
)
.into_box(),
),
PackageResolveErrorKind::PackageFolderResolve(err) => {
match err.as_kind() {
PackageFolderResolveErrorKind::Io(
PackageFolderResolveIoError { package_name, .. },
)
| PackageFolderResolveErrorKind::PackageNotFound(
PackageNotFoundError { package_name, .. },
) => {
if self.in_npm_pkg_checker.in_npm_package(referrer) {
return Err(
ResolveIfForNpmPackageErrorKind::NodeResolve(
NodeResolveErrorKind::PackageResolve(err.into())
.into(),
)
.into_box(),
);
}
if let Some(byonm_npm_resolver) = &self.byonm_resolver {
if byonm_npm_resolver
.find_ancestor_package_json_with_dep(
package_name,
referrer,
)
.is_some()
{
return Err(
ResolveIfForNpmPackageErrorKind::NodeModulesOutOfDate(
NodeModulesOutOfDateError {
specifier: specifier.to_string(),
},
).into_box(),
);
}
}
Ok(None)
}
PackageFolderResolveErrorKind::ReferrerNotFound(_) => {
if self.in_npm_pkg_checker.in_npm_package(referrer) {
return Err(
ResolveIfForNpmPackageErrorKind::NodeResolve(
NodeResolveErrorKind::PackageResolve(err.into())
.into(),
)
.into_box(),
);
}
Ok(None)
}
}
}
}
}
}
}
}
}
}