/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
libs/resolver/loader/npm.rs
312 строк
9 KB
Nathan Whitaker
fix(cjs): use loader sources for recursive analysis (#36111)
30 июл 2026, 23:13
Не верифицирован
30 июл 2026, 23:13
786225a
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. use std::borrow::Cow; use std::path::PathBuf; use deno_media_type::MediaType; use node_resolver::InNpmPackageChecker; use node_resolver::IsBuiltInNodeModuleChecker; use node_resolver::NpmPackageFolderResolver; use node_resolver::analyze::CjsAnalysisSourceProvider; use node_resolver::analyze::CjsCodeAnalyzer; use node_resolver::analyze::NodeCodeTranslatorRc; use node_resolver::analyze::NodeCodeTranslatorSys; use thiserror::Error; use url::Url; use super::LoadedModule; use super::LoadedModuleSource; use super::RequestedModuleType; use super::media_type_name; use crate::cjs::CjsTrackerRc; #[derive(Debug, Error, deno_error::JsError)] #[class(type)] #[error("[{}]: Stripping types is currently unsupported for files under node_modules, for \"{}\"", self.code(), specifier)] pub struct StrippingTypesNodeModulesError { pub specifier: Url, } impl StrippingTypesNodeModulesError { pub fn code(&self) -> &'static str { "ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING" } } #[derive(Debug, Error, deno_error::JsError)] pub enum NpmModuleLoadError { #[class(inherit)] #[error(transparent)] UrlToFilePath(#[from] deno_path_util::UrlToFilePathError), #[class(inherit)] #[error(transparent)] StrippingTypesNodeModules(#[from] StrippingTypesNodeModulesError), #[class(inherit)] #[error(transparent)] ClosestPkgJson(#[from] node_resolver::errors::PackageJsonLoadError), #[class(inherit)] #[error(transparent)] TranslateCjsToEsm(#[from] node_resolver::analyze::TranslateCjsToEsmError), #[class(inherit)] #[error("Unable to load {}{}", file_path.display(), maybe_referrer.as_ref().map(|r| format!(" imported from {}", r)).unwrap_or_default())] UnableToLoad { file_path: PathBuf, maybe_referrer: Option<Url>, #[source] #[inherit] source: std::io::Error, }, #[class(inherit)] #[error( "{}", format_dir_import_message(file_path, maybe_referrer, suggestion) )] DirImport { file_path: PathBuf, maybe_referrer: Option<Url>, suggestion: Option<&'static str>, #[source] #[inherit] source: std::io::Error, }, #[class(type)] #[error( "Expected a JSON module, but identified a {actual} module.\n Specifier: {specifier}" )] ExpectedJsonModule { specifier: Url, actual: &'static str, }, } fn format_dir_import_message( file_path: &std::path::Path, maybe_referrer: &Option<Url>, suggestion: &Option<&'static str>, ) -> String { // directory imports are not allowed when importing from an // ES module, so provide the user with a helpful error message let dir_path = file_path; let mut msg = "Directory import ".to_string(); msg.push_str(&dir_path.to_string_lossy()); if let Some(referrer) = maybe_referrer { msg.push_str(" is not supported resolving import from "); msg.push_str(referrer.as_str()); } if let Some(entrypoint_name) = suggestion { msg.push_str("\nDid you mean to import "); msg.push_str(entrypoint_name); msg.push_str(" within the directory?"); } msg } #[sys_traits::auto_impl] pub trait NpmModuleLoaderSys: NodeCodeTranslatorSys {} #[allow(clippy::disallowed_types, reason = "definition")] pub type DenoNpmModuleLoaderRc<TSys> = deno_maybe_sync::MaybeArc<DenoNpmModuleLoader<TSys>>; pub type DenoNpmModuleLoader<TSys> = NpmModuleLoader< crate::cjs::analyzer::DenoCjsCodeAnalyzer<TSys>, crate::npm::DenoInNpmPackageChecker, node_resolver::DenoIsBuiltInNodeModuleChecker, crate::npm::NpmResolver<TSys>, TSys, >; #[derive(Clone)] pub struct NpmModuleLoader< TCjsCodeAnalyzer: CjsCodeAnalyzer, TInNpmPackageChecker: InNpmPackageChecker + Clone, TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker, TNpmPackageFolderResolver: NpmPackageFolderResolver, TSys: NpmModuleLoaderSys, > { cjs_tracker: CjsTrackerRc<TInNpmPackageChecker, TSys>, in_npm_pkg_checker: TInNpmPackageChecker, node_code_translator: NodeCodeTranslatorRc< TCjsCodeAnalyzer, TInNpmPackageChecker, TIsBuiltInNodeModuleChecker, TNpmPackageFolderResolver, TSys, >, sys: TSys, } impl< TCjsCodeAnalyzer: CjsCodeAnalyzer, TInNpmPackageChecker: InNpmPackageChecker + Clone, TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker, TNpmPackageFolderResolver: NpmPackageFolderResolver, TSys: NpmModuleLoaderSys, > NpmModuleLoader< TCjsCodeAnalyzer, TInNpmPackageChecker, TIsBuiltInNodeModuleChecker, TNpmPackageFolderResolver, TSys, > { pub fn new( cjs_tracker: CjsTrackerRc<TInNpmPackageChecker, TSys>, in_npm_pkg_checker: TInNpmPackageChecker, node_code_translator: NodeCodeTranslatorRc< TCjsCodeAnalyzer, TInNpmPackageChecker, TIsBuiltInNodeModuleChecker, TNpmPackageFolderResolver, TSys, >, sys: TSys, ) -> Self { Self { cjs_tracker, in_npm_pkg_checker, node_code_translator, sys, } } pub async fn load<'a>( &self, specifier: Cow<'a, Url>, maybe_referrer: Option<&Url>, requested_module_type: &RequestedModuleType<'_>, fallback_source_provider: Option<&dyn CjsAnalysisSourceProvider>, ) -> Result<LoadedModule<'a>, NpmModuleLoadError> { let file_path = deno_path_util::url_to_file_path(&specifier)?; let code = self.sys.fs_read(&file_path).map_err(|source| { if self.sys.fs_is_dir_no_err(&file_path) { let suggestion = ["index.mjs", "index.js", "index.cjs"] .into_iter() .find(|e| self.sys.fs_is_file_no_err(file_path.join(e))); NpmModuleLoadError::DirImport { file_path, maybe_referrer: maybe_referrer.cloned(), suggestion, source, } } else { NpmModuleLoadError::UnableToLoad { file_path, maybe_referrer: maybe_referrer.cloned(), source, } } })?; let media_type = MediaType::from_specifier(&specifier); if matches!(requested_module_type, RequestedModuleType::Json) && media_type != MediaType::Json { return Err(NpmModuleLoadError::ExpectedJsonModule { specifier: specifier.clone().into_owned(), actual: media_type_name(media_type), }); } match requested_module_type { RequestedModuleType::Text | RequestedModuleType::Bytes => { Ok(LoadedModule { specifier, media_type, source: LoadedModuleSource::Bytes(code), }) } RequestedModuleType::None | RequestedModuleType::Json | RequestedModuleType::Other(_) => { if media_type.is_emittable() { return Err(NpmModuleLoadError::StrippingTypesNodeModules( StrippingTypesNodeModulesError { specifier: specifier.into_owned(), }, )); } let source = if self.cjs_tracker.is_maybe_cjs(&specifier, media_type)? { // translate cjs to esm if it's cjs and inject node globals let code = from_utf8_lossy_cow(code); LoadedModuleSource::String( self .node_code_translator .translate_cjs_to_esm_with_source_provider( &specifier, Some(code), Some(&NpmCjsAnalysisSourceProvider { in_npm_pkg_checker: &self.in_npm_pkg_checker, sys: &self.sys, fallback: fallback_source_provider, }), ) .await? .into_owned() .into(), ) } else { // esm and json code is untouched LoadedModuleSource::Bytes(code) }; Ok(LoadedModule { source, specifier, media_type, }) } } } } struct NpmCjsAnalysisSourceProvider<'a, TInNpmPackageChecker, TSys> { in_npm_pkg_checker: &'a TInNpmPackageChecker, sys: &'a TSys, fallback: Option<&'a dyn CjsAnalysisSourceProvider>, } impl<TInNpmPackageChecker, TSys> CjsAnalysisSourceProvider for NpmCjsAnalysisSourceProvider<'_, TInNpmPackageChecker, TSys> where TInNpmPackageChecker: InNpmPackageChecker, TSys: NpmModuleLoaderSys, { fn load_source<'a>(&'a self, specifier: &Url) -> Option<Cow<'a, str>> { // Recursive npm analysis reads package-owned sources directly. Targets // outside package roots must go through the active loader's source // provider so its permission and source-selection policy remains in force. if !self.in_npm_pkg_checker.in_npm_package(specifier) { return self .fallback .and_then(|provider| provider.load_source(specifier)); } let path = deno_path_util::url_to_file_path(specifier).ok()?; self .sys .fs_read_to_string_lossy(path) .ok() .map(|source| Cow::Owned(source.into_owned())) } } #[inline(always)] fn from_utf8_lossy_cow(bytes: Cow<'_, [u8]>) -> Cow<'_, str> { match bytes { Cow::Borrowed(bytes) => String::from_utf8_lossy(bytes), Cow::Owned(bytes) => Cow::Owned(from_utf8_lossy_owned(bytes)), } } // todo(https://github.com/rust-lang/rust/issues/129436): remove once stabilized #[inline(always)] fn from_utf8_lossy_owned(bytes: Vec<u8>) -> String { match String::from_utf8_lossy(&bytes) { Cow::Owned(code) => code, // SAFETY: `String::from_utf8_lossy` guarantees that the result is valid // UTF-8 if `Cow::Borrowed` is returned. Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(bytes) }, } }