mirror of
https://github.com/denoland/deno.git
synced 2025-10-03 07:34:36 +00:00
fix: improved support for cjs and cts modules (#26558)
* cts support * better cjs/cts type checking * deno compile cjs/cts support * More efficient detect cjs (going towards stabilization) * Determination of whether .js, .ts, .jsx, or .tsx is cjs or esm is only done after loading * Support `import x = require(...);` Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
parent
4774eab64d
commit
826e42a5b5
143 changed files with 2418 additions and 1527 deletions
|
@ -15,13 +15,3 @@ pub trait DenoResolverFs {
|
|||
fn is_dir_sync(&self, path: &Path) -> bool;
|
||||
fn read_dir_sync(&self, dir_path: &Path) -> std::io::Result<Vec<DirEntry>>;
|
||||
}
|
||||
|
||||
pub(crate) struct DenoPkgJsonFsAdapter<'a, Fs: DenoResolverFs>(pub &'a Fs);
|
||||
|
||||
impl<'a, Fs: DenoResolverFs> deno_package_json::fs::DenoPkgJsonFs
|
||||
for DenoPkgJsonFsAdapter<'a, Fs>
|
||||
{
|
||||
fn read_to_string_lossy(&self, path: &Path) -> std::io::Result<String> {
|
||||
self.0.read_to_string_lossy(path)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,16 +10,17 @@ use deno_package_json::PackageJsonDepValue;
|
|||
use deno_path_util::url_to_file_path;
|
||||
use deno_semver::package::PackageReq;
|
||||
use deno_semver::Version;
|
||||
use node_resolver::env::NodeResolverEnv;
|
||||
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::InNpmPackageChecker;
|
||||
use node_resolver::NpmResolver;
|
||||
use node_resolver::PackageJsonResolverRc;
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
use crate::fs::DenoPkgJsonFsAdapter;
|
||||
use crate::fs::DenoResolverFs;
|
||||
|
||||
use super::local::normalize_pkg_name_for_node_modules_deno_folder;
|
||||
|
@ -36,32 +37,41 @@ pub enum ByonmResolvePkgFolderFromDenoReqError {
|
|||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
pub struct ByonmNpmResolverCreateOptions<Fs: DenoResolverFs> {
|
||||
pub fs: Fs,
|
||||
pub struct ByonmNpmResolverCreateOptions<
|
||||
Fs: DenoResolverFs,
|
||||
TEnv: NodeResolverEnv,
|
||||
> {
|
||||
// todo(dsherret): investigate removing this
|
||||
pub root_node_modules_dir: Option<PathBuf>,
|
||||
pub fs: Fs,
|
||||
pub pkg_json_resolver: PackageJsonResolverRc<TEnv>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ByonmNpmResolver<Fs: DenoResolverFs> {
|
||||
pub struct ByonmNpmResolver<Fs: DenoResolverFs, TEnv: NodeResolverEnv> {
|
||||
fs: Fs,
|
||||
pkg_json_resolver: PackageJsonResolverRc<TEnv>,
|
||||
root_node_modules_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl<Fs: DenoResolverFs + Clone> Clone for ByonmNpmResolver<Fs> {
|
||||
impl<Fs: DenoResolverFs + Clone, TEnv: NodeResolverEnv> Clone
|
||||
for ByonmNpmResolver<Fs, TEnv>
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
fs: self.fs.clone(),
|
||||
pkg_json_resolver: self.pkg_json_resolver.clone(),
|
||||
root_node_modules_dir: self.root_node_modules_dir.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fs: DenoResolverFs> ByonmNpmResolver<Fs> {
|
||||
pub fn new(options: ByonmNpmResolverCreateOptions<Fs>) -> Self {
|
||||
impl<Fs: DenoResolverFs, TEnv: NodeResolverEnv> ByonmNpmResolver<Fs, TEnv> {
|
||||
pub fn new(options: ByonmNpmResolverCreateOptions<Fs, TEnv>) -> Self {
|
||||
Self {
|
||||
fs: options.fs,
|
||||
root_node_modules_dir: options.root_node_modules_dir,
|
||||
fs: options.fs,
|
||||
pkg_json_resolver: options.pkg_json_resolver,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,7 +83,7 @@ impl<Fs: DenoResolverFs> ByonmNpmResolver<Fs> {
|
|||
&self,
|
||||
path: &Path,
|
||||
) -> Result<Option<Arc<PackageJson>>, PackageJsonLoadError> {
|
||||
load_pkg_json(&DenoPkgJsonFsAdapter(&self.fs), path)
|
||||
self.pkg_json_resolver.load_package_json(path)
|
||||
}
|
||||
|
||||
/// Finds the ancestor package.json that contains the specified dependency.
|
||||
|
@ -290,8 +300,10 @@ impl<Fs: DenoResolverFs> ByonmNpmResolver<Fs> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<Fs: DenoResolverFs + Send + Sync + std::fmt::Debug> NpmResolver
|
||||
for ByonmNpmResolver<Fs>
|
||||
impl<
|
||||
Fs: DenoResolverFs + Send + Sync + std::fmt::Debug,
|
||||
TEnv: NodeResolverEnv,
|
||||
> NpmResolver for ByonmNpmResolver<Fs, TEnv>
|
||||
{
|
||||
fn resolve_package_folder_from_package(
|
||||
&self,
|
||||
|
@ -342,7 +354,12 @@ impl<Fs: DenoResolverFs + Send + Sync + std::fmt::Debug> NpmResolver
|
|||
.into()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ByonmInNpmPackageChecker;
|
||||
|
||||
impl InNpmPackageChecker for ByonmInNpmPackageChecker {
|
||||
fn in_npm_package(&self, specifier: &Url) -> bool {
|
||||
specifier.scheme() == "file"
|
||||
&& specifier
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
mod byonm;
|
||||
mod local;
|
||||
|
||||
pub use byonm::ByonmInNpmPackageChecker;
|
||||
pub use byonm::ByonmNpmResolver;
|
||||
pub use byonm::ByonmNpmResolverCreateOptions;
|
||||
pub use byonm::ByonmResolvePkgFolderFromDenoReqError;
|
||||
|
|
|
@ -232,7 +232,7 @@ impl<Fs: SloppyImportResolverFs> SloppyImportsResolver<Fs> {
|
|||
| MediaType::Tsx
|
||||
| MediaType::Json
|
||||
| MediaType::Wasm
|
||||
| MediaType::TsBuildInfo
|
||||
| MediaType::Css
|
||||
| MediaType::SourceMap => {
|
||||
return None;
|
||||
}
|
||||
|
|
|
@ -19,18 +19,19 @@ use anyhow::Error as AnyError;
|
|||
use url::Url;
|
||||
|
||||
use crate::env::NodeResolverEnv;
|
||||
use crate::package_json::load_pkg_json;
|
||||
use crate::npm::InNpmPackageCheckerRc;
|
||||
use crate::resolution::NodeResolverRc;
|
||||
use crate::NodeModuleKind;
|
||||
use crate::NodeResolutionMode;
|
||||
use crate::NpmResolverRc;
|
||||
use crate::PackageJsonResolverRc;
|
||||
use crate::PathClean;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CjsAnalysis {
|
||||
pub enum CjsAnalysis<'a> {
|
||||
/// File was found to be an ES module and the translator should
|
||||
/// load the code as ESM.
|
||||
Esm(String),
|
||||
Esm(Cow<'a, str>),
|
||||
Cjs(CjsAnalysisExports),
|
||||
}
|
||||
|
||||
|
@ -50,11 +51,11 @@ pub trait CjsCodeAnalyzer {
|
|||
/// already has it. If the source is needed by the implementation,
|
||||
/// then it can use the provided source, or otherwise load it if
|
||||
/// necessary.
|
||||
async fn analyze_cjs(
|
||||
async fn analyze_cjs<'a>(
|
||||
&self,
|
||||
specifier: &Url,
|
||||
maybe_source: Option<String>,
|
||||
) -> Result<CjsAnalysis, AnyError>;
|
||||
maybe_source: Option<Cow<'a, str>>,
|
||||
) -> Result<CjsAnalysis<'a>, AnyError>;
|
||||
}
|
||||
|
||||
pub struct NodeCodeTranslator<
|
||||
|
@ -63,8 +64,10 @@ pub struct NodeCodeTranslator<
|
|||
> {
|
||||
cjs_code_analyzer: TCjsCodeAnalyzer,
|
||||
env: TNodeResolverEnv,
|
||||
in_npm_pkg_checker: InNpmPackageCheckerRc,
|
||||
node_resolver: NodeResolverRc<TNodeResolverEnv>,
|
||||
npm_resolver: NpmResolverRc,
|
||||
pkg_json_resolver: PackageJsonResolverRc<TNodeResolverEnv>,
|
||||
}
|
||||
|
||||
impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
|
||||
|
@ -73,14 +76,18 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
|
|||
pub fn new(
|
||||
cjs_code_analyzer: TCjsCodeAnalyzer,
|
||||
env: TNodeResolverEnv,
|
||||
in_npm_pkg_checker: InNpmPackageCheckerRc,
|
||||
node_resolver: NodeResolverRc<TNodeResolverEnv>,
|
||||
npm_resolver: NpmResolverRc,
|
||||
pkg_json_resolver: PackageJsonResolverRc<TNodeResolverEnv>,
|
||||
) -> Self {
|
||||
Self {
|
||||
cjs_code_analyzer,
|
||||
env,
|
||||
in_npm_pkg_checker,
|
||||
node_resolver,
|
||||
npm_resolver,
|
||||
pkg_json_resolver,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,11 +97,11 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
|
|||
/// For all discovered reexports the analysis will be performed recursively.
|
||||
///
|
||||
/// If successful a source code for equivalent ES module is returned.
|
||||
pub async fn translate_cjs_to_esm(
|
||||
pub async fn translate_cjs_to_esm<'a>(
|
||||
&self,
|
||||
entry_specifier: &Url,
|
||||
source: Option<String>,
|
||||
) -> Result<String, AnyError> {
|
||||
source: Option<Cow<'a, str>>,
|
||||
) -> Result<Cow<'a, str>, AnyError> {
|
||||
let mut temp_var_count = 0;
|
||||
|
||||
let analysis = self
|
||||
|
@ -108,7 +115,7 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
|
|||
};
|
||||
|
||||
let mut source = vec![
|
||||
r#"import {createRequire as __internalCreateRequire} from "node:module";
|
||||
r#"import {createRequire as __internalCreateRequire, Module as __internalModule } from "node:module";
|
||||
const require = __internalCreateRequire(import.meta.url);"#
|
||||
.to_string(),
|
||||
];
|
||||
|
@ -135,7 +142,12 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
|
|||
}
|
||||
|
||||
source.push(format!(
|
||||
"const mod = require(\"{}\");",
|
||||
r#"let mod;
|
||||
if (import.meta.main) {{
|
||||
mod = __internalModule._load("{0}", null, true)
|
||||
}} else {{
|
||||
mod = require("{0}");
|
||||
}}"#,
|
||||
url_to_file_path(entry_specifier)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
|
@ -159,7 +171,7 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
|
|||
source.push("export default mod;".to_string());
|
||||
|
||||
let translated_source = source.join("\n");
|
||||
Ok(translated_source)
|
||||
Ok(Cow::Owned(translated_source))
|
||||
}
|
||||
|
||||
async fn analyze_reexports<'a>(
|
||||
|
@ -174,7 +186,7 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
|
|||
struct Analysis {
|
||||
reexport_specifier: url::Url,
|
||||
referrer: url::Url,
|
||||
analysis: CjsAnalysis,
|
||||
analysis: CjsAnalysis<'static>,
|
||||
}
|
||||
|
||||
type AnalysisFuture<'a> = LocalBoxFuture<'a, Result<Analysis, AnyError>>;
|
||||
|
@ -329,8 +341,9 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
|
|||
}?;
|
||||
|
||||
let package_json_path = module_dir.join("package.json");
|
||||
let maybe_package_json =
|
||||
load_pkg_json(self.env.pkg_json_fs(), &package_json_path)?;
|
||||
let maybe_package_json = self
|
||||
.pkg_json_resolver
|
||||
.load_package_json(&package_json_path)?;
|
||||
if let Some(package_json) = maybe_package_json {
|
||||
if let Some(exports) = &package_json.exports {
|
||||
return Some(
|
||||
|
@ -356,8 +369,9 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
|
|||
if self.env.is_dir_sync(&d) {
|
||||
// subdir might have a package.json that specifies the entrypoint
|
||||
let package_json_path = d.join("package.json");
|
||||
let maybe_package_json =
|
||||
load_pkg_json(self.env.pkg_json_fs(), &package_json_path)?;
|
||||
let maybe_package_json = self
|
||||
.pkg_json_resolver
|
||||
.load_package_json(&package_json_path)?;
|
||||
if let Some(package_json) = maybe_package_json {
|
||||
if let Some(main) = package_json.main(NodeModuleKind::Cjs) {
|
||||
return Ok(Some(url_from_file_path(&d.join(main).clean())?));
|
||||
|
@ -382,7 +396,7 @@ impl<TCjsCodeAnalyzer: CjsCodeAnalyzer, TNodeResolverEnv: NodeResolverEnv>
|
|||
// as a fallback, attempt to resolve it via the ancestor directories
|
||||
let mut last = referrer_path.as_path();
|
||||
while let Some(parent) = last.parent() {
|
||||
if !self.npm_resolver.in_npm_package_at_dir_path(parent) {
|
||||
if !self.in_npm_pkg_checker.in_npm_package_at_dir_path(parent) {
|
||||
break;
|
||||
}
|
||||
let path = if parent.ends_with("node_modules") {
|
||||
|
|
|
@ -81,29 +81,6 @@ pub trait NodeJsErrorCoded {
|
|||
fn code(&self) -> NodeJsErrorCode;
|
||||
}
|
||||
|
||||
kinded_err!(
|
||||
ResolvePkgSubpathFromDenoModuleError,
|
||||
ResolvePkgSubpathFromDenoModuleErrorKind
|
||||
);
|
||||
|
||||
impl NodeJsErrorCoded for ResolvePkgSubpathFromDenoModuleError {
|
||||
fn code(&self) -> NodeJsErrorCode {
|
||||
use ResolvePkgSubpathFromDenoModuleErrorKind::*;
|
||||
match self.as_kind() {
|
||||
PackageSubpathResolve(e) => e.code(),
|
||||
UrlToNodeResolution(e) => e.code(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ResolvePkgSubpathFromDenoModuleErrorKind {
|
||||
#[error(transparent)]
|
||||
PackageSubpathResolve(#[from] PackageSubpathResolveError),
|
||||
#[error(transparent)]
|
||||
UrlToNodeResolution(#[from] UrlToNodeResolutionError),
|
||||
}
|
||||
|
||||
// todo(https://github.com/denoland/deno_core/issues/810): make this a TypeError
|
||||
#[derive(Debug, Clone, Error)]
|
||||
#[error(
|
||||
|
@ -394,37 +371,6 @@ impl NodeJsErrorCoded for CanonicalizingPkgJsonDirError {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("TypeScript files are not supported in npm packages: {specifier}")]
|
||||
pub struct TypeScriptNotSupportedInNpmError {
|
||||
pub specifier: Url,
|
||||
}
|
||||
|
||||
impl NodeJsErrorCoded for TypeScriptNotSupportedInNpmError {
|
||||
fn code(&self) -> NodeJsErrorCode {
|
||||
NodeJsErrorCode::ERR_UNKNOWN_FILE_EXTENSION
|
||||
}
|
||||
}
|
||||
|
||||
kinded_err!(UrlToNodeResolutionError, UrlToNodeResolutionErrorKind);
|
||||
|
||||
impl NodeJsErrorCoded for UrlToNodeResolutionError {
|
||||
fn code(&self) -> NodeJsErrorCode {
|
||||
match self.as_kind() {
|
||||
UrlToNodeResolutionErrorKind::TypeScriptNotSupported(e) => e.code(),
|
||||
UrlToNodeResolutionErrorKind::ClosestPkgJson(e) => e.code(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum UrlToNodeResolutionErrorKind {
|
||||
#[error(transparent)]
|
||||
TypeScriptNotSupported(#[from] TypeScriptNotSupportedInNpmError),
|
||||
#[error(transparent)]
|
||||
ClosestPkgJson(#[from] ClosestPkgJsonError),
|
||||
}
|
||||
|
||||
// todo(https://github.com/denoland/deno_core/issues/810): make this a TypeError
|
||||
#[derive(Debug, Error)]
|
||||
#[error(
|
||||
|
@ -533,8 +479,6 @@ pub enum NodeResolveErrorKind {
|
|||
TypesNotFound(#[from] TypesNotFoundError),
|
||||
#[error(transparent)]
|
||||
FinalizeResolution(#[from] FinalizeResolutionError),
|
||||
#[error(transparent)]
|
||||
UrlToNodeResolution(#[from] UrlToNodeResolutionError),
|
||||
}
|
||||
|
||||
kinded_err!(FinalizeResolutionError, FinalizeResolutionErrorKind);
|
||||
|
@ -728,8 +672,6 @@ pub enum ResolvePkgJsonBinExportError {
|
|||
MissingPkgJson { pkg_json_path: PathBuf },
|
||||
#[error("Failed resolving binary export. {message}")]
|
||||
InvalidBinProperty { message: String },
|
||||
#[error(transparent)]
|
||||
UrlToNodeResolution(#[from] UrlToNodeResolutionError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
|
@ -13,9 +13,12 @@ mod resolution;
|
|||
mod sync;
|
||||
|
||||
pub use deno_package_json::PackageJson;
|
||||
pub use npm::InNpmPackageChecker;
|
||||
pub use npm::InNpmPackageCheckerRc;
|
||||
pub use npm::NpmResolver;
|
||||
pub use npm::NpmResolverRc;
|
||||
pub use package_json::load_pkg_json;
|
||||
pub use package_json::PackageJsonResolver;
|
||||
pub use package_json::PackageJsonResolverRc;
|
||||
pub use package_json::PackageJsonThreadLocalCache;
|
||||
pub use path::PathClean;
|
||||
pub use resolution::parse_npm_pkg_name;
|
||||
|
|
|
@ -22,7 +22,13 @@ pub trait NpmResolver: std::fmt::Debug + MaybeSend + MaybeSync {
|
|||
specifier: &str,
|
||||
referrer: &Url,
|
||||
) -> Result<PathBuf, errors::PackageFolderResolveError>;
|
||||
}
|
||||
|
||||
#[allow(clippy::disallowed_types)]
|
||||
pub type InNpmPackageCheckerRc = crate::sync::MaybeArc<dyn InNpmPackageChecker>;
|
||||
|
||||
/// Checks if a provided specifier is in an npm package.
|
||||
pub trait InNpmPackageChecker: std::fmt::Debug + MaybeSend + MaybeSync {
|
||||
fn in_npm_package(&self, specifier: &Url) -> bool;
|
||||
|
||||
fn in_npm_package_at_dir_path(&self, path: &Path) -> bool {
|
||||
|
|
|
@ -2,15 +2,21 @@
|
|||
|
||||
use deno_package_json::PackageJson;
|
||||
use deno_package_json::PackageJsonRc;
|
||||
use deno_path_util::strip_unc_prefix;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::io::ErrorKind;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use url::Url;
|
||||
|
||||
use crate::env::NodeResolverEnv;
|
||||
use crate::errors::CanonicalizingPkgJsonDirError;
|
||||
use crate::errors::ClosestPkgJsonError;
|
||||
use crate::errors::PackageJsonLoadError;
|
||||
|
||||
// use a thread local cache so that workers have their own distinct cache
|
||||
// todo(dsherret): this isn't exactly correct and we should change it to instead
|
||||
// be created per worker and passed down as a ctor arg to the pkg json resolver
|
||||
thread_local! {
|
||||
static CACHE: RefCell<HashMap<PathBuf, PackageJsonRc>> = RefCell::new(HashMap::new());
|
||||
}
|
||||
|
@ -33,21 +39,91 @@ impl deno_package_json::PackageJsonCache for PackageJsonThreadLocalCache {
|
|||
}
|
||||
}
|
||||
|
||||
/// Helper to load a package.json file using the thread local cache
|
||||
/// in node_resolver.
|
||||
pub fn load_pkg_json(
|
||||
fs: &dyn deno_package_json::fs::DenoPkgJsonFs,
|
||||
path: &Path,
|
||||
) -> Result<Option<PackageJsonRc>, PackageJsonLoadError> {
|
||||
let result =
|
||||
PackageJson::load_from_path(path, fs, Some(&PackageJsonThreadLocalCache));
|
||||
match result {
|
||||
Ok(pkg_json) => Ok(Some(pkg_json)),
|
||||
Err(deno_package_json::PackageJsonLoadError::Io { source, .. })
|
||||
if source.kind() == ErrorKind::NotFound =>
|
||||
{
|
||||
#[allow(clippy::disallowed_types)]
|
||||
pub type PackageJsonResolverRc<TEnv> =
|
||||
crate::sync::MaybeArc<PackageJsonResolver<TEnv>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PackageJsonResolver<TEnv: NodeResolverEnv> {
|
||||
env: TEnv,
|
||||
}
|
||||
|
||||
impl<TEnv: NodeResolverEnv> PackageJsonResolver<TEnv> {
|
||||
pub fn new(env: TEnv) -> Self {
|
||||
Self { env }
|
||||
}
|
||||
|
||||
pub fn get_closest_package_json(
|
||||
&self,
|
||||
url: &Url,
|
||||
) -> Result<Option<PackageJsonRc>, ClosestPkgJsonError> {
|
||||
let Ok(file_path) = deno_path_util::url_to_file_path(url) else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.get_closest_package_json_from_path(&file_path)
|
||||
}
|
||||
|
||||
pub fn get_closest_package_json_from_path(
|
||||
&self,
|
||||
file_path: &Path,
|
||||
) -> Result<Option<PackageJsonRc>, ClosestPkgJsonError> {
|
||||
// we use this for deno compile using byonm because the script paths
|
||||
// won't be in virtual file system, but the package.json paths will be
|
||||
fn canonicalize_first_ancestor_exists<TEnv: NodeResolverEnv>(
|
||||
dir_path: &Path,
|
||||
env: &TEnv,
|
||||
) -> Result<Option<PathBuf>, std::io::Error> {
|
||||
for ancestor in dir_path.ancestors() {
|
||||
match env.realpath_sync(ancestor) {
|
||||
Ok(dir_path) => return Ok(Some(dir_path)),
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
// keep searching
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
Err(err) => Err(PackageJsonLoadError(err)),
|
||||
|
||||
let parent_dir = file_path.parent().unwrap();
|
||||
let Some(start_dir) = canonicalize_first_ancestor_exists(
|
||||
parent_dir, &self.env,
|
||||
)
|
||||
.map_err(|source| CanonicalizingPkgJsonDirError {
|
||||
dir_path: parent_dir.to_path_buf(),
|
||||
source,
|
||||
})?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
let start_dir = strip_unc_prefix(start_dir);
|
||||
for current_dir in start_dir.ancestors() {
|
||||
let package_json_path = current_dir.join("package.json");
|
||||
if let Some(pkg_json) = self.load_package_json(&package_json_path)? {
|
||||
return Ok(Some(pkg_json));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn load_package_json(
|
||||
&self,
|
||||
path: &Path,
|
||||
) -> Result<Option<PackageJsonRc>, PackageJsonLoadError> {
|
||||
let result = PackageJson::load_from_path(
|
||||
path,
|
||||
self.env.pkg_json_fs(),
|
||||
Some(&PackageJsonThreadLocalCache),
|
||||
);
|
||||
match result {
|
||||
Ok(pkg_json) => Ok(Some(pkg_json)),
|
||||
Err(deno_package_json::PackageJsonLoadError::Io { source, .. })
|
||||
if source.kind() == ErrorKind::NotFound =>
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
Err(err) => Err(PackageJsonLoadError(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,6 @@ use std::path::PathBuf;
|
|||
|
||||
use anyhow::bail;
|
||||
use anyhow::Error as AnyError;
|
||||
use deno_media_type::MediaType;
|
||||
use deno_package_json::PackageJsonRc;
|
||||
use deno_path_util::strip_unc_prefix;
|
||||
use deno_path_util::url_from_file_path;
|
||||
use serde_json::Map;
|
||||
use serde_json::Value;
|
||||
|
@ -16,8 +13,6 @@ use url::Url;
|
|||
|
||||
use crate::env::NodeResolverEnv;
|
||||
use crate::errors;
|
||||
use crate::errors::CanonicalizingPkgJsonDirError;
|
||||
use crate::errors::ClosestPkgJsonError;
|
||||
use crate::errors::DataUrlReferrerError;
|
||||
use crate::errors::FinalizeResolutionError;
|
||||
use crate::errors::InvalidModuleSpecifierError;
|
||||
|
@ -32,7 +27,6 @@ use crate::errors::PackageExportsResolveError;
|
|||
use crate::errors::PackageImportNotDefinedError;
|
||||
use crate::errors::PackageImportsResolveError;
|
||||
use crate::errors::PackageImportsResolveErrorKind;
|
||||
use crate::errors::PackageJsonLoadError;
|
||||
use crate::errors::PackagePathNotExportedError;
|
||||
use crate::errors::PackageResolveError;
|
||||
use crate::errors::PackageSubpathResolveError;
|
||||
|
@ -42,14 +36,13 @@ use crate::errors::PackageTargetResolveError;
|
|||
use crate::errors::PackageTargetResolveErrorKind;
|
||||
use crate::errors::ResolveBinaryCommandsError;
|
||||
use crate::errors::ResolvePkgJsonBinExportError;
|
||||
use crate::errors::ResolvePkgSubpathFromDenoModuleError;
|
||||
use crate::errors::TypeScriptNotSupportedInNpmError;
|
||||
use crate::errors::TypesNotFoundError;
|
||||
use crate::errors::TypesNotFoundErrorData;
|
||||
use crate::errors::UnsupportedDirImportError;
|
||||
use crate::errors::UnsupportedEsmUrlSchemeError;
|
||||
use crate::errors::UrlToNodeResolutionError;
|
||||
use crate::npm::InNpmPackageCheckerRc;
|
||||
use crate::NpmResolverRc;
|
||||
use crate::PackageJsonResolverRc;
|
||||
use crate::PathClean;
|
||||
use deno_package_json::PackageJson;
|
||||
|
||||
|
@ -73,16 +66,14 @@ impl NodeResolutionMode {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum NodeResolution {
|
||||
Esm(Url),
|
||||
CommonJs(Url),
|
||||
Module(Url),
|
||||
BuiltIn(String),
|
||||
}
|
||||
|
||||
impl NodeResolution {
|
||||
pub fn into_url(self) -> Url {
|
||||
match self {
|
||||
Self::Esm(u) => u,
|
||||
Self::CommonJs(u) => u,
|
||||
Self::Module(u) => u,
|
||||
Self::BuiltIn(specifier) => {
|
||||
if specifier.starts_with("node:") {
|
||||
Url::parse(&specifier).unwrap()
|
||||
|
@ -92,42 +83,6 @@ impl NodeResolution {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_specifier_and_media_type(
|
||||
resolution: Option<Self>,
|
||||
) -> (Url, MediaType) {
|
||||
match resolution {
|
||||
Some(NodeResolution::CommonJs(specifier)) => {
|
||||
let media_type = MediaType::from_specifier(&specifier);
|
||||
(
|
||||
specifier,
|
||||
match media_type {
|
||||
MediaType::JavaScript | MediaType::Jsx => MediaType::Cjs,
|
||||
MediaType::TypeScript | MediaType::Tsx => MediaType::Cts,
|
||||
MediaType::Dts => MediaType::Dcts,
|
||||
_ => media_type,
|
||||
},
|
||||
)
|
||||
}
|
||||
Some(NodeResolution::Esm(specifier)) => {
|
||||
let media_type = MediaType::from_specifier(&specifier);
|
||||
(
|
||||
specifier,
|
||||
match media_type {
|
||||
MediaType::JavaScript | MediaType::Jsx => MediaType::Mjs,
|
||||
MediaType::TypeScript | MediaType::Tsx => MediaType::Mts,
|
||||
MediaType::Dts => MediaType::Dmts,
|
||||
_ => media_type,
|
||||
},
|
||||
)
|
||||
}
|
||||
Some(resolution) => (resolution.into_url(), MediaType::Dts),
|
||||
None => (
|
||||
Url::parse("internal:///missing_dependency.d.ts").unwrap(),
|
||||
MediaType::Dts,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::disallowed_types)]
|
||||
|
@ -136,16 +91,28 @@ pub type NodeResolverRc<TEnv> = crate::sync::MaybeArc<NodeResolver<TEnv>>;
|
|||
#[derive(Debug)]
|
||||
pub struct NodeResolver<TEnv: NodeResolverEnv> {
|
||||
env: TEnv,
|
||||
in_npm_pkg_checker: InNpmPackageCheckerRc,
|
||||
npm_resolver: NpmResolverRc,
|
||||
pkg_json_resolver: PackageJsonResolverRc<TEnv>,
|
||||
}
|
||||
|
||||
impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
|
||||
pub fn new(env: TEnv, npm_resolver: NpmResolverRc) -> Self {
|
||||
Self { env, npm_resolver }
|
||||
pub fn new(
|
||||
env: TEnv,
|
||||
in_npm_pkg_checker: InNpmPackageCheckerRc,
|
||||
npm_resolver: NpmResolverRc,
|
||||
pkg_json_resolver: PackageJsonResolverRc<TEnv>,
|
||||
) -> Self {
|
||||
Self {
|
||||
env,
|
||||
in_npm_pkg_checker,
|
||||
npm_resolver,
|
||||
pkg_json_resolver,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn in_npm_package(&self, specifier: &Url) -> bool {
|
||||
self.npm_resolver.in_npm_package(specifier)
|
||||
self.in_npm_pkg_checker.in_npm_package(specifier)
|
||||
}
|
||||
|
||||
/// This function is an implementation of `defaultResolve` in
|
||||
|
@ -166,7 +133,7 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
|
|||
|
||||
if let Ok(url) = Url::parse(specifier) {
|
||||
if url.scheme() == "data" {
|
||||
return Ok(NodeResolution::Esm(url));
|
||||
return Ok(NodeResolution::Module(url));
|
||||
}
|
||||
|
||||
if let Some(module_name) =
|
||||
|
@ -191,7 +158,7 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
|
|||
let url = referrer
|
||||
.join(specifier)
|
||||
.map_err(|source| DataUrlReferrerError { source })?;
|
||||
return Ok(NodeResolution::Esm(url));
|
||||
return Ok(NodeResolution::Module(url));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,7 +179,7 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
|
|||
};
|
||||
|
||||
let url = self.finalize_resolution(url, Some(referrer))?;
|
||||
let resolve_response = self.url_to_node_resolution(url)?;
|
||||
let resolve_response = NodeResolution::Module(url);
|
||||
// TODO(bartlomieju): skipped checking errors for commonJS resolution and
|
||||
// "preserveSymlinksMain"/"preserveSymlinks" options.
|
||||
Ok(resolve_response)
|
||||
|
@ -236,6 +203,7 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
|
|||
})?)
|
||||
} else if specifier.starts_with('#') {
|
||||
let pkg_config = self
|
||||
.pkg_json_resolver
|
||||
.get_closest_package_json(referrer)
|
||||
.map_err(PackageImportsResolveErrorKind::ClosestPkgJson)
|
||||
.map_err(|err| PackageImportsResolveError(Box::new(err)))?;
|
||||
|
@ -332,7 +300,7 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
|
|||
package_subpath: Option<&str>,
|
||||
maybe_referrer: Option<&Url>,
|
||||
mode: NodeResolutionMode,
|
||||
) -> Result<NodeResolution, ResolvePkgSubpathFromDenoModuleError> {
|
||||
) -> Result<Url, PackageSubpathResolveError> {
|
||||
let node_module_kind = NodeModuleKind::Esm;
|
||||
let package_subpath = package_subpath
|
||||
.map(|s| format!("./{s}"))
|
||||
|
@ -345,10 +313,9 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
|
|||
DEFAULT_CONDITIONS,
|
||||
mode,
|
||||
)?;
|
||||
let resolve_response = self.url_to_node_resolution(resolved_url)?;
|
||||
// TODO(bartlomieju): skipped checking errors for commonJS resolution and
|
||||
// "preserveSymlinksMain"/"preserveSymlinks" options.
|
||||
Ok(resolve_response)
|
||||
Ok(resolved_url)
|
||||
}
|
||||
|
||||
pub fn resolve_binary_commands(
|
||||
|
@ -356,7 +323,9 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
|
|||
package_folder: &Path,
|
||||
) -> Result<Vec<String>, ResolveBinaryCommandsError> {
|
||||
let pkg_json_path = package_folder.join("package.json");
|
||||
let Some(package_json) = self.load_package_json(&pkg_json_path)? else {
|
||||
let Some(package_json) =
|
||||
self.pkg_json_resolver.load_package_json(&pkg_json_path)?
|
||||
else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
|
||||
|
@ -381,9 +350,11 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
|
|||
&self,
|
||||
package_folder: &Path,
|
||||
sub_path: Option<&str>,
|
||||
) -> Result<NodeResolution, ResolvePkgJsonBinExportError> {
|
||||
) -> Result<Url, ResolvePkgJsonBinExportError> {
|
||||
let pkg_json_path = package_folder.join("package.json");
|
||||
let Some(package_json) = self.load_package_json(&pkg_json_path)? else {
|
||||
let Some(package_json) =
|
||||
self.pkg_json_resolver.load_package_json(&pkg_json_path)?
|
||||
else {
|
||||
return Err(ResolvePkgJsonBinExportError::MissingPkgJson {
|
||||
pkg_json_path,
|
||||
});
|
||||
|
@ -396,37 +367,9 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
|
|||
})?;
|
||||
let url = url_from_file_path(&package_folder.join(bin_entry)).unwrap();
|
||||
|
||||
let resolve_response = self.url_to_node_resolution(url)?;
|
||||
// TODO(bartlomieju): skipped checking errors for commonJS resolution and
|
||||
// "preserveSymlinksMain"/"preserveSymlinks" options.
|
||||
Ok(resolve_response)
|
||||
}
|
||||
|
||||
pub fn url_to_node_resolution(
|
||||
&self,
|
||||
url: Url,
|
||||
) -> Result<NodeResolution, UrlToNodeResolutionError> {
|
||||
let url_str = url.as_str().to_lowercase();
|
||||
if url_str.starts_with("http") || url_str.ends_with(".json") {
|
||||
Ok(NodeResolution::Esm(url))
|
||||
} else if url_str.ends_with(".js") || url_str.ends_with(".d.ts") {
|
||||
let maybe_package_config = self.get_closest_package_json(&url)?;
|
||||
match maybe_package_config {
|
||||
Some(c) if c.typ == "module" => Ok(NodeResolution::Esm(url)),
|
||||
Some(_) => Ok(NodeResolution::CommonJs(url)),
|
||||
None => Ok(NodeResolution::Esm(url)),
|
||||
}
|
||||
} else if url_str.ends_with(".mjs") || url_str.ends_with(".d.mts") {
|
||||
Ok(NodeResolution::Esm(url))
|
||||
} else if url_str.ends_with(".ts") || url_str.ends_with(".mts") {
|
||||
if self.in_npm_package(&url) {
|
||||
Err(TypeScriptNotSupportedInNpmError { specifier: url }.into())
|
||||
} else {
|
||||
Ok(NodeResolution::Esm(url))
|
||||
}
|
||||
} else {
|
||||
Ok(NodeResolution::CommonJs(url))
|
||||
}
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
/// Checks if the resolved file has a corresponding declaration file.
|
||||
|
@ -1101,7 +1044,9 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
|
|||
let (package_name, package_subpath, _is_scoped) =
|
||||
parse_npm_pkg_name(specifier, referrer)?;
|
||||
|
||||
if let Some(package_config) = self.get_closest_package_json(referrer)? {
|
||||
if let Some(package_config) =
|
||||
self.pkg_json_resolver.get_closest_package_json(referrer)?
|
||||
{
|
||||
// ResolveSelf
|
||||
if package_config.name.as_ref() == Some(&package_name) {
|
||||
if let Some(exports) = &package_config.exports {
|
||||
|
@ -1216,7 +1161,10 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
|
|||
mode: NodeResolutionMode,
|
||||
) -> Result<Url, PackageSubpathResolveError> {
|
||||
let package_json_path = package_dir_path.join("package.json");
|
||||
match self.load_package_json(&package_json_path)? {
|
||||
match self
|
||||
.pkg_json_resolver
|
||||
.load_package_json(&package_json_path)?
|
||||
{
|
||||
Some(pkg_json) => self.resolve_package_subpath(
|
||||
&pkg_json,
|
||||
package_subpath,
|
||||
|
@ -1337,70 +1285,6 @@ impl<TEnv: NodeResolverEnv> NodeResolver<TEnv> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_closest_package_json(
|
||||
&self,
|
||||
url: &Url,
|
||||
) -> Result<Option<PackageJsonRc>, ClosestPkgJsonError> {
|
||||
let Ok(file_path) = deno_path_util::url_to_file_path(url) else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.get_closest_package_json_from_path(&file_path)
|
||||
}
|
||||
|
||||
pub fn get_closest_package_json_from_path(
|
||||
&self,
|
||||
file_path: &Path,
|
||||
) -> Result<Option<PackageJsonRc>, ClosestPkgJsonError> {
|
||||
// we use this for deno compile using byonm because the script paths
|
||||
// won't be in virtual file system, but the package.json paths will be
|
||||
fn canonicalize_first_ancestor_exists(
|
||||
dir_path: &Path,
|
||||
env: &dyn NodeResolverEnv,
|
||||
) -> Result<Option<PathBuf>, std::io::Error> {
|
||||
for ancestor in dir_path.ancestors() {
|
||||
match env.realpath_sync(ancestor) {
|
||||
Ok(dir_path) => return Ok(Some(dir_path)),
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
// keep searching
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
let parent_dir = file_path.parent().unwrap();
|
||||
let Some(start_dir) = canonicalize_first_ancestor_exists(
|
||||
parent_dir, &self.env,
|
||||
)
|
||||
.map_err(|source| CanonicalizingPkgJsonDirError {
|
||||
dir_path: parent_dir.to_path_buf(),
|
||||
source,
|
||||
})?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
let start_dir = strip_unc_prefix(start_dir);
|
||||
for current_dir in start_dir.ancestors() {
|
||||
let package_json_path = current_dir.join("package.json");
|
||||
if let Some(pkg_json) = self.load_package_json(&package_json_path)? {
|
||||
return Ok(Some(pkg_json));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn load_package_json(
|
||||
&self,
|
||||
package_json_path: &Path,
|
||||
) -> Result<Option<PackageJsonRc>, PackageJsonLoadError> {
|
||||
crate::package_json::load_pkg_json(
|
||||
self.env.pkg_json_fs(),
|
||||
package_json_path,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn legacy_main_resolve(
|
||||
&self,
|
||||
package_json: &PackageJson,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue