refactor(cli): decouple resolvers from module_loader.rs for standalone use (#22147)

It makes it easier to write a standalone bin target for `deno compile`
without pulling a lot of the tooling and tsc loader logic
This commit is contained in:
Divy Srivastava 2024-01-27 22:40:36 +05:30 committed by GitHub
parent d9191db0ce
commit ed65bc6abc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 244 additions and 242 deletions

View file

@ -23,11 +23,8 @@ use crate::graph_util::FileWatcherReporter;
use crate::graph_util::ModuleGraphBuilder;
use crate::graph_util::ModuleGraphContainer;
use crate::http_util::HttpClient;
use crate::module_loader::CjsResolutionStore;
use crate::module_loader::CliModuleLoaderFactory;
use crate::module_loader::CliNodeResolver;
use crate::module_loader::ModuleLoadPreparer;
use crate::module_loader::NpmModuleLoader;
use crate::node::CliCjsCodeAnalyzer;
use crate::node::CliNodeCodeTranslator;
use crate::npm::create_cli_npm_resolver;
@ -37,8 +34,11 @@ use crate::npm::CliNpmResolverCreateOptions;
use crate::npm::CliNpmResolverManagedCreateOptions;
use crate::npm::CliNpmResolverManagedPackageJsonInstallerOption;
use crate::npm::CliNpmResolverManagedSnapshotOption;
use crate::resolver::CjsResolutionStore;
use crate::resolver::CliGraphResolver;
use crate::resolver::CliGraphResolverOptions;
use crate::resolver::CliNodeResolver;
use crate::resolver::NpmModuleLoader;
use crate::resolver::SloppyImportsResolver;
use crate::standalone::DenoCompileBinaryWriter;
use crate::tools::check::TypeChecker;

View file

@ -14,9 +14,10 @@ use crate::graph_util::FileWatcherReporter;
use crate::graph_util::ModuleGraphBuilder;
use crate::graph_util::ModuleGraphContainer;
use crate::node;
use crate::node::CliNodeCodeTranslator;
use crate::npm::CliNpmResolver;
use crate::resolver::CliGraphResolver;
use crate::resolver::CliNodeResolver;
use crate::resolver::ModuleCodeStringSource;
use crate::resolver::NpmModuleLoader;
use crate::tools::check;
use crate::tools::check::TypeChecker;
use crate::util::progress_bar::ProgressBar;
@ -53,14 +54,10 @@ use deno_graph::Resolution;
use deno_lockfile::Lockfile;
use deno_runtime::colors;
use deno_runtime::deno_fs;
use deno_runtime::deno_node::NodeResolution;
use deno_runtime::deno_node::NodeResolutionMode;
use deno_runtime::deno_node::NodeResolver;
use deno_runtime::permissions::PermissionsContainer;
use deno_semver::npm::NpmPackageReqReference;
use std::borrow::Cow;
use std::collections::HashSet;
use std::path::Path;
use std::pin::Pin;
use std::rc::Rc;
use std::str;
@ -271,12 +268,6 @@ impl ModuleLoadPreparer {
}
}
pub struct ModuleCodeStringSource {
pub code: ModuleCodeString,
pub found_url: ModuleSpecifier,
pub media_type: MediaType,
}
struct PreparedModuleLoader {
emitter: Arc<Emitter>,
graph_container: Arc<ModuleGraphContainer>,
@ -741,226 +732,3 @@ impl SourceMapGetter for CliSourceMapGetter {
}
}
}
pub struct CliNodeResolver {
cjs_resolutions: Arc<CjsResolutionStore>,
node_resolver: Arc<NodeResolver>,
npm_resolver: Arc<dyn CliNpmResolver>,
}
impl CliNodeResolver {
pub fn new(
cjs_resolutions: Arc<CjsResolutionStore>,
node_resolver: Arc<NodeResolver>,
npm_resolver: Arc<dyn CliNpmResolver>,
) -> Self {
Self {
cjs_resolutions,
node_resolver,
npm_resolver,
}
}
pub fn in_npm_package(&self, referrer: &ModuleSpecifier) -> bool {
self.npm_resolver.in_npm_package(referrer)
}
pub fn resolve_if_in_npm_package(
&self,
specifier: &str,
referrer: &ModuleSpecifier,
permissions: &PermissionsContainer,
) -> Option<Result<ModuleSpecifier, AnyError>> {
if self.in_npm_package(referrer) {
// we're in an npm package, so use node resolution
Some(
self
.handle_node_resolve_result(self.node_resolver.resolve(
specifier,
referrer,
NodeResolutionMode::Execution,
permissions,
))
.with_context(|| {
format!("Could not resolve '{specifier}' from '{referrer}'.")
}),
)
} else {
None
}
}
pub fn resolve_req_reference(
&self,
req_ref: &NpmPackageReqReference,
permissions: &PermissionsContainer,
referrer: &ModuleSpecifier,
) -> Result<ModuleSpecifier, AnyError> {
let package_folder = self
.npm_resolver
.resolve_pkg_folder_from_deno_module_req(req_ref.req(), referrer)?;
self
.resolve_package_sub_path(
&package_folder,
req_ref.sub_path(),
referrer,
permissions,
)
.with_context(|| format!("Could not resolve '{}'.", req_ref))
}
pub fn resolve_package_sub_path(
&self,
package_folder: &Path,
sub_path: Option<&str>,
referrer: &ModuleSpecifier,
permissions: &PermissionsContainer,
) -> Result<ModuleSpecifier, AnyError> {
self.handle_node_resolve_result(
self.node_resolver.resolve_package_subpath_from_deno_module(
package_folder,
sub_path,
referrer,
NodeResolutionMode::Execution,
permissions,
),
)
}
fn handle_node_resolve_result(
&self,
result: Result<Option<NodeResolution>, AnyError>,
) -> Result<ModuleSpecifier, AnyError> {
let response = match result? {
Some(response) => response,
None => return Err(generic_error("not found")),
};
if let NodeResolution::CommonJs(specifier) = &response {
// remember that this was a common js resolution
self.cjs_resolutions.insert(specifier.clone());
}
Ok(response.into_url())
}
}
pub struct NpmModuleLoader {
cjs_resolutions: Arc<CjsResolutionStore>,
node_code_translator: Arc<CliNodeCodeTranslator>,
fs: Arc<dyn deno_fs::FileSystem>,
node_resolver: Arc<CliNodeResolver>,
}
impl NpmModuleLoader {
pub fn new(
cjs_resolutions: Arc<CjsResolutionStore>,
node_code_translator: Arc<CliNodeCodeTranslator>,
fs: Arc<dyn deno_fs::FileSystem>,
node_resolver: Arc<CliNodeResolver>,
) -> Self {
Self {
cjs_resolutions,
node_code_translator,
fs,
node_resolver,
}
}
pub fn maybe_prepare_load(
&self,
specifier: &ModuleSpecifier,
) -> Option<Result<(), AnyError>> {
if self.node_resolver.in_npm_package(specifier) {
// nothing to prepare
Some(Ok(()))
} else {
None
}
}
pub fn load_sync_if_in_npm_package(
&self,
specifier: &ModuleSpecifier,
maybe_referrer: Option<&ModuleSpecifier>,
permissions: &PermissionsContainer,
) -> Option<Result<ModuleCodeStringSource, AnyError>> {
if self.node_resolver.in_npm_package(specifier) {
Some(self.load_sync(specifier, maybe_referrer, permissions))
} else {
None
}
}
fn load_sync(
&self,
specifier: &ModuleSpecifier,
maybe_referrer: Option<&ModuleSpecifier>,
permissions: &PermissionsContainer,
) -> Result<ModuleCodeStringSource, AnyError> {
let file_path = specifier.to_file_path().unwrap();
let code = self
.fs
.read_text_file_sync(&file_path)
.map_err(AnyError::from)
.with_context(|| {
if file_path.is_dir() {
// 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());
let entrypoint_name = ["index.mjs", "index.js", "index.cjs"]
.iter()
.find(|e| dir_path.join(e).is_file());
if let Some(entrypoint_name) = entrypoint_name {
msg.push_str("\nDid you mean to import ");
msg.push_str(entrypoint_name);
msg.push_str(" within the directory?");
}
}
msg
} else {
let mut msg = "Unable to load ".to_string();
msg.push_str(&file_path.to_string_lossy());
if let Some(referrer) = &maybe_referrer {
msg.push_str(" imported from ");
msg.push_str(referrer.as_str());
}
msg
}
})?;
let code = if self.cjs_resolutions.contains(specifier) {
// translate cjs to esm if it's cjs and inject node globals
self.node_code_translator.translate_cjs_to_esm(
specifier,
Some(code.as_str()),
permissions,
)?
} else {
// esm and json code is untouched
code
};
Ok(ModuleCodeStringSource {
code: code.into(),
found_url: specifier.clone(),
media_type: MediaType::from_specifier(specifier),
})
}
}
/// Keeps track of what module specifiers were resolved as CJS.
#[derive(Debug, Default)]
pub struct CjsResolutionStore(Mutex<HashSet<ModuleSpecifier>>);
impl CjsResolutionStore {
pub fn contains(&self, specifier: &ModuleSpecifier) -> bool {
self.0.lock().contains(specifier)
}
pub fn insert(&self, specifier: ModuleSpecifier) {
self.0.lock().insert(specifier);
}
}

