use deno_ast::{MediaType, ParseParams}; use deno_core::{ModuleCodeString, ModuleName, SourceMapData}; use deno_error::JsErrorBox; use deno_fetch::FetchPermissions; use deno_net::NetPermissions; use deno_web::{BlobStore, TimersPermission}; use std::borrow::Cow; use std::env; use std::io::Write; use std::path::{Path, PathBuf}; use std::sync::Arc; pub struct PermissionsContainer; impl FetchPermissions for PermissionsContainer { #[inline(always)] fn check_net_url( &mut self, _url: &deno_core::url::Url, _api_name: &str, ) -> Result<(), deno_permissions::PermissionCheckError> { unreachable!("snapshotting") } #[inline(always)] fn check_read<'a>( &mut self, _path: Cow<'a, Path>, _api_name: &str, _get_path: &'a dyn deno_fs::GetPath, ) -> Result, deno_io::fs::FsError> { unreachable!("snapshotting") } #[inline(always)] fn check_write<'a>( &mut self, _path: Cow<'a, Path>, _api_name: &str, _get_path: &'a dyn deno_fs::GetPath, ) -> Result, deno_io::fs::FsError> { unreachable!("snapshotting") } #[inline(always)] fn check_net_vsock( &mut self, _cid: u32, _port: u32, _api_name: &str, ) -> Result<(), deno_permissions::PermissionCheckError> { unreachable!("snapshotting") } } impl TimersPermission for PermissionsContainer { #[inline(always)] fn allow_hrtime(&mut self) -> bool { true } } impl NetPermissions for PermissionsContainer { fn check_read( &mut self, _p: &str, _api_name: &str, ) -> Result { unreachable!("snapshotting") } fn check_write( &mut self, _p: &str, _api_name: &str, ) -> Result { unreachable!("snapshotting") } fn check_net>( &mut self, _host: &(T, Option), _api_name: &str, ) -> Result<(), deno_permissions::PermissionCheckError> { unreachable!("snapshotting") } fn check_write_path<'a>( &mut self, _p: Cow<'a, Path>, _api_name: &str, ) -> Result, deno_permissions::PermissionCheckError> { unreachable!("snapshotting") } fn check_vsock( &mut self, _cid: u32, _port: u32, _api_name: &str, ) -> Result<(), deno_permissions::PermissionCheckError> { unreachable!("snapshotting") } } deno_core::extension!( fetch, esm_entry_point = "ext:fetch/src/runtime.js", esm = ["src/runtime.js"], ); // `extension_transpiler` callback for `deno_core::snapshot::create_snapshot`. // // Specialized to our snapshot's inputs. Of the seven deno_* extensions // we register via `init()`, six ship pre-built `.js` files // in their `esm` lists (webidl/url/console/web/fetch/net) — only // `deno_telemetry`'s `extension!` macro lists `.ts` files // (`telemetry.ts`, `util.ts`), so the TypeScript branch is needed // solely for that crate. Our local `fetch` extension contributes // `src/runtime.js` (pure JS). No `node:` imports happen at snapshot // build time, no `.mjs`, no user-supplied modules. So: // - `.js` → pass through. // - `.ts` → transpile via deno_ast (deno_telemetry only). // - anything else → build bug (deno shipping an unexpected file type // or us mislabelling one), panic loudly rather than emit a broken // snapshot. // // No source maps: the snapshot is a binary blob the runtime loads — source // maps would never be consumed. // // The signature still returns `Result<_, JsErrorBox>` because that's what // `extension_transpiler` expects, but we never construct one — parse and // transpile failures are build-time bugs in deno's own .ts internals (or // in our runtime.js, if we ever change its extension), so they panic. // // This replaces a call to `deno_runtime::transpile::maybe_transpile_source` // from `deno_runtime 0.198.0`. The original is more general (handles // `node:` modules, `.mjs`, emits source maps in debug builds, plumbs // errors via `JsErrorBox`); none of that surface is reachable in our // build. Dropping the `deno_runtime` dep eliminates a // `deno_cache → rusqlite → libsqlite3-sys 0.35` transitive chain that // collides with sqlx-sqlite's `libsqlite3-sys 0.30` (cargo's // `links = "sqlite3"` rule). fn maybe_transpile_source( name: ModuleName, source: ModuleCodeString, ) -> Result<(ModuleCodeString, Option), JsErrorBox> { let media_type = MediaType::from_path(Path::new(&name)); match media_type { MediaType::JavaScript => return Ok((source, None)), MediaType::TypeScript => {} _ => panic!("unexpected media type {media_type:?} for {name} during snapshot build"), } let parsed = deno_ast::parse_module(ParseParams { specifier: deno_core::url::Url::parse(&name).unwrap(), text: source.into(), media_type, capture_tokens: false, scope_analysis: false, maybe_syntax: None, }) .unwrap_or_else(|e| panic!("snapshot transpile: parse failed for {name}: {e}")); let transpiled = parsed .transpile( &deno_ast::TranspileOptions { imports_not_used_as_values: deno_ast::ImportsNotUsedAsValues::Remove, ..Default::default() }, &deno_ast::TranspileModuleOptions::default(), &deno_ast::EmitOptions::default(), ) .unwrap_or_else(|e| panic!("snapshot transpile: emit failed for {name}: {e}")) .into_source(); Ok((transpiled.text.into(), None)) } fn main() { println!("cargo:rustc-env=TARGET={}", env::var("TARGET").unwrap()); println!("cargo:rustc-env=PROFILE={}", env::var("PROFILE").unwrap()); let exts = vec![ deno_telemetry::deno_telemetry::init(), deno_webidl::deno_webidl::init(), deno_url::deno_url::init(), deno_console::deno_console::init(), deno_web::deno_web::init::(Arc::new(BlobStore::default()), None), deno_fetch::deno_fetch::init::(Default::default()), deno_net::deno_net::init::(None, None), fetch::init(), ]; // Build the file path to the snapshot. let o = PathBuf::from(env::var_os("OUT_DIR").unwrap()); let snapshot_path = o.join("FETCH_SNAPSHOT.bin"); // Create the snapshot. let output = deno_core::snapshot::create_snapshot( deno_core::snapshot::CreateSnapshotOptions { cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"), startup_snapshot: None, extension_transpiler: Some(std::rc::Rc::new(|specifier, source| { maybe_transpile_source(specifier, source) })), extensions: exts, with_runtime_cb: None, skip_op_registration: false, }, None, ) .unwrap(); let mut file = std::fs::File::create(snapshot_path).unwrap(); file.write_all(&output.output).unwrap(); for path in output.files_loaded_during_snapshot { println!("cargo:rerun-if-changed={}", path.display()); } }