diff --git a/crates/deno_facade/emitter.rs b/crates/deno_facade/emitter.rs index 065abd0a1..1307b7624 100644 --- a/crates/deno_facade/emitter.rs +++ b/crates/deno_facade/emitter.rs @@ -32,11 +32,13 @@ use deno::graph_util::ModuleGraphBuilder; use deno::graph_util::ModuleGraphCreator; use deno::http_util::HttpClientProvider; use deno::node_resolver::InNpmPackageChecker; +use deno::npm::byonm::CliByonmNpmResolverCreateOptions; +use deno::npm::create_cli_npm_resolver; use deno::npm::create_in_npm_pkg_checker; -use deno::npm::create_managed_npm_resolver; use deno::npm::CliManagedInNpmPkgCheckerCreateOptions; use deno::npm::CliManagedNpmResolverCreateOptions; use deno::npm::CliNpmResolver; +use deno::npm::CliNpmResolverCreateOptions; use deno::npm::CliNpmResolverManagedSnapshotOption; use deno::npm::CreateInNpmPkgCheckerOptions; use deno::resolver::CjsTracker; @@ -45,6 +47,7 @@ use deno::resolver::CliDenoResolverFs; use deno::resolver::CliNpmReqResolver; use deno::resolver::CliResolver; use deno::resolver::CliResolverOptions; +use deno::util::fs::canonicalize_path_maybe_not_exists; use deno::DenoOptions; use deno::PermissionsContainer; use deno_core::error::AnyError; @@ -279,7 +282,7 @@ impl EmitterFactory { .get_or_init(|| Arc::new(HttpClientProvider::new(None, None))) } - pub fn real_fs(&self) -> Arc { + pub fn fs(&self) -> Arc { Arc::new(deno::deno_fs::RealFs) } @@ -326,25 +329,49 @@ impl EmitterFactory { ) -> Result<&Arc, anyhow::Error> { self .npm_resolver - .get_or_try_init_async(async { - let options = self.deno_options()?; - create_managed_npm_resolver(CliManagedNpmResolverCreateOptions { - snapshot: CliNpmResolverManagedSnapshotOption::Specified(None), - maybe_lockfile: options.maybe_lockfile().cloned(), - fs: self.real_fs(), - http_client_provider: self.http_client_provider().clone(), - npm_cache_dir: self.npm_cache_dir()?.clone(), - cache_setting: self - .cache_strategy - .clone() - .unwrap_or(CacheSetting::Use), - maybe_node_modules_path: None, - npm_system_info: Default::default(), - npm_install_deps_provider: Default::default(), - npmrc: self.resolved_npm_rc()?.clone(), - }) - .await - }) + .get_or_try_init_async( + async { + let fs = self.fs(); + let options = self.deno_options()?; + create_cli_npm_resolver(if options.use_byonm() { + CliNpmResolverCreateOptions::Byonm( + CliByonmNpmResolverCreateOptions { + fs: CliDenoResolverFs(fs), + pkg_json_resolver: self.pkg_json_resolver().clone(), + root_node_modules_dir: Some( + match options.node_modules_dir_path() { + Some(node_modules_path) => node_modules_path.to_path_buf(), + None => { + canonicalize_path_maybe_not_exists(options.initial_cwd())? + .join("node_modules") + } + }, + ), + }, + ) + } else { + CliNpmResolverCreateOptions::Managed( + CliManagedNpmResolverCreateOptions { + snapshot: CliNpmResolverManagedSnapshotOption::Specified(None), + maybe_lockfile: options.maybe_lockfile().cloned(), + fs, + http_client_provider: self.http_client_provider().clone(), + npm_cache_dir: self.npm_cache_dir()?.clone(), + cache_setting: self + .cache_strategy + .clone() + .unwrap_or(CacheSetting::Use), + maybe_node_modules_path: None, + npm_system_info: Default::default(), + npm_install_deps_provider: Default::default(), + npmrc: self.resolved_npm_rc()?.clone(), + }, + ) + }) + .await + } + .boxed_local(), + ) .await } @@ -357,7 +384,7 @@ impl EmitterFactory { let npm_resolver = self.npm_resolver().await?; Ok(Arc::new(CliNpmReqResolver::new(NpmReqResolverOptions { byonm_resolver: (npm_resolver.clone()).into_maybe_byonm(), - fs: CliDenoResolverFs(self.real_fs()), + fs: CliDenoResolverFs(self.fs()), in_npm_pkg_checker: self.in_npm_pkg_checker()?.clone(), node_resolver: self.node_resolver().await?.clone(), npm_req_resolver: npm_resolver.clone().into_npm_req_resolver(), @@ -404,7 +431,7 @@ impl EmitterFactory { pub fn npm_cache_dir(&self) -> Result<&Arc, anyhow::Error> { self.npm_cache_dir.get_or_try_init(|| { - let fs = self.real_fs(); + let fs = self.fs(); let global_path = self.deno_dir.npm_folder_path(); let options = self.deno_options()?; Ok(Arc::new(NpmCacheDir::new( @@ -429,7 +456,7 @@ impl EmitterFactory { .get_or_try_init_async( async { Ok(Arc::new(NodeResolver::new( - DenoFsNodeResolverEnv::new(self.real_fs().clone()), + DenoFsNodeResolverEnv::new(self.fs().clone()), self.in_npm_pkg_checker()?.clone(), self .npm_resolver() @@ -447,7 +474,7 @@ impl EmitterFactory { pub fn pkg_json_resolver(&self) -> &Arc { self.pkg_json_resolver.get_or_init(|| { Arc::new(PackageJsonResolver::new(DenoFsNodeResolverEnv::new( - self.real_fs().clone(), + self.fs().clone(), ))) }) } @@ -456,7 +483,7 @@ impl EmitterFactory { &self, ) -> Result<&Arc, anyhow::Error> { self.permission_desc_parser.get_or_try_init(|| { - let fs = self.real_fs(); + let fs = self.fs(); Ok(Arc::new(RuntimePermissionDescriptorParser::new(fs))) }) } @@ -537,7 +564,7 @@ impl EmitterFactory { self.cjs_tracker()?.clone(), options.clone(), self.file_fetcher()?.clone(), - self.real_fs().clone(), + self.fs().clone(), self.global_http_cache().clone(), self.in_npm_pkg_checker()?.clone(), options.maybe_lockfile().cloned(), diff --git a/crates/deno_facade/eszip/migrate.rs b/crates/deno_facade/eszip/migrate.rs index feb45777b..a18ca1ebd 100644 --- a/crates/deno_facade/eszip/migrate.rs +++ b/crates/deno_facade/eszip/migrate.rs @@ -450,6 +450,7 @@ mod v2 { ca_stores: None, ca_data: None, unsafely_ignore_certificate_errors: None, + node_modules: None, }; v2_eszip.add_opaque_data( diff --git a/crates/deno_facade/eszip/mod.rs b/crates/deno_facade/eszip/mod.rs index 1927f758b..6e8745a58 100644 --- a/crates/deno_facade/eszip/mod.rs +++ b/crates/deno_facade/eszip/mod.rs @@ -9,13 +9,20 @@ use std::sync::Arc; use anyhow::anyhow; use anyhow::bail; use anyhow::Context; +use deno::deno_ast; +use deno::deno_fs::FileSystem; +use deno::deno_fs::RealFs; +use deno::deno_graph; use deno::deno_npm::NpmSystemInfo; +use deno::deno_path_util; use deno::deno_path_util::normalize_path; use deno::npm::InnerCliNpmResolverRef; +use deno::standalone::binary::NodeModules; use deno::standalone::binary::SerializedResolverWorkspaceJsrPackage; use deno::standalone::binary::SerializedWorkspaceResolver; use deno::standalone::binary::SerializedWorkspaceResolverImportMap; use deno::tools::compile; +use deno_core::error::AnyError; use deno_core::serde_json; use deno_core::url::Url; use deno_core::FastString; @@ -34,17 +41,22 @@ use eszip_trait::AsyncEszipDataRead; use eszip_trait::SUPABASE_ESZIP_VERSION; use eszip_trait::SUPABASE_ESZIP_VERSION_KEY; use fs::virtual_fs::VfsBuilder; +use fs::virtual_fs::VfsEntry; use fs::VfsOpts; +use futures::future::BoxFuture; use futures::future::OptionFuture; use futures::io::AllowStdIo; use futures::io::BufReader; use futures::AsyncReadExt; use futures::AsyncSeekExt; +use futures::FutureExt; use glob::glob; use once_cell::sync::Lazy; use regex::Regex; use scopeguard::ScopeGuard; use tokio::fs::create_dir_all; +use tokio::fs::File; +use tokio::io::AsyncWriteExt; use tokio::sync::Mutex; use tokio::sync::Semaphore; use vfs::build_npm_vfs; @@ -743,31 +755,120 @@ pub async fn generate_binary_eszip( }; let resolver = emitter_factory.npm_resolver().await.cloned()?; - let (vfs, npm_snapshot) = - match resolver.clone().as_inner() { - InnerCliNpmResolverRef::Managed(managed) => { - let snapshot = managed - .serialized_valid_snapshot_for_system(&NpmSystemInfo::default()); - if !snapshot.as_serialized().packages.is_empty() { - let npm_vfs_builder = build_npm_vfs( - VfsOpts { - npm_resolver: resolver.clone(), - }, - &mut vfs_content_callback_fn, - )?; + let (mut vfs, node_modules, npm_snapshot) = match resolver.clone().as_inner() + { + InnerCliNpmResolverRef::Managed(managed) => { + let snapshot = + managed.serialized_valid_snapshot_for_system(&NpmSystemInfo::default()); + if !snapshot.as_serialized().packages.is_empty() { + let npm_vfs_builder = build_npm_vfs( + VfsOpts { + root_path, + npm_resolver: resolver.clone(), + }, + emitter_factory.deno_options()?.clone(), + &mut vfs_content_callback_fn, + )?; - (npm_vfs_builder, Some(managed - .serialized_valid_snapshot_for_system(&NpmSystemInfo::default()) )) - } else { - ( - VfsBuilder::new(root_path, &mut vfs_content_callback_fn)?, - None, - ) + ( + npm_vfs_builder, + Some(NodeModules::Managed { + node_modules_dir: resolver.root_node_modules_path().map(|it| { + root_dir_url + .specifier_key( + &ModuleSpecifier::from_directory_path(it).unwrap(), + ) + .into_owned() + }), + }), + Some( + managed + .serialized_valid_snapshot_for_system(&NpmSystemInfo::default()), + ), + ) + } else { + ( + VfsBuilder::new(root_path, &mut vfs_content_callback_fn)?, + None, + None, + ) + } + } + InnerCliNpmResolverRef::Byonm(_) => { + let npm_vfs_builder = build_npm_vfs( + VfsOpts { + root_path, + npm_resolver: resolver.clone(), + }, + emitter_factory.deno_options()?.clone(), + vfs_content_callback_fn, + )?; + ( + npm_vfs_builder, + Some(NodeModules::Byonm { + root_node_modules_dir: resolver.root_node_modules_path().map(|it| { + root_dir_url + .specifier_key(&ModuleSpecifier::from_directory_path(it).unwrap()) + .into_owned() + }), + }), + None, + ) + } + }; + let workspace_resolver = emitter_factory.workspace_resolver()?.clone(); + if deno_options.use_byonm() { + let cjs_tracker = emitter_factory.cjs_tracker()?.clone(); + let emitter = emitter_factory.emitter()?.clone(); + for module in graph.modules() { + if module.specifier().scheme() == "data" { + continue; // don't store data urls as an entry as they're in the code + } + let maybe_source = match module { + deno_graph::Module::Js(m) => { + let source = if m.media_type.is_emittable() { + let is_cjs = cjs_tracker.is_cjs_with_known_is_script( + &m.specifier, + m.media_type, + m.is_script, + )?; + let module_kind = deno_ast::ModuleKind::from_is_cjs(is_cjs); + let source = emitter + .emit_parsed_source( + &m.specifier, + m.media_type, + module_kind, + &m.source, + ) + .await?; + source.into_bytes() + } else { + m.source.as_bytes().to_vec() + }; + Some(source) } + deno_graph::Module::Json(m) => Some(m.source.as_bytes().to_vec()), + deno_graph::Module::Wasm(m) => Some(m.source.to_vec()), + deno_graph::Module::Npm(_) + | deno_graph::Module::Node(_) + | deno_graph::Module::External(_) => None, + }; + if module.specifier().scheme() == "file" { + let file_path = deno_path_util::url_to_file_path(module.specifier())?; + vfs + .add_file( + &file_path, + match maybe_source { + Some(source) => source, + None => RealFs.read_file_sync(&file_path, None)?.into_owned(), + }, + ) + .with_context(|| { + format!("Failed adding '{}'", file_path.display()) + })?; } - InnerCliNpmResolverRef::Byonm(_) => unreachable!(), - }; - + } + } let vfs = vfs.into_dir(); let mut eszip = create_eszip_from_graph_raw( graph, @@ -819,7 +920,6 @@ pub async fn generate_binary_eszip( })) }) .collect(); - let workspace_resolver = emitter_factory.workspace_resolver()?.clone(); let serialized_workspace_resolver = SerializedWorkspaceResolver { import_map: workspace_resolver.maybe_import_map().map(|it| { SerializedWorkspaceResolverImportMap { @@ -863,6 +963,12 @@ pub async fn generate_binary_eszip( serde_json::to_vec(&serialized_workspace_resolver) .with_context(|| "failed to serialize workspace resolver")?, ); + metadata.node_modules = node_modules + .map(|it| { + serde_json::to_vec(&it) + .with_context(|| "failed to serialize node modules") + }) + .transpose()?; if let Some(static_patterns) = maybe_static_patterns { include_glob_patterns_in_eszip( @@ -974,18 +1080,94 @@ pub async fn extract_eszip(payload: ExtractEszipPayload) -> bool { eszip.ensure_read_all().await.unwrap(); + let mut metadata = match OptionFuture::<_>::from( + eszip + .ensure_module(eszip_trait::v2::METADATA_KEY) + .map(|it| async move { it.source().await }), + ) + .await + .flatten() + .map(|it| { + rkyv::from_bytes::(it.as_ref()) + .map_err(|_| anyhow!("failed to deserialize metadata from eszip")) + }) + .transpose() + { + Ok(metadata) => metadata, + Err(err) => { + log::error!("{err}"); + return false; + } + } + .unwrap_or_default(); + let node_modules = match metadata.node_modules() { + Ok(node_modules) => node_modules, + Err(err) => { + log::error!("{err}"); + return false; + } + }; + let use_byonm = matches!(node_modules, Some(NodeModules::Byonm { .. })); + if !output_folder.exists() { create_dir_all(&output_folder).await.unwrap(); } + if use_byonm { + fn extract_entries( + eszip: Arc, + entries: Vec, + base_path: PathBuf, + ) -> BoxFuture<'static, Result<(), AnyError>> { + async move { + for entry in entries { + match entry { + VfsEntry::Dir(virtual_directory) => { + let path = base_path.join(&virtual_directory.name); + create_dir_all(&path).await.unwrap(); + extract_entries(eszip.clone(), virtual_directory.entries, path) + .await?; + } + VfsEntry::File(virtual_file) => { + let path = base_path.join(&virtual_file.name); + let module_content = eszip + .get_module(&virtual_file.key) + .unwrap() + .source() + .await + .unwrap(); + let mut file = File::create(&path).await.unwrap(); + file.write_all(module_content.as_ref()).await.unwrap(); + } + VfsEntry::Symlink(virtual_symlink) => { + let name = virtual_symlink.name; + bail!("found unexpected symlink: {name}"); + } + } + } + Ok(()) + } + .boxed() + } - let file_specifiers = extract_file_specifiers(&eszip); - if let Some(lowest_path) = - deno::util::path::find_lowest_path(&file_specifiers) - { - extract_modules(&eszip, &file_specifiers, &lowest_path, &output_folder) - .await; + let eszip = Arc::new(eszip); + let Some(dir) = metadata.virtual_dir.take() else { + return true; + }; + if let Err(err) = extract_entries(eszip, dir.entries, output_folder).await { + log::error!("{err}"); + return false; + } true } else { - panic!("Path seems to be invalid"); + let file_specifiers = extract_file_specifiers(&eszip); + if let Some(lowest_path) = + deno::util::path::find_lowest_path(&file_specifiers) + { + extract_modules(&eszip, &file_specifiers, &lowest_path, &output_folder) + .await; + true + } else { + panic!("Path seems to be invalid"); + } } } diff --git a/crates/deno_facade/eszip/vfs.rs b/crates/deno_facade/eszip/vfs.rs index 588fe4532..3a7bd77ae 100644 --- a/crates/deno_facade/eszip/vfs.rs +++ b/crates/deno_facade/eszip/vfs.rs @@ -1,10 +1,13 @@ +use std::collections::VecDeque; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; +use anyhow::Context; use deno::deno_npm::NpmSystemInfo; use deno::npm::CliNpmResolver; use deno::npm::InnerCliNpmResolverRef; +use deno::DenoOptions; use deno_core::error::AnyError; use eszip_trait::AsyncEszipDataRead; use fs::virtual_fs::FileBackedVfs; @@ -47,6 +50,7 @@ pub fn load_npm_vfs( pub fn build_npm_vfs<'scope, F>( opts: VfsOpts, + deno_options: Arc, add_content_callback_fn: F, ) -> Result, AnyError> where @@ -119,9 +123,36 @@ where Ok(builder) } } - - _ => { - unreachable!(); + InnerCliNpmResolverRef::Byonm(_) => { + let mut builder = + VfsBuilder::new(opts.root_path.clone(), add_content_callback_fn)?; + for pkg_json in deno_options.workspace().package_jsons() { + builder.add_file_at_path(&pkg_json.path)?; + } + // traverse and add all the node_modules directories in the workspace + let mut pending_dirs = VecDeque::new(); + pending_dirs + .push_back(deno_options.workspace().root_dir().to_file_path().unwrap()); + while let Some(pending_dir) = pending_dirs.pop_front() { + let mut entries = std::fs::read_dir(&pending_dir) + .with_context(|| { + format!("Failed reading: {}", pending_dir.display()) + })? + .collect::, _>>()?; + entries.sort_by_cached_key(|entry| entry.file_name()); // determinism + for entry in entries { + let path = entry.path(); + if !path.is_dir() { + continue; + } + if path.ends_with("node_modules") { + builder.add_dir_recursive(&path)?; + } else { + pending_dirs.push_back(path); + } + } + } + Ok(builder) } } } diff --git a/crates/deno_facade/metadata.rs b/crates/deno_facade/metadata.rs index ad1db9538..7592bb584 100644 --- a/crates/deno_facade/metadata.rs +++ b/crates/deno_facade/metadata.rs @@ -9,6 +9,7 @@ use deno::deno_npm; use deno::deno_npm::npm_rc::RegistryConfigWithUrl; use deno::deno_npm::npm_rc::ResolvedNpmRc; use deno::deno_path_util::normalize_path; +use deno::standalone::binary::NodeModules; use deno::standalone::binary::SerializedWorkspaceResolver; use deno_core::error::AnyError; use deno_core::serde_json; @@ -40,6 +41,7 @@ pub struct Metadata { pub ca_stores: Option>, pub ca_data: Option>, pub unsafely_ignore_certificate_errors: Option>, + pub node_modules: Option>, } impl Metadata { @@ -51,7 +53,7 @@ impl Metadata { .serialized_workspace_resolver_raw .as_ref() .map(|it| { - serde_json::from_slice::(it.as_slice()) + serde_json::from_slice(it.as_slice()) .context("failed to deserialize workspace resolver from metadata") }) .transpose()? @@ -59,6 +61,17 @@ impl Metadata { ) } + pub fn node_modules(&self) -> Result, AnyError> { + self + .node_modules + .as_ref() + .map(|it| { + serde_json::from_slice(it.as_slice()) + .context("failed to deserialize node modules from metadata") + }) + .transpose() + } + pub fn resolved_npmrc( &self, registry_yrl: &Url, diff --git a/crates/deno_facade/module_loader/standalone.rs b/crates/deno_facade/module_loader/standalone.rs index ad78f19c0..9f0daaa66 100644 --- a/crates/deno_facade/module_loader/standalone.rs +++ b/crates/deno_facade/module_loader/standalone.rs @@ -34,17 +34,20 @@ use deno::node_resolver::analyze::NodeCodeTranslator; use deno::node_resolver::NodeResolutionKind; use deno::node_resolver::PackageJsonResolver; use deno::node_resolver::ResolutionMode; +use deno::npm::byonm::CliByonmNpmResolverCreateOptions; +use deno::npm::create_cli_npm_resolver; use deno::npm::create_in_npm_pkg_checker; -use deno::npm::create_managed_npm_resolver; use deno::npm::CliManagedInNpmPkgCheckerCreateOptions; use deno::npm::CliManagedNpmResolverCreateOptions; use deno::npm::CliNpmResolver; +use deno::npm::CliNpmResolverCreateOptions; use deno::npm::CliNpmResolverManagedSnapshotOption; use deno::npm::CreateInNpmPkgCheckerOptions; use deno::resolver::CjsTracker; use deno::resolver::CliDenoResolverFs; use deno::resolver::CliNpmReqResolver; use deno::resolver::NpmModuleLoader; +use deno::standalone::binary; use deno::util::text_encoding::from_utf8_lossy_cow; use deno::PermissionsContainer; use deno_config::workspace::MappedResolution; @@ -126,6 +129,7 @@ impl WorkspaceEszip { } pub struct SharedModuleLoaderState { + pub(crate) root_path: PathBuf, pub(crate) eszip: WorkspaceEszip, pub(crate) workspace_resolver: WorkspaceResolver, pub(crate) cjs_tracker: Arc, @@ -159,8 +163,7 @@ impl ModuleLoader for EmbeddedModuleLoader { ))); } - let current_dir = std::env::current_dir().unwrap(); - deno_core::resolve_path(".", ¤t_dir)? + deno_core::resolve_path(".", &self.shared.root_path)? } else { ModuleSpecifier::parse(referrer).map_err(|err| { type_error(format!("Referrer uses invalid specifier: {}", err)) @@ -558,9 +561,15 @@ pub async fn create_module_loader_for_eszip( } .join(format!("sb-compile-{}", current_exe_name)); + let node_modules = metadata.node_modules()?; let root_dir_url = Arc::new(ModuleSpecifier::from_directory_path(&root_path).unwrap()); - let root_node_modules_path = root_path.join("node_modules"); + let root_node_modules_path = match &node_modules { + Some(binary::NodeModules::Managed { .. }) | None => { + root_path.join("node_modules") + } + Some(binary::NodeModules::Byonm { .. }) => root_path.clone(), + }; let static_files = metadata.static_assets_lookup(&root_path); // use a dummy npm registry url @@ -606,33 +615,57 @@ pub async fn create_module_loader_for_eszip( let pkg_json_resolver = Arc::new(PackageJsonResolver::new( ext_node::DenoFsNodeResolverEnv::new(fs.clone()), )); - let in_npm_pkg_checker = - create_in_npm_pkg_checker(CreateInNpmPkgCheckerOptions::Managed( - CliManagedInNpmPkgCheckerCreateOptions { - root_cache_dir_url: npm_cache_dir.root_dir_url(), - maybe_node_modules_path: None, - }, - )); - - let npm_resolver = - create_managed_npm_resolver(CliManagedNpmResolverCreateOptions { - snapshot: CliNpmResolverManagedSnapshotOption::Specified( - snapshot.clone(), - ), - maybe_lockfile: None, - fs: fs.clone(), - http_client_provider, - npm_cache_dir: npm_cache_dir.clone(), - cache_setting: CacheSetting::Use, - maybe_node_modules_path: None, - npm_system_info: Default::default(), - npm_install_deps_provider: Arc::new( - // this is only used for installing packages, which isn't necessary with deno compile - NpmInstallDepsProvider::empty(), - ), - npmrc, - }) - .await?; + let (in_npm_pkg_checker, npm_resolver) = match node_modules { + Some(binary::NodeModules::Managed { .. }) | None => { + let in_npm_pkg_checker = + create_in_npm_pkg_checker(CreateInNpmPkgCheckerOptions::Managed( + CliManagedInNpmPkgCheckerCreateOptions { + root_cache_dir_url: npm_cache_dir.root_dir_url(), + maybe_node_modules_path: None, + }, + )); + let npm_resolver = + create_cli_npm_resolver(CliNpmResolverCreateOptions::Managed( + CliManagedNpmResolverCreateOptions { + snapshot: CliNpmResolverManagedSnapshotOption::Specified( + snapshot.clone(), + ), + maybe_lockfile: None, + fs: fs.clone(), + http_client_provider, + npm_cache_dir: npm_cache_dir.clone(), + cache_setting: CacheSetting::Use, + maybe_node_modules_path: None, + npm_system_info: Default::default(), + npm_install_deps_provider: Arc::new( + // this is only used for installing packages, which isn't necessary + // with deno compile + NpmInstallDepsProvider::empty(), + ), + npmrc, + }, + )) + .await?; + (in_npm_pkg_checker, npm_resolver) + } + Some(binary::NodeModules::Byonm { + root_node_modules_dir, + }) => { + let root_node_modules_dir = + root_node_modules_dir.map(|p| vfs.root().join(p)); + let in_npm_pkg_checker = + create_in_npm_pkg_checker(CreateInNpmPkgCheckerOptions::Byonm); + let npm_resolver = create_cli_npm_resolver( + CliNpmResolverCreateOptions::Byonm(CliByonmNpmResolverCreateOptions { + fs: CliDenoResolverFs(fs.clone()), + pkg_json_resolver: pkg_json_resolver.clone(), + root_node_modules_dir, + }), + ) + .await?; + (in_npm_pkg_checker, npm_resolver) + } + }; let node_resolver = Arc::new(NodeResolver::new( DenoFsNodeResolverEnv::new(fs.clone()), @@ -678,6 +711,7 @@ pub async fn create_module_loader_for_eszip( let module_loader_factory = StandaloneModuleLoaderFactory { shared: Arc::new(SharedModuleLoaderState { + root_path, eszip: WorkspaceEszip { eszip, root_dir_url: root_dir_url.clone(), diff --git a/crates/fs/impl/virtual_fs.rs b/crates/fs/impl/virtual_fs.rs index df4ecf72e..748341ae7 100644 --- a/crates/fs/impl/virtual_fs.rs +++ b/crates/fs/impl/virtual_fs.rs @@ -209,7 +209,11 @@ impl<'scope> VfsBuilder<'scope> { self.add_file(path, file_bytes) } - fn add_file(&mut self, path: &Path, data: Vec) -> Result<(), AnyError> { + pub fn add_file( + &mut self, + path: &Path, + data: Vec, + ) -> Result<(), AnyError> { log::debug!("Adding file '{}'", path.display()); let checksum = checksum::gen(&[&data]); let offset = if let Some(offset) = self.file_offsets.get(&checksum) { diff --git a/crates/fs/lib.rs b/crates/fs/lib.rs index 43c0f3663..2878e91c7 100644 --- a/crates/fs/lib.rs +++ b/crates/fs/lib.rs @@ -1,3 +1,4 @@ +use std::path::PathBuf; use std::sync::Arc; use deno::npm::CliNpmResolver; @@ -16,5 +17,6 @@ pub use r#impl::virtual_fs; pub use rt::IO_RT; pub struct VfsOpts { + pub root_path: PathBuf, pub npm_resolver: Arc, } diff --git a/deno/lib.rs b/deno/lib.rs index e3ba6580b..c1b5b7fdc 100644 --- a/deno/lib.rs +++ b/deno/lib.rs @@ -81,6 +81,7 @@ pub fn version() -> &'static str { } pub struct DenoOptions { + initial_cwd: PathBuf, maybe_node_modules_folder: Option, npmrc: Arc, maybe_lockfile: Option>, @@ -90,6 +91,10 @@ pub struct DenoOptions { } impl DenoOptions { + pub fn initial_cwd(&self) -> &Path { + &self.initial_cwd + } + pub fn npmrc(&self) -> &Arc { &self.npmrc } @@ -110,8 +115,23 @@ impl DenoOptions { self.builder.unstable_detect_cjs.unwrap_or_default() } + fn byonm_enabled(&self) -> bool { + self.node_modules_dir().ok().flatten() == Some(NodeModulesDirMode::Manual) + } + pub fn use_byonm(&self) -> bool { - self.builder.use_byonm.unwrap_or_default() + if self.node_modules_dir().ok().flatten().is_none() + && self.maybe_node_modules_folder.is_some() + && self + .workspace() + .config_folders() + .values() + .any(|it| it.pkg_json.is_some()) + { + return true; + } + + self.byonm_enabled() } pub fn is_node_main(&self) -> bool { @@ -293,6 +313,7 @@ impl DenoOptions { load_env_variables_from_env_file(builder.env_file.as_ref()); Ok(Self { + initial_cwd, maybe_node_modules_folder, npmrc, maybe_lockfile: maybe_lockfile.map(Arc::new), diff --git a/deno/npm/mod.rs b/deno/npm/mod.rs index b4e289b11..5dadcb254 100644 --- a/deno/npm/mod.rs +++ b/deno/npm/mod.rs @@ -8,10 +8,12 @@ use std::path::Path; use std::sync::Arc; use byonm::CliByonmNpmResolver; +use byonm::CliByonmNpmResolverCreateOptions; use deno_core::error::AnyError; use deno_core::url::Url; use deno_fs::FileSystem; use deno_resolver::npm::ByonmInNpmPackageChecker; +use deno_resolver::npm::ByonmNpmResolver; use deno_resolver::npm::CliNpmReqResolver; use ext_node::NodePermissions; use http::HeaderName; @@ -127,6 +129,21 @@ impl deno_npm_cache::NpmCacheEnv for CliNpmCacheEnv { // Byonm, // } +pub enum CliNpmResolverCreateOptions { + Managed(CliManagedNpmResolverCreateOptions), + Byonm(CliByonmNpmResolverCreateOptions), +} + +pub async fn create_cli_npm_resolver( + options: CliNpmResolverCreateOptions, +) -> Result, AnyError> { + use CliNpmResolverCreateOptions::*; + match options { + Managed(options) => managed::create_managed_npm_resolver(options).await, + Byonm(options) => Ok(Arc::new(ByonmNpmResolver::new(options))), + } +} + pub enum CreateInNpmPkgCheckerOptions<'a> { Managed(CliManagedInNpmPkgCheckerCreateOptions<'a>), Byonm, diff --git a/deno/standalone/binary.rs b/deno/standalone/binary.rs index 506e7222a..a18be402b 100644 --- a/deno/standalone/binary.rs +++ b/deno/standalone/binary.rs @@ -9,6 +9,17 @@ use indexmap::IndexMap; use serde::Deserialize; use serde::Serialize; +#[derive(Deserialize, Serialize)] +pub enum NodeModules { + Managed { + /// Relative path for the node_modules directory in the vfs. + node_modules_dir: Option, + }, + Byonm { + root_node_modules_dir: Option, + }, +} + #[derive(Deserialize, Serialize)] pub struct SerializedWorkspaceResolverImportMap { pub specifier: String, diff --git a/examples/commonjs/index.js b/examples/commonjs/index.js index 2ecd3ae99..7ddc64580 100644 --- a/examples/commonjs/index.js +++ b/examples/commonjs/index.js @@ -1,11 +1,15 @@ const http = require("http"); +const isOdd = require("is-odd"); console.log(require); +console.log(isOdd(33)); -http.createServer((_, resp) => { +const server = http.createServer((_, resp) => { resp.writeHead(200, { "content-type": "text-plain", }); - resp.write("meow\n"); + resp.write("Hello, World!\n"); resp.end(); }); + +server.listen(8080); diff --git a/examples/commonjs/package.json b/examples/commonjs/package.json index 35e47b772..c72f9a87e 100644 --- a/examples/commonjs/package.json +++ b/examples/commonjs/package.json @@ -4,5 +4,8 @@ "main": "index.js", "devDependencies": { "@types/node": "^22.13.4" + }, + "dependencies": { + "is-odd": "^3.0.1" } }