View file

@ -2,11 +2,14 @@
use deno_ast::MediaType;
use deno_core::anyhow::anyhow;
use deno_core::anyhow::Context;
use deno_core::error::generic_error;
use deno_core::error::AnyError;
use deno_core::futures::future;
use deno_core::futures::future::LocalBoxFuture;
use deno_core::futures::FutureExt;
use deno_core::parking_lot::Mutex;
use deno_core::ModuleCodeString;
use deno_core::ModuleSpecifier;
use deno_graph::source::NpmPackageReqResolution;
use deno_graph::source::NpmResolver;
@ -15,6 +18,7 @@ use deno_graph::source::ResolveError;
use deno_graph::source::Resolver;
use deno_graph::source::UnknownBuiltInNodeModuleError;
use deno_graph::source::DEFAULT_JSX_IMPORT_SOURCE_MODULE;
use deno_runtime::deno_fs;
use deno_runtime::deno_fs::FileSystem;
use deno_runtime::deno_node::is_builtin_node_module;
use deno_runtime::deno_node::parse_npm_pkg_name;
@ -28,6 +32,7 @@ use deno_semver::package::PackageReq;
use import_map::ImportMap;
use std::borrow::Cow;
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
@ -36,13 +41,242 @@ use crate::args::package_json::PackageJsonDeps;
use crate::args::JsxImportSourceConfig;
use crate::args::PackageJsonDepsProvider;
use crate::graph_util::format_range_with_colors;
use crate::module_loader::CjsResolutionStore;
use crate::node::CliNodeCodeTranslator;
use crate::npm::ByonmCliNpmResolver;
use crate::npm::CliNpmResolver;
use crate::npm::InnerCliNpmResolverRef;
use crate::util::path::specifier_to_file_path;
use crate::util::sync::AtomicFlag;
pub struct ModuleCodeStringSource {
pub code: ModuleCodeString,
pub found_url: ModuleSpecifier,
pub media_type: MediaType,
}
pub struct CliNodeResolver {
cjs_resolutions: Arc<CjsResolutionStore>,
node_resolver: Arc<NodeResolver>,
pub(crate) npm_resolver: Arc<dyn CliNpmResolver>,
}
impl CliNodeResolver {
pub fn new(
cjs_resolutions: Arc<CjsResolutionStore>,
node_resolver: Arc<NodeResolver>,
npm_resolver: Arc<dyn CliNpmResolver>,
) -> Self {
Self {
cjs_resolutions,
node_resolver,
npm_resolver,
}
}
pub fn in_npm_package(&self, referrer: &ModuleSpecifier) -> bool {
self.npm_resolver.in_npm_package(referrer)
}
pub fn resolve_if_in_npm_package(
&self,
specifier: &str,
referrer: &ModuleSpecifier,
permissions: &PermissionsContainer,
) -> Option<Result<ModuleSpecifier, AnyError>> {
if self.in_npm_package(referrer) {
// we're in an npm package, so use node resolution
Some(
self
.handle_node_resolve_result(self.node_resolver.resolve(
specifier,
referrer,
NodeResolutionMode::Execution,
permissions,
))
.with_context(|| {
format!("Could not resolve '{specifier}' from '{referrer}'.")
}),
)
} else {
None
}
}
pub fn resolve_req_reference(
&self,
req_ref: &NpmPackageReqReference,
permissions: &PermissionsContainer,
referrer: &ModuleSpecifier,
) -> Result<ModuleSpecifier, AnyError> {
let package_folder = self
.npm_resolver
.resolve_pkg_folder_from_deno_module_req(req_ref.req(), referrer)?;
self
.resolve_package_sub_path(
&package_folder,
req_ref.sub_path(),
referrer,
permissions,
)
.with_context(|| format!("Could not resolve '{}'.", req_ref))
}
pub fn resolve_package_sub_path(
&self,
package_folder: &Path,
sub_path: Option<&str>,
referrer: &ModuleSpecifier,
permissions: &PermissionsContainer,
) -> Result<ModuleSpecifier, AnyError> {
self.handle_node_resolve_result(
self.node_resolver.resolve_package_subpath_from_deno_module(
package_folder,
sub_path,
referrer,
NodeResolutionMode::Execution,
permissions,
),
)
}
fn handle_node_resolve_result(
&self,
result: Result<Option<NodeResolution>, AnyError>,
) -> Result<ModuleSpecifier, AnyError> {
let response = match result? {
Some(response) => response,
None => return Err(generic_error("not found")),
};
if let NodeResolution::CommonJs(specifier) = &response {
// remember that this was a common js resolution
self.cjs_resolutions.insert(specifier.clone());
}
Ok(response.into_url())
}
}
pub struct NpmModuleLoader {
cjs_resolutions: Arc<CjsResolutionStore>,
node_code_translator: Arc<CliNodeCodeTranslator>,
fs: Arc<dyn deno_fs::FileSystem>,
node_resolver: Arc<CliNodeResolver>,
}
impl NpmModuleLoader {
pub fn new(
cjs_resolutions: Arc<CjsResolutionStore>,
node_code_translator: Arc<CliNodeCodeTranslator>,
fs: Arc<dyn deno_fs::FileSystem>,
node_resolver: Arc<CliNodeResolver>,
) -> Self {
Self {
cjs_resolutions,
node_code_translator,
fs,
node_resolver,
}
}
pub fn maybe_prepare_load(
&self,
specifier: &ModuleSpecifier,
) -> Option<Result<(), AnyError>> {
if self.node_resolver.in_npm_package(specifier) {
// nothing to prepare
Some(Ok(()))
} else {
None
}
}
pub fn load_sync_if_in_npm_package(
&self,
specifier: &ModuleSpecifier,
maybe_referrer: Option<&ModuleSpecifier>,
permissions: &PermissionsContainer,
) -> Option<Result<ModuleCodeStringSource, AnyError>> {
if self.node_resolver.in_npm_package(specifier) {
Some(self.load_sync(specifier, maybe_referrer, permissions))
} else {
None
}
}
fn load_sync(
&self,
specifier: &ModuleSpecifier,
maybe_referrer: Option<&ModuleSpecifier>,
permissions: &PermissionsContainer,
) -> Result<ModuleCodeStringSource, AnyError> {
let file_path = specifier.to_file_path().unwrap();
let code = self
.fs
.read_text_file_sync(&file_path)
.map_err(AnyError::from)
.with_context(|| {
if file_path.is_dir() {
// 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());
let entrypoint_name = ["index.mjs", "index.js", "index.cjs"]
.iter()
.find(|e| dir_path.join(e).is_file());
if let Some(entrypoint_name) = entrypoint_name {
msg.push_str("\nDid you mean to import ");
msg.push_str(entrypoint_name);
msg.push_str(" within the directory?");
}
}
msg
} else {
let mut msg = "Unable to load ".to_string();
msg.push_str(&file_path.to_string_lossy());
if let Some(referrer) = &maybe_referrer {
msg.push_str(" imported from ");
msg.push_str(referrer.as_str());
}
msg
}
})?;
let code = if self.cjs_resolutions.contains(specifier) {
// translate cjs to esm if it's cjs and inject node globals
self.node_code_translator.translate_cjs_to_esm(
specifier,
Some(code.as_str()),
permissions,
)?
} else {
// esm and json code is untouched
code
};
Ok(ModuleCodeStringSource {
code: code.into(),
found_url: specifier.clone(),
media_type: MediaType::from_specifier(specifier),
})
}
}
/// Keeps track of what module specifiers were resolved as CJS.
#[derive(Debug, Default)]
pub struct CjsResolutionStore(Mutex<HashSet<ModuleSpecifier>>);
impl CjsResolutionStore {
pub fn contains(&self, specifier: &ModuleSpecifier) -> bool {
self.0.lock().contains(specifier)
}
pub fn insert(&self, specifier: ModuleSpecifier) {
self.0.lock().insert(specifier);
}
}
/// Result of checking if a specifier is mapped via
/// an import map or package.json.
pub enum MappedResolution {

View file

@ -11,9 +11,6 @@ use crate::cache::DenoDirProvider;
use crate::cache::NodeAnalysisCache;
use crate::file_fetcher::get_source_from_data_url;
use crate::http_util::HttpClient;
use crate::module_loader::CjsResolutionStore;
use crate::module_loader::CliNodeResolver;
use crate::module_loader::NpmModuleLoader;
use crate::node::CliCjsCodeAnalyzer;
use crate::npm::create_cli_npm_resolver;
use crate::npm::CliNpmResolverByonmCreateOptions;
@ -22,7 +19,10 @@ use crate::npm::CliNpmResolverManagedCreateOptions;
use crate::npm::CliNpmResolverManagedPackageJsonInstallerOption;
use crate::npm::CliNpmResolverManagedSnapshotOption;
use crate::npm::NpmCacheDir;
use crate::resolver::CjsResolutionStore;
use crate::resolver::CliNodeResolver;
use crate::resolver::MappedSpecifierResolver;
use crate::resolver::NpmModuleLoader;
use crate::util::progress_bar::ProgressBar;
use crate::util::progress_bar::ProgressBarStyle;
use crate::util::v8::construct_v8_flags;