diff --git a/Cargo.lock b/Cargo.lock
index 6577368826..23ddcb6772 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1971,11 +1971,12 @@ dependencies = [
[[package]]
name = "deno_media_type"
-version = "0.2.3"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a417f8bd3f1074185c4c8ccb6ea6261ae173781596cc358e68ad07aaac11009d"
+checksum = "577fe2bbe04f3e9b1b7c6fac6a75101a9fbd611c50a6b68789e69f4d63dcb2b4"
dependencies = [
"data-url",
+ "encoding_rs",
"serde",
"url",
]
@@ -2352,7 +2353,6 @@ dependencies = [
"dlopen2",
"encoding_rs",
"fastwebsockets",
- "flate2",
"http 1.1.0",
"http-body-util",
"hyper 0.14.28",
@@ -2693,12 +2693,11 @@ name = "denort"
version = "2.1.5"
dependencies = [
"async-trait",
- "deno_ast",
+ "bincode",
"deno_cache_dir",
"deno_config",
"deno_core",
"deno_error",
- "deno_graph",
"deno_lib",
"deno_media_type",
"deno_npm",
@@ -2716,8 +2715,10 @@ dependencies = [
"node_resolver",
"pretty_assertions",
"serde",
+ "serde_json",
"sys_traits",
"test_server",
+ "thiserror 2.0.3",
"tokio",
"tokio-util",
"twox-hash",
@@ -5257,6 +5258,7 @@ dependencies = [
"once_cell",
"path-clean",
"regex",
+ "serde",
"serde_json",
"sys_traits",
"thiserror 2.0.3",
diff --git a/Cargo.toml b/Cargo.toml
index 8ca7a0e923..62e87eb791 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -56,7 +56,7 @@ deno_core = { version = "0.331.0" }
deno_bench_util = { version = "0.180.0", path = "./bench_util" }
deno_config = { version = "=0.45.0", features = ["workspace", "sync"] }
deno_lockfile = "=0.24.0"
-deno_media_type = { version = "0.2.3", features = ["module_specifier"] }
+deno_media_type = { version = "0.2.4", features = ["module_specifier"] }
deno_npm = "=0.27.2"
deno_path_util = "=0.3.0"
deno_permissions = { version = "0.45.0", path = "./runtime/permissions" }
diff --git a/README.md b/README.md
index 19d4fa8a12..ca71529e28 100644
--- a/README.md
+++ b/README.md
@@ -6,8 +6,8 @@
-[Deno](https://www.deno.com)
-([/ˈdiːnoʊ/](http://ipa-reader.xyz/?text=%CB%88di%CB%90no%CA%8A), pronounced
+[Deno](https://deno.com)
+([/ˈdiːnoʊ/](https://ipa-reader.com/?text=%CB%88di%CB%90no%CA%8A), pronounced
`dee-no`) is a JavaScript, TypeScript, and WebAssembly runtime with secure
defaults and a great developer experience. It's built on [V8](https://v8.dev/),
[Rust](https://www.rust-lang.org/), and [Tokio](https://tokio.rs/).
diff --git a/cli/factory.rs b/cli/factory.rs
index 4f3090651f..09813b301b 100644
--- a/cli/factory.rs
+++ b/cli/factory.rs
@@ -813,19 +813,11 @@ impl CliFactory {
.services
.node_code_translator
.get_or_try_init_async(async {
- let caches = self.caches()?;
- let node_analysis_cache =
- NodeAnalysisCache::new(caches.node_analysis_db());
let node_resolver = self.node_resolver().await?.clone();
- let cjs_esm_analyzer = CliCjsCodeAnalyzer::new(
- node_analysis_cache,
- self.cjs_tracker()?.clone(),
- self.fs().clone(),
- Some(self.parsed_source_cache().clone()),
- );
+ let cjs_code_analyzer = self.create_cjs_code_analyzer()?;
Ok(Arc::new(NodeCodeTranslator::new(
- cjs_esm_analyzer,
+ cjs_code_analyzer,
self.in_npm_pkg_checker()?.clone(),
node_resolver,
self.npm_resolver().await?.clone(),
@@ -836,6 +828,17 @@ impl CliFactory {
.await
}
+ fn create_cjs_code_analyzer(&self) -> Result {
+ let caches = self.caches()?;
+ let node_analysis_cache = NodeAnalysisCache::new(caches.node_analysis_db());
+ Ok(CliCjsCodeAnalyzer::new(
+ node_analysis_cache,
+ self.cjs_tracker()?.clone(),
+ self.fs().clone(),
+ Some(self.parsed_source_cache().clone()),
+ ))
+ }
+
pub async fn npm_req_resolver(
&self,
) -> Result<&Arc, AnyError> {
@@ -1025,6 +1028,7 @@ impl CliFactory {
) -> Result {
let cli_options = self.cli_options()?;
Ok(DenoCompileBinaryWriter::new(
+ self.create_cjs_code_analyzer()?,
self.cjs_tracker()?,
self.cli_options()?,
self.deno_dir()?,
diff --git a/cli/lib/build.rs b/cli/lib/build.rs
index b093009fe0..1f52e0c02a 100644
--- a/cli/lib/build.rs
+++ b/cli/lib/build.rs
@@ -1,6 +1,15 @@
// Copyright 2018-2025 the Deno authors. MIT license.
fn main() {
+ // todo(dsherret): remove this after Deno 2.2.0 is published and then
+ // align the version of this crate with Deno then. We need to wait because
+ // there was previously a deno_lib 2.2.0 published (https://crates.io/crates/deno_lib/versions)
+ let version_path = std::path::Path::new(".").join("version.txt");
+ println!("cargo:rerun-if-changed={}", version_path.display());
+ #[allow(clippy::disallowed_methods)]
+ let text = std::fs::read_to_string(version_path).unwrap();
+ println!("cargo:rustc-env=DENO_VERSION={}", text);
+
let commit_hash = git_commit_hash();
println!("cargo:rustc-env=GIT_COMMIT_HASH={}", commit_hash);
println!("cargo:rerun-if-env-changed=GIT_COMMIT_HASH");
diff --git a/cli/lib/standalone/binary.rs b/cli/lib/standalone/binary.rs
index eb158d414e..ae02197bf4 100644
--- a/cli/lib/standalone/binary.rs
+++ b/cli/lib/standalone/binary.rs
@@ -4,10 +4,12 @@ use std::borrow::Cow;
use std::collections::BTreeMap;
use deno_config::workspace::PackageJsonDepResolution;
+use deno_media_type::MediaType;
use deno_runtime::deno_permissions::PermissionsOptions;
use deno_runtime::deno_telemetry::OtelConfig;
use deno_semver::Version;
use indexmap::IndexMap;
+use node_resolver::analyze::CjsAnalysisExports;
use serde::Deserialize;
use serde::Serialize;
use url::Url;
@@ -17,6 +19,24 @@ use crate::args::UnstableConfig;
pub const MAGIC_BYTES: &[u8; 8] = b"d3n0l4nd";
+pub trait DenoRtDeserializable<'a>: Sized {
+ fn deserialize(input: &'a [u8]) -> std::io::Result<(&'a [u8], Self)>;
+}
+
+impl<'a> DenoRtDeserializable<'a> for Cow<'a, [u8]> {
+ fn deserialize(input: &'a [u8]) -> std::io::Result<(&'a [u8], Self)> {
+ let (input, data) = read_bytes_with_u32_len(input)?;
+ Ok((input, Cow::Borrowed(data)))
+ }
+}
+
+pub trait DenoRtSerializable<'a> {
+ fn serialize(
+ &'a self,
+ builder: &mut capacity_builder::BytesBuilder<'a, Vec>,
+ );
+}
+
#[derive(Deserialize, Serialize)]
pub enum NodeModules {
Managed {
@@ -73,19 +93,208 @@ pub struct Metadata {
pub vfs_case_sensitivity: FileSystemCaseSensitivity,
}
-pub struct SourceMapStore {
- data: IndexMap, Cow<'static, [u8]>>,
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub struct SpecifierId(u32);
+
+impl SpecifierId {
+ pub fn new(id: u32) -> Self {
+ Self(id)
+ }
}
-impl SourceMapStore {
+impl<'a> capacity_builder::BytesAppendable<'a> for SpecifierId {
+ fn append_to_builder(
+ self,
+ builder: &mut capacity_builder::BytesBuilder<'a, TBytes>,
+ ) {
+ builder.append_le(self.0);
+ }
+}
+
+impl<'a> DenoRtSerializable<'a> for SpecifierId {
+ fn serialize(
+ &'a self,
+ builder: &mut capacity_builder::BytesBuilder<'a, Vec>,
+ ) {
+ builder.append_le(self.0);
+ }
+}
+
+impl<'a> DenoRtDeserializable<'a> for SpecifierId {
+ fn deserialize(input: &'a [u8]) -> std::io::Result<(&'a [u8], Self)> {
+ let (input, id) = read_u32(input)?;
+ Ok((input, Self(id)))
+ }
+}
+
+#[derive(Deserialize, Serialize)]
+pub enum CjsExportAnalysisEntry {
+ Esm,
+ Cjs(CjsAnalysisExports),
+}
+
+const HAS_TRANSPILED_FLAG: u8 = 1 << 0;
+const HAS_SOURCE_MAP_FLAG: u8 = 1 << 1;
+const HAS_CJS_EXPORT_ANALYSIS_FLAG: u8 = 1 << 2;
+
+pub struct RemoteModuleEntry<'a> {
+ pub media_type: MediaType,
+ pub data: Cow<'a, [u8]>,
+ pub maybe_transpiled: Option>,
+ pub maybe_source_map: Option>,
+ pub maybe_cjs_export_analysis: Option>,
+}
+
+impl<'a> DenoRtSerializable<'a> for RemoteModuleEntry<'a> {
+ fn serialize(
+ &'a self,
+ builder: &mut capacity_builder::BytesBuilder<'a, Vec>,
+ ) {
+ fn append_maybe_data<'a>(
+ builder: &mut capacity_builder::BytesBuilder<'a, Vec>,
+ maybe_data: Option<&'a [u8]>,
+ ) {
+ if let Some(data) = maybe_data {
+ builder.append_le(data.len() as u32);
+ builder.append(data);
+ }
+ }
+
+ let mut has_data_flags = 0;
+ if self.maybe_transpiled.is_some() {
+ has_data_flags |= HAS_TRANSPILED_FLAG;
+ }
+ if self.maybe_source_map.is_some() {
+ has_data_flags |= HAS_SOURCE_MAP_FLAG;
+ }
+ if self.maybe_cjs_export_analysis.is_some() {
+ has_data_flags |= HAS_CJS_EXPORT_ANALYSIS_FLAG;
+ }
+ builder.append(serialize_media_type(self.media_type));
+ builder.append_le(self.data.len() as u32);
+ builder.append(self.data.as_ref());
+ builder.append(has_data_flags);
+ append_maybe_data(builder, self.maybe_transpiled.as_deref());
+ append_maybe_data(builder, self.maybe_source_map.as_deref());
+ append_maybe_data(builder, self.maybe_cjs_export_analysis.as_deref());
+ }
+}
+
+impl<'a> DenoRtDeserializable<'a> for RemoteModuleEntry<'a> {
+ fn deserialize(input: &'a [u8]) -> std::io::Result<(&'a [u8], Self)> {
+ #[allow(clippy::type_complexity)]
+ fn deserialize_data_if_has_flag(
+ input: &[u8],
+ has_data_flags: u8,
+ flag: u8,
+ ) -> std::io::Result<(&[u8], Option>)> {
+ if has_data_flags & flag != 0 {
+ let (input, bytes) = read_bytes_with_u32_len(input)?;
+ Ok((input, Some(Cow::Borrowed(bytes))))
+ } else {
+ Ok((input, None))
+ }
+ }
+
+ let (input, media_type) = MediaType::deserialize(input)?;
+ let (input, data) = read_bytes_with_u32_len(input)?;
+ let (input, has_data_flags) = read_u8(input)?;
+ let (input, maybe_transpiled) =
+ deserialize_data_if_has_flag(input, has_data_flags, HAS_TRANSPILED_FLAG)?;
+ let (input, maybe_source_map) =
+ deserialize_data_if_has_flag(input, has_data_flags, HAS_SOURCE_MAP_FLAG)?;
+ let (input, maybe_cjs_export_analysis) = deserialize_data_if_has_flag(
+ input,
+ has_data_flags,
+ HAS_CJS_EXPORT_ANALYSIS_FLAG,
+ )?;
+ Ok((
+ input,
+ Self {
+ media_type,
+ data: Cow::Borrowed(data),
+ maybe_transpiled,
+ maybe_source_map,
+ maybe_cjs_export_analysis,
+ },
+ ))
+ }
+}
+
+fn serialize_media_type(media_type: MediaType) -> u8 {
+ match media_type {
+ MediaType::JavaScript => 0,
+ MediaType::Jsx => 1,
+ MediaType::Mjs => 2,
+ MediaType::Cjs => 3,
+ MediaType::TypeScript => 4,
+ MediaType::Mts => 5,
+ MediaType::Cts => 6,
+ MediaType::Dts => 7,
+ MediaType::Dmts => 8,
+ MediaType::Dcts => 9,
+ MediaType::Tsx => 10,
+ MediaType::Json => 11,
+ MediaType::Wasm => 12,
+ MediaType::Css => 13,
+ MediaType::SourceMap => 14,
+ MediaType::Unknown => 15,
+ }
+}
+
+impl<'a> DenoRtDeserializable<'a> for MediaType {
+ fn deserialize(input: &'a [u8]) -> std::io::Result<(&'a [u8], Self)> {
+ let (input, value) = read_u8(input)?;
+ let value = match value {
+ 0 => MediaType::JavaScript,
+ 1 => MediaType::Jsx,
+ 2 => MediaType::Mjs,
+ 3 => MediaType::Cjs,
+ 4 => MediaType::TypeScript,
+ 5 => MediaType::Mts,
+ 6 => MediaType::Cts,
+ 7 => MediaType::Dts,
+ 8 => MediaType::Dmts,
+ 9 => MediaType::Dcts,
+ 10 => MediaType::Tsx,
+ 11 => MediaType::Json,
+ 12 => MediaType::Wasm,
+ 13 => MediaType::Css,
+ 14 => MediaType::SourceMap,
+ 15 => MediaType::Unknown,
+ value => {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::InvalidData,
+ format!("Unknown media type value: {value}"),
+ ))
+ }
+ };
+ Ok((input, value))
+ }
+}
+
+/// Data stored keyed by specifier.
+pub struct SpecifierDataStore {
+ data: IndexMap,
+}
+
+impl Default for SpecifierDataStore {
+ fn default() -> Self {
+ Self {
+ data: IndexMap::new(),
+ }
+ }
+}
+
+impl SpecifierDataStore {
pub fn with_capacity(capacity: usize) -> Self {
Self {
data: IndexMap::with_capacity(capacity),
}
}
- pub fn iter(&self) -> impl Iterator- {
- self.data.iter().map(|(k, v)| (k.as_ref(), v.as_ref()))
+ pub fn iter(&self) -> impl Iterator
- {
+ self.data.iter().map(|(k, v)| (*k, v))
}
#[allow(clippy::len_without_is_empty)]
@@ -93,15 +302,88 @@ impl SourceMapStore {
self.data.len()
}
- pub fn add(
- &mut self,
- specifier: Cow<'static, str>,
- source_map: Cow<'static, [u8]>,
- ) {
- self.data.insert(specifier, source_map);
+ pub fn contains(&self, specifier: SpecifierId) -> bool {
+ self.data.contains_key(&specifier)
}
- pub fn get(&self, specifier: &str) -> Option<&[u8]> {
- self.data.get(specifier).map(|v| v.as_ref())
+ pub fn add(&mut self, specifier: SpecifierId, value: TData) {
+ self.data.insert(specifier, value);
+ }
+
+ pub fn get(&self, specifier: SpecifierId) -> Option<&TData> {
+ self.data.get(&specifier)
+ }
+}
+
+impl<'a, TData> SpecifierDataStore
+where
+ TData: DenoRtSerializable<'a> + 'a,
+{
+ pub fn serialize(
+ &'a self,
+ builder: &mut capacity_builder::BytesBuilder<'a, Vec>,
+ ) {
+ builder.append_le(self.len() as u32);
+ for (specifier, value) in self.iter() {
+ builder.append(specifier);
+ value.serialize(builder);
+ }
+ }
+}
+
+impl<'a, TData> DenoRtDeserializable<'a> for SpecifierDataStore
+where
+ TData: DenoRtDeserializable<'a>,
+{
+ fn deserialize(input: &'a [u8]) -> std::io::Result<(&'a [u8], Self)> {
+ let (input, len) = read_u32_as_usize(input)?;
+ let mut data = IndexMap::with_capacity(len);
+ let mut input = input;
+ for _ in 0..len {
+ let (new_input, specifier) = SpecifierId::deserialize(input)?;
+ let (new_input, value) = TData::deserialize(new_input)?;
+ data.insert(specifier, value);
+ input = new_input;
+ }
+ Ok((input, Self { data }))
+ }
+}
+
+fn read_bytes_with_u32_len(input: &[u8]) -> std::io::Result<(&[u8], &[u8])> {
+ let (input, len) = read_u32_as_usize(input)?;
+ let (input, data) = read_bytes(input, len)?;
+ Ok((input, data))
+}
+
+fn read_u32_as_usize(input: &[u8]) -> std::io::Result<(&[u8], usize)> {
+ read_u32(input).map(|(input, len)| (input, len as usize))
+}
+
+fn read_u32(input: &[u8]) -> std::io::Result<(&[u8], u32)> {
+ let (input, len_bytes) = read_bytes(input, 4)?;
+ let len = u32::from_le_bytes(len_bytes.try_into().unwrap());
+ Ok((input, len))
+}
+
+fn read_u8(input: &[u8]) -> std::io::Result<(&[u8], u8)> {
+ check_has_len(input, 1)?;
+ Ok((&input[1..], input[0]))
+}
+
+fn read_bytes(input: &[u8], len: usize) -> std::io::Result<(&[u8], &[u8])> {
+ check_has_len(input, len)?;
+ let (len_bytes, input) = input.split_at(len);
+ Ok((input, len_bytes))
+}
+
+#[inline(always)]
+fn check_has_len(input: &[u8], len: usize) -> std::io::Result<()> {
+ if input.len() < len {
+ Err(std::io::Error::new(
+ std::io::ErrorKind::InvalidData,
+ "Unexpected end of data",
+ ))
+ } else {
+ Ok(())
}
}
diff --git a/cli/lib/standalone/virtual_fs.rs b/cli/lib/standalone/virtual_fs.rs
index 5e11491349..124c2a0002 100644
--- a/cli/lib/standalone/virtual_fs.rs
+++ b/cli/lib/standalone/virtual_fs.rs
@@ -1,7 +1,10 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use std::cmp::Ordering;
+use std::collections::hash_map::Entry;
use std::collections::HashMap;
+use std::collections::VecDeque;
+use std::fmt;
use std::path::Path;
use std::path::PathBuf;
@@ -12,17 +15,13 @@ use deno_runtime::deno_core::anyhow::bail;
use deno_runtime::deno_core::anyhow::Context;
use deno_runtime::deno_core::error::AnyError;
use indexmap::IndexSet;
+use serde::de;
+use serde::de::SeqAccess;
+use serde::de::Visitor;
use serde::Deserialize;
+use serde::Deserializer;
use serde::Serialize;
-
-#[derive(Debug, Copy, Clone)]
-pub enum VfsFileSubDataKind {
- /// Raw bytes of the file.
- Raw,
- /// Bytes to use for module loading. For example, for TypeScript
- /// files this will be the transpiled JavaScript source.
- ModuleGraph,
-}
+use serde::Serializer;
#[derive(Debug, PartialEq, Eq)]
pub enum WindowsSystemRootablePath {
@@ -32,6 +31,14 @@ pub enum WindowsSystemRootablePath {
}
impl WindowsSystemRootablePath {
+ pub fn root_for_current_os() -> Self {
+ if cfg!(windows) {
+ WindowsSystemRootablePath::WindowSystemRoot
+ } else {
+ WindowsSystemRootablePath::Path(PathBuf::from("/"))
+ }
+ }
+
pub fn join(&self, name_component: &str) -> PathBuf {
// this method doesn't handle multiple components
debug_assert!(
@@ -118,6 +125,10 @@ impl VirtualDirectoryEntries {
self.0.get_mut(index)
}
+ pub fn get_by_index(&self, index: usize) -> Option<&VfsEntry> {
+ self.0.get(index)
+ }
+
pub fn binary_search(
&self,
name: &str,
@@ -188,27 +199,67 @@ pub struct VirtualDirectory {
pub entries: VirtualDirectoryEntries,
}
-#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
+#[derive(Debug, Clone, Copy)]
pub struct OffsetWithLength {
- #[serde(rename = "o")]
pub offset: u64,
- #[serde(rename = "l")]
pub len: u64,
}
+// serialize as an array in order to save space
+impl Serialize for OffsetWithLength {
+ fn serialize
(&self, serializer: S) -> Result
+ where
+ S: Serializer,
+ {
+ let array = [self.offset, self.len];
+ array.serialize(serializer)
+ }
+}
+
+impl<'de> Deserialize<'de> for OffsetWithLength {
+ fn deserialize(deserializer: D) -> Result
+ where
+ D: Deserializer<'de>,
+ {
+ struct OffsetWithLengthVisitor;
+
+ impl<'de> Visitor<'de> for OffsetWithLengthVisitor {
+ type Value = OffsetWithLength;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("an array with two elements: [offset, len]")
+ }
+
+ fn visit_seq(self, mut seq: A) -> Result
+ where
+ A: SeqAccess<'de>,
+ {
+ let offset = seq
+ .next_element()?
+ .ok_or_else(|| de::Error::invalid_length(0, &self))?;
+ let len = seq
+ .next_element()?
+ .ok_or_else(|| de::Error::invalid_length(1, &self))?;
+ Ok(OffsetWithLength { offset, len })
+ }
+ }
+
+ deserializer.deserialize_seq(OffsetWithLengthVisitor)
+ }
+}
+
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VirtualFile {
#[serde(rename = "n")]
pub name: String,
#[serde(rename = "o")]
pub offset: OffsetWithLength,
- /// Offset file to use for module loading when it differs from the
- /// raw file. Often this will be the same offset as above for data
- /// such as JavaScript files, but for TypeScript files the `offset`
- /// will be the original raw bytes when included as an asset and this
- /// offset will be to the transpiled JavaScript source.
- #[serde(rename = "m")]
- pub module_graph_offset: OffsetWithLength,
+ #[serde(rename = "m", skip_serializing_if = "Option::is_none")]
+ pub transpiled_offset: Option,
+ #[serde(rename = "c", skip_serializing_if = "Option::is_none")]
+ pub cjs_export_analysis_offset: Option,
+ #[serde(rename = "s", skip_serializing_if = "Option::is_none")]
+ pub source_map_offset: Option,
}
#[derive(Debug, Serialize, Deserialize)]
@@ -314,12 +365,82 @@ pub struct BuiltVfs {
pub files: Vec>,
}
+#[derive(Debug, Default)]
+struct FilesData {
+ files: Vec>,
+ current_offset: u64,
+ file_offsets: HashMap<(String, usize), OffsetWithLength>,
+}
+
+impl FilesData {
+ pub fn file_bytes(&self, offset: OffsetWithLength) -> Option<&[u8]> {
+ if offset.len == 0 {
+ return Some(&[]);
+ }
+
+ // the debug assertions in this method should never happen
+ // because it would indicate providing an offset not in the vfs
+ let mut count: u64 = 0;
+ for file in &self.files {
+ // clippy wanted a match
+ match count.cmp(&offset.offset) {
+ Ordering::Equal => {
+ debug_assert_eq!(offset.len, file.len() as u64);
+ if offset.len == file.len() as u64 {
+ return Some(file);
+ } else {
+ return None;
+ }
+ }
+ Ordering::Less => {
+ count += file.len() as u64;
+ }
+ Ordering::Greater => {
+ debug_assert!(false);
+ return None;
+ }
+ }
+ }
+ debug_assert!(false);
+ None
+ }
+
+ pub fn add_data(&mut self, data: Vec) -> OffsetWithLength {
+ if data.is_empty() {
+ return OffsetWithLength { offset: 0, len: 0 };
+ }
+ let checksum = crate::util::checksum::gen(&[&data]);
+ match self.file_offsets.entry((checksum, data.len())) {
+ Entry::Occupied(occupied_entry) => {
+ let offset_and_len = *occupied_entry.get();
+ debug_assert_eq!(data.len() as u64, offset_and_len.len);
+ offset_and_len
+ }
+ Entry::Vacant(vacant_entry) => {
+ let offset_and_len = OffsetWithLength {
+ offset: self.current_offset,
+ len: data.len() as u64,
+ };
+ vacant_entry.insert(offset_and_len);
+ self.current_offset += offset_and_len.len;
+ self.files.push(data);
+ offset_and_len
+ }
+ }
+ }
+}
+
+pub struct AddFileDataOptions {
+ pub data: Vec,
+ pub maybe_transpiled: Option>,
+ pub maybe_source_map: Option>,
+ pub maybe_cjs_export_analysis: Option>,
+}
+
#[derive(Debug)]
pub struct VfsBuilder {
executable_root: VirtualDirectory,
- files: Vec>,
- current_offset: u64,
- file_offsets: HashMap,
+ files: FilesData,
/// The minimum root directory that should be included in the VFS.
min_root_dir: Option,
case_sensitivity: FileSystemCaseSensitivity,
@@ -338,9 +459,7 @@ impl VfsBuilder {
name: "/".to_string(),
entries: Default::default(),
},
- files: Vec::new(),
- current_offset: 0,
- file_offsets: Default::default(),
+ files: Default::default(),
min_root_dir: Default::default(),
// This is not exactly correct because file systems on these OSes
// may be case-sensitive or not based on the directory, but this
@@ -360,7 +479,11 @@ impl VfsBuilder {
}
pub fn files_len(&self) -> usize {
- self.files.len()
+ self.files.files.len()
+ }
+
+ pub fn file_bytes(&self, offset: OffsetWithLength) -> Option<&[u8]> {
+ self.files.file_bytes(offset)
}
/// Add a directory that might be the minimum root directory
@@ -387,13 +510,8 @@ impl VfsBuilder {
common_components.push(a);
}
if common_components.is_empty() {
- if cfg!(windows) {
- self.min_root_dir =
- Some(WindowsSystemRootablePath::WindowSystemRoot);
- } else {
- self.min_root_dir =
- Some(WindowsSystemRootablePath::Path(PathBuf::from("/")));
- }
+ self.min_root_dir =
+ Some(WindowsSystemRootablePath::root_for_current_os());
} else {
self.min_root_dir = Some(WindowsSystemRootablePath::Path(
common_components.iter().collect(),
@@ -513,7 +631,7 @@ impl VfsBuilder {
VfsEntry::Dir(dir) => {
current_dir = dir;
}
- _ => unreachable!(),
+ _ => unreachable!("{}", path.display()),
};
}
@@ -525,7 +643,15 @@ impl VfsBuilder {
#[allow(clippy::disallowed_methods)]
let file_bytes = std::fs::read(path)
.with_context(|| format!("Reading {}", path.display()))?;
- self.add_file_with_data(path, file_bytes, VfsFileSubDataKind::Raw)
+ self.add_file_with_data(
+ path,
+ AddFileDataOptions {
+ data: file_bytes,
+ maybe_cjs_export_analysis: None,
+ maybe_transpiled: None,
+ maybe_source_map: None,
+ },
+ )
}
fn add_file_at_path_not_symlink(
@@ -536,14 +662,13 @@ impl VfsBuilder {
#[allow(clippy::disallowed_methods)]
let file_bytes = std::fs::read(path)
.with_context(|| format!("Reading {}", path.display()))?;
- self.add_file_with_data_raw(path, file_bytes, VfsFileSubDataKind::Raw)
+ self.add_file_with_data_raw(path, file_bytes)
}
pub fn add_file_with_data(
&mut self,
path: &Path,
- data: Vec,
- sub_data_kind: VfsFileSubDataKind,
+ options: AddFileDataOptions,
) -> Result<(), AnyError> {
// ok, fs implementation
#[allow(clippy::disallowed_methods)]
@@ -552,9 +677,9 @@ impl VfsBuilder {
})?;
if metadata.is_symlink() {
let target = self.add_symlink(path)?.into_path_buf();
- self.add_file_with_data_raw(&target, data, sub_data_kind)
+ self.add_file_with_data_raw_options(&target, options)
} else {
- self.add_file_with_data_raw(path, data, sub_data_kind)
+ self.add_file_with_data_raw_options(path, options)
}
}
@@ -562,25 +687,39 @@ impl VfsBuilder {
&mut self,
path: &Path,
data: Vec,
- sub_data_kind: VfsFileSubDataKind,
+ ) -> Result<(), AnyError> {
+ self.add_file_with_data_raw_options(
+ path,
+ AddFileDataOptions {
+ data,
+ maybe_transpiled: None,
+ maybe_cjs_export_analysis: None,
+ maybe_source_map: None,
+ },
+ )
+ }
+
+ fn add_file_with_data_raw_options(
+ &mut self,
+ path: &Path,
+ options: AddFileDataOptions,
) -> Result<(), AnyError> {
log::debug!("Adding file '{}'", path.display());
- let checksum = crate::util::checksum::gen(&[&data]);
let case_sensitivity = self.case_sensitivity;
- let offset = if let Some(offset) = self.file_offsets.get(&checksum) {
- // duplicate file, reuse an old offset
- *offset
- } else {
- self.file_offsets.insert(checksum, self.current_offset);
- self.current_offset
- };
+ let offset_and_len = self.files.add_data(options.data);
+ let transpiled_offset = options
+ .maybe_transpiled
+ .map(|data| self.files.add_data(data));
+ let source_map_offset = options
+ .maybe_source_map
+ .map(|data| self.files.add_data(data));
+ let cjs_export_analysis_offset = options
+ .maybe_cjs_export_analysis
+ .map(|data| self.files.add_data(data));
let dir = self.add_dir_raw(path.parent().unwrap());
let name = path.file_name().unwrap().to_string_lossy();
- let offset_and_len = OffsetWithLength {
- offset,
- len: data.len() as u64,
- };
+
dir.entries.insert_or_modify(
&name,
case_sensitivity,
@@ -588,28 +727,30 @@ impl VfsBuilder {
VfsEntry::File(VirtualFile {
name: name.to_string(),
offset: offset_and_len,
- module_graph_offset: offset_and_len,
+ transpiled_offset,
+ cjs_export_analysis_offset,
+ source_map_offset,
})
},
|entry| match entry {
- VfsEntry::File(virtual_file) => match sub_data_kind {
- VfsFileSubDataKind::Raw => {
- virtual_file.offset = offset_and_len;
+ VfsEntry::File(virtual_file) => {
+ virtual_file.offset = offset_and_len;
+ // doesn't overwrite to None
+ if transpiled_offset.is_some() {
+ virtual_file.transpiled_offset = transpiled_offset;
}
- VfsFileSubDataKind::ModuleGraph => {
- virtual_file.module_graph_offset = offset_and_len;
+ if source_map_offset.is_some() {
+ virtual_file.source_map_offset = source_map_offset;
}
- },
+ if cjs_export_analysis_offset.is_some() {
+ virtual_file.cjs_export_analysis_offset =
+ cjs_export_analysis_offset;
+ }
+ }
VfsEntry::Dir(_) | VfsEntry::Symlink(_) => unreachable!(),
},
);
- // new file, update the list of files
- if self.current_offset == offset {
- self.files.push(data);
- self.current_offset += offset_and_len.len;
- }
-
Ok(())
}
@@ -689,6 +830,53 @@ impl VfsBuilder {
}
}
+ /// Adds the CJS export analysis to the provided file.
+ ///
+ /// Warning: This will panic if the file wasn't properly
+ /// setup before calling this.
+ pub fn add_cjs_export_analysis(&mut self, path: &Path, data: Vec) {
+ self.add_data_for_file_or_panic(path, data, |file, offset_with_length| {
+ file.cjs_export_analysis_offset = Some(offset_with_length);
+ })
+ }
+
+ fn add_data_for_file_or_panic(
+ &mut self,
+ path: &Path,
+ data: Vec,
+ update_file: impl FnOnce(&mut VirtualFile, OffsetWithLength),
+ ) {
+ let offset_with_length = self.files.add_data(data);
+ let case_sensitivity = self.case_sensitivity;
+ let dir = self.get_dir_mut(path.parent().unwrap()).unwrap();
+ let name = path.file_name().unwrap().to_string_lossy();
+ let file = dir
+ .entries
+ .get_mut_by_name(&name, case_sensitivity)
+ .unwrap();
+ match file {
+ VfsEntry::File(virtual_file) => {
+ update_file(virtual_file, offset_with_length);
+ }
+ VfsEntry::Dir(_) | VfsEntry::Symlink(_) => {
+ unreachable!()
+ }
+ }
+ }
+
+ /// Iterates through all the files in the virtual file system.
+ pub fn iter_files(
+ &self,
+ ) -> impl Iterator- + '_ {
+ FileIterator {
+ pending_dirs: VecDeque::from([(
+ WindowsSystemRootablePath::root_for_current_os(),
+ &self.executable_root,
+ )]),
+ current_dir_index: 0,
+ }
+ }
+
pub fn build(self) -> BuiltVfs {
fn strip_prefix_from_symlinks(
dir: &mut VirtualDirectory,
@@ -714,11 +902,7 @@ impl VfsBuilder {
}
let mut current_dir = self.executable_root;
- let mut current_path = if cfg!(windows) {
- WindowsSystemRootablePath::WindowSystemRoot
- } else {
- WindowsSystemRootablePath::Path(PathBuf::from("/"))
- };
+ let mut current_path = WindowsSystemRootablePath::root_for_current_os();
loop {
if current_dir.entries.len() != 1 {
break;
@@ -754,11 +938,51 @@ impl VfsBuilder {
root_path: current_path,
case_sensitivity: self.case_sensitivity,
entries: current_dir.entries,
- files: self.files,
+ files: self.files.files,
}
}
}
+struct FileIterator<'a> {
+ pending_dirs: VecDeque<(WindowsSystemRootablePath, &'a VirtualDirectory)>,
+ current_dir_index: usize,
+}
+
+impl<'a> Iterator for FileIterator<'a> {
+ type Item = (PathBuf, &'a VirtualFile);
+
+ fn next(&mut self) -> Option {
+ while !self.pending_dirs.is_empty() {
+ let (dir_path, current_dir) = self.pending_dirs.front()?;
+ if let Some(entry) =
+ current_dir.entries.get_by_index(self.current_dir_index)
+ {
+ self.current_dir_index += 1;
+ match entry {
+ VfsEntry::Dir(virtual_directory) => {
+ self.pending_dirs.push_back((
+ WindowsSystemRootablePath::Path(
+ dir_path.join(&virtual_directory.name),
+ ),
+ virtual_directory,
+ ));
+ }
+ VfsEntry::File(virtual_file) => {
+ return Some((dir_path.join(&virtual_file.name), virtual_file));
+ }
+ VfsEntry::Symlink(_) => {
+ // ignore
+ }
+ }
+ } else {
+ self.pending_dirs.pop_front();
+ self.current_dir_index = 0;
+ }
+ }
+ None
+ }
+}
+
#[derive(Debug)]
pub enum SymlinkTarget {
File(PathBuf),
diff --git a/cli/lib/version.rs b/cli/lib/version.rs
index bcb7d2c1c6..88a25dffeb 100644
--- a/cli/lib/version.rs
+++ b/cli/lib/version.rs
@@ -15,7 +15,7 @@ pub fn otel_runtime_config() -> OtelRuntimeConfig {
const GIT_COMMIT_HASH: &str = env!("GIT_COMMIT_HASH");
const TYPESCRIPT: &str = "5.6.2";
-const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
+const DENO_VERSION: &str = env!("DENO_VERSION");
// TODO(bartlomieju): ideally we could remove this const.
const IS_CANARY: bool = option_env!("DENO_CANARY").is_some();
// TODO(bartlomieju): this is temporary, to allow Homebrew to cut RC releases as well
@@ -38,13 +38,9 @@ pub static DENO_VERSION_INFO: std::sync::LazyLock =
DenoVersionInfo {
deno: if release_channel == ReleaseChannel::Canary {
- concat!(
- env!("CARGO_PKG_VERSION"),
- "+",
- env!("GIT_COMMIT_HASH_SHORT")
- )
+ concat!(env!("DENO_VERSION"), "+", env!("GIT_COMMIT_HASH_SHORT"))
} else {
- env!("CARGO_PKG_VERSION")
+ env!("DENO_VERSION")
},
release_channel,
@@ -55,12 +51,12 @@ pub static DENO_VERSION_INFO: std::sync::LazyLock =
user_agent: if release_channel == ReleaseChannel::Canary {
concat!(
"Deno/",
- env!("CARGO_PKG_VERSION"),
+ env!("DENO_VERSION"),
"+",
env!("GIT_COMMIT_HASH_SHORT")
)
} else {
- concat!("Deno/", env!("CARGO_PKG_VERSION"))
+ concat!("Deno/", env!("DENO_VERSION"))
},
typescript: TYPESCRIPT,
@@ -92,7 +88,7 @@ impl DenoVersionInfo {
if self.release_channel == ReleaseChannel::Canary {
self.git_hash
} else {
- CARGO_PKG_VERSION
+ DENO_VERSION
}
}
}
diff --git a/cli/lib/version.txt b/cli/lib/version.txt
new file mode 100644
index 0000000000..b6da51221f
--- /dev/null
+++ b/cli/lib/version.txt
@@ -0,0 +1 @@
+2.1.6
\ No newline at end of file
diff --git a/cli/npm/mod.rs b/cli/npm/mod.rs
index afb3d345d7..388b007f81 100644
--- a/cli/npm/mod.rs
+++ b/cli/npm/mod.rs
@@ -9,6 +9,7 @@ use dashmap::DashMap;
use deno_core::serde_json;
use deno_core::url::Url;
use deno_error::JsErrorBox;
+use deno_lib::version::DENO_VERSION_INFO;
use deno_npm::npm_rc::ResolvedNpmRc;
use deno_npm::registry::NpmPackageInfo;
use deno_resolver::npm::ByonmNpmResolverCreateOptions;
@@ -182,8 +183,8 @@ pub const NPM_CONFIG_USER_AGENT_ENV_VAR: &str = "npm_config_user_agent";
pub fn get_npm_config_user_agent() -> String {
format!(
"deno/{} npm/? deno/{} {} {}",
- env!("CARGO_PKG_VERSION"),
- env!("CARGO_PKG_VERSION"),
+ DENO_VERSION_INFO.deno,
+ DENO_VERSION_INFO.deno,
std::env::consts::OS,
std::env::consts::ARCH
)
diff --git a/cli/rt/Cargo.toml b/cli/rt/Cargo.toml
index 63eaba29c4..c10a1640c4 100644
--- a/cli/rt/Cargo.toml
+++ b/cli/rt/Cargo.toml
@@ -26,16 +26,12 @@ deno_runtime = { workspace = true, features = ["include_js_files_for_snapshottin
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] }
[dependencies]
-deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] }
-# todo(dsherret): remove deno_cache_dir
deno_cache_dir.workspace = true
deno_config.workspace = true
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] }
deno_error.workspace = true
-# todo(dsherret): remove deno_graph
-deno_graph = { version = "=0.87.0" }
deno_lib.workspace = true
-deno_media_type.workspace = true
+deno_media_type = { workspace = true, features = ["data_url", "decoding"] }
deno_npm.workspace = true
deno_package_json.workspace = true
deno_path_util.workspace = true
@@ -48,11 +44,14 @@ libsui = "0.5.0"
node_resolver.workspace = true
async-trait.workspace = true
+bincode = "=1.3.3"
import_map = { version = "=0.21.0", features = ["ext"] }
indexmap.workspace = true
log = { workspace = true, features = ["serde"] }
serde.workspace = true
+serde_json.workspace = true
sys_traits = { workspace = true, features = ["getrandom", "filetime", "libc", "real", "strip_unc", "winapi"] }
+thiserror.workspace = true
tokio.workspace = true
tokio-util.workspace = true
twox-hash.workspace = true
diff --git a/cli/rt/binary.rs b/cli/rt/binary.rs
index 0c77892296..19aad257ca 100644
--- a/cli/rt/binary.rs
+++ b/cli/rt/binary.rs
@@ -16,11 +16,14 @@ use deno_core::url::Url;
use deno_core::FastString;
use deno_core::ModuleSourceCode;
use deno_core::ModuleType;
+use deno_error::JsError;
use deno_error::JsErrorBox;
+use deno_lib::standalone::binary::DenoRtDeserializable;
use deno_lib::standalone::binary::Metadata;
-use deno_lib::standalone::binary::SourceMapStore;
+use deno_lib::standalone::binary::RemoteModuleEntry;
+use deno_lib::standalone::binary::SpecifierDataStore;
+use deno_lib::standalone::binary::SpecifierId;
use deno_lib::standalone::binary::MAGIC_BYTES;
-use deno_lib::standalone::virtual_fs::VfsFileSubDataKind;
use deno_lib::standalone::virtual_fs::VirtualDirectory;
use deno_lib::standalone::virtual_fs::VirtualDirectoryEntries;
use deno_media_type::MediaType;
@@ -33,16 +36,17 @@ use deno_runtime::deno_fs::RealFs;
use deno_runtime::deno_io::fs::FsError;
use deno_semver::package::PackageReq;
use deno_semver::StackString;
+use indexmap::IndexMap;
+use thiserror::Error;
use crate::file_system::FileBackedVfs;
use crate::file_system::VfsRoot;
pub struct StandaloneData {
pub metadata: Metadata,
- pub modules: StandaloneModules,
+ pub modules: Arc,
pub npm_snapshot: Option,
pub root_path: PathBuf,
- pub source_maps: SourceMapStore,
pub vfs: Arc,
}
@@ -58,18 +62,6 @@ pub fn extract_standalone(
return Ok(None);
};
- let DeserializedDataSection {
- mut metadata,
- npm_snapshot,
- remote_modules,
- source_maps,
- vfs_root_entries,
- vfs_files_data,
- } = match deserialize_binary_data_section(data)? {
- Some(data_section) => data_section,
- None => return Ok(None),
- };
-
let root_path = {
let maybe_current_exe = std::env::current_exe().ok();
let current_exe_name = maybe_current_exe
@@ -80,6 +72,19 @@ pub fn extract_standalone(
.unwrap_or_else(|| Cow::Borrowed("binary"));
std::env::temp_dir().join(format!("deno-compile-{}", current_exe_name))
};
+ let root_url = deno_path_util::url_from_directory_path(&root_path)?;
+
+ let DeserializedDataSection {
+ mut metadata,
+ npm_snapshot,
+ modules_store: remote_modules,
+ vfs_root_entries,
+ vfs_files_data,
+ } = match deserialize_binary_data_section(&root_url, data)? {
+ Some(data_section) => data_section,
+ None => return Ok(None),
+ };
+
let cli_args = cli_args.into_owned();
metadata.argv.reserve(cli_args.len() - 1);
for arg in cli_args.into_iter().skip(1) {
@@ -103,13 +108,12 @@ pub fn extract_standalone(
};
Ok(Some(StandaloneData {
metadata,
- modules: StandaloneModules {
- remote_modules,
+ modules: Arc::new(StandaloneModules {
+ modules: remote_modules,
vfs: vfs.clone(),
- },
+ }),
npm_snapshot,
root_path,
- source_maps,
vfs,
}))
}
@@ -117,13 +121,13 @@ pub fn extract_standalone(
pub struct DeserializedDataSection {
pub metadata: Metadata,
pub npm_snapshot: Option,
- pub remote_modules: RemoteModulesStore,
- pub source_maps: SourceMapStore,
+ pub modules_store: RemoteModulesStore,
pub vfs_root_entries: VirtualDirectoryEntries,
pub vfs_files_data: &'static [u8],
}
pub fn deserialize_binary_data_section(
+ root_dir_url: &Url,
data: &'static [u8],
) -> Result