summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--cli/args/flags.rs106
-rw-r--r--cli/args/mod.rs4
-rw-r--r--cli/cache/caches.rs15
-rw-r--r--cli/cache/code_cache.rs231
-rw-r--r--cli/cache/deno_dir.rs6
-rw-r--r--cli/cache/mod.rs2
-rw-r--r--cli/cache/module_info.rs24
-rw-r--r--cli/factory.rs22
-rw-r--r--cli/graph_util.rs2
-rw-r--r--cli/lsp/analysis.rs2
-rw-r--r--cli/lsp/cache.rs2
-rw-r--r--cli/lsp/completions.rs2
-rw-r--r--cli/lsp/config.rs2
-rw-r--r--cli/lsp/documents.rs2
-rw-r--r--cli/lsp/language_server.rs2
-rw-r--r--cli/lsp/tsc.rs2
-rw-r--r--cli/module_loader.rs86
-rw-r--r--cli/npm/byonm.rs2
-rw-r--r--cli/resolver.rs2
-rw-r--r--cli/standalone/mod.rs2
-rw-r--r--cli/tools/vendor/mod.rs2
-rw-r--r--cli/util/path.rs63
-rw-r--r--cli/worker.rs5
-rw-r--r--runtime/Cargo.toml1
-rw-r--r--runtime/code_cache.rs31
-rw-r--r--runtime/fs_util.rs74
-rw-r--r--runtime/lib.rs1
-rw-r--r--runtime/worker.rs65
-rw-r--r--tests/integration/run_tests.rs202
-rw-r--r--tests/testdata/run/rejection_handled.ts2
31 files changed, 889 insertions, 76 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 6477eb63e..f5760f3dd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1836,6 +1836,7 @@ dependencies = [
"notify",
"ntapi",
"once_cell",
+ "percent-encoding",
"regex",
"ring",
"rustyline",
diff --git a/cli/args/flags.rs b/cli/args/flags.rs
index 9384dacf8..d57f78aff 100644
--- a/cli/args/flags.rs
+++ b/cli/args/flags.rs
@@ -517,6 +517,7 @@ pub struct Flags {
pub unstable_config: UnstableConfig,
pub unsafely_ignore_certificate_errors: Option<Vec<String>>,
pub v8_flags: Vec<String>,
+ pub code_cache_enabled: bool,
}
fn join_paths(allowlist: &[String], d: &str) -> String {
@@ -2236,6 +2237,7 @@ fn run_subcommand() -> Command {
.trailing_var_arg(true),
)
.arg(env_file_arg())
+ .arg(no_code_cache_arg())
.about("Run a JavaScript or TypeScript program")
.long_about(
"Run a JavaScript or TypeScript program
@@ -3222,6 +3224,13 @@ fn no_clear_screen_arg() -> Arg {
.help("Do not clear terminal screen when under watch mode")
}
+fn no_code_cache_arg() -> Arg {
+ Arg::new("no-code-cache")
+ .long("no-code-cache")
+ .help("Disable V8 code cache feature")
+ .action(ArgAction::SetTrue)
+}
+
fn watch_exclude_arg() -> Arg {
Arg::new("watch-exclude")
.long("watch-exclude")
@@ -3829,6 +3838,8 @@ fn run_parse(
) -> clap::error::Result<()> {
runtime_args_parse(flags, matches, true, true);
+ flags.code_cache_enabled = !matches.get_flag("no-code-cache");
+
let mut script_arg =
matches.remove_many::<String>("script_arg").ok_or_else(|| {
let mut app = app;
@@ -4469,6 +4480,7 @@ mod tests {
..Default::default()
},
log_level: Some(Level::Error),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4540,6 +4552,7 @@ mod tests {
"script.ts".to_string()
)),
reload: true,
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4561,6 +4574,7 @@ mod tests {
exclude: vec![],
}),
}),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4585,6 +4599,7 @@ mod tests {
exclude: vec![],
}),
}),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4609,6 +4624,7 @@ mod tests {
exclude: vec![],
}),
}),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4633,6 +4649,7 @@ mod tests {
exclude: vec![],
}),
}),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4659,6 +4676,7 @@ mod tests {
exclude: vec![],
}),
}),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4687,6 +4705,7 @@ mod tests {
exclude: vec![],
}),
}),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4715,6 +4734,7 @@ mod tests {
exclude: vec![String::from("foo")],
}),
}),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4739,6 +4759,7 @@ mod tests {
exclude: vec![String::from("bar")],
}),
}),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4764,6 +4785,7 @@ mod tests {
exclude: vec![String::from("foo"), String::from("bar")],
}),
}),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4789,6 +4811,7 @@ mod tests {
exclude: vec![String::from("baz"), String::from("qux"),],
}),
}),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4806,6 +4829,7 @@ mod tests {
"script.ts".to_string()
)),
allow_write: Some(vec![]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4819,6 +4843,7 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Run(RunFlags::new_default("_".to_string())),
v8_flags: svec!["--help"],
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4836,6 +4861,7 @@ mod tests {
"script.ts".to_string()
)),
v8_flags: svec!["--expose-gc", "--gc-stats=1"],
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4889,6 +4915,7 @@ mod tests {
)),
argv: svec!["--title", "X"],
allow_net: Some(vec![]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4912,6 +4939,7 @@ mod tests {
allow_write: Some(vec![]),
allow_ffi: Some(vec![]),
allow_hrtime: true,
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4927,6 +4955,7 @@ mod tests {
"gist.ts".to_string()
)),
allow_read: Some(vec![]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4942,6 +4971,7 @@ mod tests {
"gist.ts".to_string()
)),
deny_read: Some(vec![]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4957,6 +4987,7 @@ mod tests {
"gist.ts".to_string(),
)),
allow_hrtime: true,
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4972,6 +5003,7 @@ mod tests {
"gist.ts".to_string(),
)),
deny_hrtime: true,
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -4999,6 +5031,7 @@ mod tests {
)),
argv: svec!["--", "-D", "--allow-net"],
allow_write: Some(vec![]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -5713,6 +5746,7 @@ mod tests {
"script.ts".to_string(),
)),
config_flag: ConfigFlag::Path("tsconfig.json".to_owned()),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6007,6 +6041,7 @@ mod tests {
subcommand: DenoSubcommand::Run(RunFlags::new_default(
"script.ts".to_string(),
)),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6031,6 +6066,7 @@ mod tests {
subcommand: DenoSubcommand::Run(RunFlags::new_default(
"script.ts".to_string(),
)),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6055,6 +6091,7 @@ mod tests {
subcommand: DenoSubcommand::Run(RunFlags::new_default(
"script.ts".to_string(),
)),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6079,6 +6116,7 @@ mod tests {
subcommand: DenoSubcommand::Run(RunFlags::new_default(
"script.ts".to_string(),
)),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6099,6 +6137,7 @@ mod tests {
"script.ts".to_string(),
)),
allow_net: Some(svec!["127.0.0.1"]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6115,6 +6154,7 @@ mod tests {
"script.ts".to_string(),
)),
deny_net: Some(svec!["127.0.0.1"]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6131,6 +6171,7 @@ mod tests {
"script.ts".to_string(),
)),
allow_env: Some(svec!["HOME"]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6147,6 +6188,7 @@ mod tests {
"script.ts".to_string(),
)),
deny_env: Some(svec!["HOME"]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6167,6 +6209,7 @@ mod tests {
"script.ts".to_string(),
)),
allow_env: Some(svec!["HOME", "PATH"]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6183,6 +6226,7 @@ mod tests {
"script.ts".to_string(),
)),
deny_env: Some(svec!["HOME", "PATH"]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6224,6 +6268,7 @@ mod tests {
"script.ts".to_string(),
)),
allow_sys: Some(vec![]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6239,6 +6284,7 @@ mod tests {
"script.ts".to_string(),
)),
deny_sys: Some(vec![]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6255,6 +6301,7 @@ mod tests {
"script.ts".to_string(),
)),
allow_sys: Some(svec!["hostname"]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6271,6 +6318,7 @@ mod tests {
"script.ts".to_string(),
)),
deny_sys: Some(svec!["hostname"]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6291,6 +6339,7 @@ mod tests {
"script.ts".to_string(),
)),
allow_sys: Some(svec!["hostname", "osRelease"]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6311,6 +6360,7 @@ mod tests {
"script.ts".to_string(),
)),
deny_sys: Some(svec!["hostname", "osRelease"]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6618,6 +6668,7 @@ mod tests {
"script.ts".to_string(),
)),
import_map_path: Some("import_map.json".to_owned()),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6699,6 +6750,22 @@ mod tests {
"script.ts".to_string(),
)),
env_file: Some(".env".to_owned()),
+ code_cache_enabled: true,
+ ..Flags::default()
+ }
+ );
+ }
+
+ #[test]
+ fn run_no_code_cache() {
+ let r =
+ flags_from_vec(svec!["deno", "run", "--no-code-cache", "script.ts"]);
+ assert_eq!(
+ r.unwrap(),
+ Flags {
+ subcommand: DenoSubcommand::Run(RunFlags::new_default(
+ "script.ts".to_string(),
+ )),
..Flags::default()
}
);
@@ -6715,6 +6782,7 @@ mod tests {
"script.ts".to_string(),
)),
env_file: Some(".another_env".to_owned()),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6746,6 +6814,7 @@ mod tests {
)),
seed: Some(250_u64),
v8_flags: svec!["--random-seed=250"],
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6769,6 +6838,7 @@ mod tests {
)),
seed: Some(250_u64),
v8_flags: svec!["--expose-gc", "--random-seed=250"],
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6907,6 +6977,7 @@ mod tests {
"script.ts".to_string(),
)),
log_level: Some(Level::Debug),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6922,6 +6993,7 @@ mod tests {
"script.ts".to_string(),
)),
log_level: Some(Level::Error),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6955,6 +7027,7 @@ mod tests {
"script.ts".to_string(),
)),
argv: svec!["--allow-read", "--allow-net"],
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6980,6 +7053,7 @@ mod tests {
location: Some(Url::parse("https://foo/").unwrap()),
allow_read: Some(vec![]),
argv: svec!["--allow-net", "-r", "--help", "--foo", "bar"],
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -6992,6 +7066,7 @@ mod tests {
"script.ts".to_string(),
)),
argv: svec!["foo", "bar"],
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7003,6 +7078,7 @@ mod tests {
"script.ts".to_string(),
)),
argv: svec!["-"],
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7016,6 +7092,7 @@ mod tests {
"script.ts".to_string(),
)),
argv: svec!["-", "foo", "bar"],
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7031,6 +7108,7 @@ mod tests {
"script.ts".to_string(),
)),
type_check_mode: TypeCheckMode::None,
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7047,6 +7125,7 @@ mod tests {
"script.ts".to_string(),
)),
type_check_mode: TypeCheckMode::Local,
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7091,6 +7170,7 @@ mod tests {
"script.ts".to_string(),
)),
unsafely_ignore_certificate_errors: Some(vec![]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7118,6 +7198,7 @@ mod tests {
"[::1]",
"1.2.3.4"
]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7161,6 +7242,7 @@ mod tests {
"script.ts".to_string(),
)),
no_remote: true,
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7176,6 +7258,7 @@ mod tests {
"script.ts".to_string(),
)),
no_npm: true,
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7192,6 +7275,7 @@ mod tests {
"script.ts".to_string(),
)),
node_modules_dir: Some(true),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7209,6 +7293,7 @@ mod tests {
"script.ts".to_string(),
)),
node_modules_dir: Some(false),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7224,6 +7309,7 @@ mod tests {
"script.ts".to_string(),
)),
vendor: Some(true),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7236,6 +7322,7 @@ mod tests {
"script.ts".to_string(),
)),
vendor: Some(false),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7251,6 +7338,7 @@ mod tests {
"script.ts".to_string(),
)),
cached_only: true,
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7279,6 +7367,7 @@ mod tests {
"127.0.0.1:4545",
"localhost:4545"
]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7307,6 +7396,7 @@ mod tests {
"127.0.0.1:4545",
"localhost:4545"
]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7338,6 +7428,7 @@ mod tests {
"localhost:5678",
"[::1]:8080"
]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7369,6 +7460,7 @@ mod tests {
"localhost:5678",
"[::1]:8080"
]),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7391,6 +7483,7 @@ mod tests {
)),
lock_write: true,
lock: Some(String::from("lock.json")),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7403,6 +7496,7 @@ mod tests {
"script.ts".to_string(),
)),
no_lock: true,
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7422,6 +7516,7 @@ mod tests {
)),
lock_write: true,
lock: Some(String::from("./deno.lock")),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7442,6 +7537,7 @@ mod tests {
)),
lock_write: true,
lock: Some(String::from("lock.json")),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7454,6 +7550,7 @@ mod tests {
"script.ts".to_string(),
)),
lock_write: true,
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7546,6 +7643,7 @@ mod tests {
"script.ts".to_string(),
)),
ca_data: Some(CaData::File("example.crt".to_owned())),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -7566,6 +7664,7 @@ mod tests {
"script.ts".to_string(),
)),
enable_testing_features: true,
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -8244,6 +8343,7 @@ mod tests {
"foo.js".to_string(),
)),
inspect: Some("127.0.0.1:9229".parse().unwrap()),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -8259,6 +8359,7 @@ mod tests {
"foo.js".to_string(),
)),
inspect_wait: Some("127.0.0.1:9229".parse().unwrap()),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -8276,6 +8377,7 @@ mod tests {
"foo.js".to_string(),
)),
inspect_wait: Some("127.0.0.1:3567".parse().unwrap()),
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -8802,6 +8904,7 @@ mod tests {
"script.ts".to_string(),
)),
type_check_mode: TypeCheckMode::Local,
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -8814,6 +8917,7 @@ mod tests {
"script.ts".to_string(),
)),
type_check_mode: TypeCheckMode::All,
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -8826,6 +8930,7 @@ mod tests {
"script.ts".to_string(),
)),
type_check_mode: TypeCheckMode::None,
+ code_cache_enabled: true,
..Flags::default()
}
);
@@ -8850,6 +8955,7 @@ mod tests {
"script.ts".to_string(),
)),
config_flag: ConfigFlag::Disabled,
+ code_cache_enabled: true,
..Flags::default()
}
);
diff --git a/cli/args/mod.rs b/cli/args/mod.rs
index 9bc409307..dbb3e6e46 100644
--- a/cli/args/mod.rs
+++ b/cli/args/mod.rs
@@ -1653,6 +1653,10 @@ impl CliOptions {
&self.flags.v8_flags
}
+ pub fn code_cache_enabled(&self) -> bool {
+ self.flags.code_cache_enabled
+ }
+
pub fn watch_paths(&self) -> Vec<PathBuf> {
let mut full_paths = Vec::new();
if let DenoSubcommand::Run(RunFlags {
diff --git a/cli/cache/caches.rs b/cli/cache/caches.rs
index dc97f02d5..1be14b53b 100644
--- a/cli/cache/caches.rs
+++ b/cli/cache/caches.rs
@@ -8,6 +8,7 @@ use once_cell::sync::OnceCell;
use super::cache_db::CacheDB;
use super::cache_db::CacheDBConfiguration;
use super::check::TYPE_CHECK_CACHE_DB;
+use super::code_cache::CODE_CACHE_DB;
use super::deno_dir::DenoDirProvider;
use super::fast_check::FAST_CHECK_CACHE_DB;
use super::incremental::INCREMENTAL_CACHE_DB;
@@ -22,6 +23,7 @@ pub struct Caches {
fast_check_db: OnceCell<CacheDB>,
node_analysis_db: OnceCell<CacheDB>,
type_checking_cache_db: OnceCell<CacheDB>,
+ code_cache_db: OnceCell<CacheDB>,
}
impl Caches {
@@ -34,6 +36,7 @@ impl Caches {
fast_check_db: Default::default(),
node_analysis_db: Default::default(),
type_checking_cache_db: Default::default(),
+ code_cache_db: Default::default(),
}
}
@@ -124,4 +127,16 @@ impl Caches {
.map(|dir| dir.type_checking_cache_db_file_path()),
)
}
+
+ pub fn code_cache_db(&self) -> CacheDB {
+ Self::make_db(
+ &self.code_cache_db,
+ &CODE_CACHE_DB,
+ self
+ .dir_provider
+ .get_or_create()
+ .ok()
+ .map(|dir| dir.code_cache_db_file_path()),
+ )
+ }
}
diff --git a/cli/cache/code_cache.rs b/cli/cache/code_cache.rs
new file mode 100644
index 000000000..5e44c366e
--- /dev/null
+++ b/cli/cache/code_cache.rs
@@ -0,0 +1,231 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use deno_core::error::AnyError;
+use deno_runtime::code_cache;
+use deno_runtime::deno_webstorage::rusqlite::params;
+
+use super::cache_db::CacheDB;
+use super::cache_db::CacheDBConfiguration;
+use super::cache_db::CacheFailure;
+
+pub static CODE_CACHE_DB: CacheDBConfiguration = CacheDBConfiguration {
+ table_initializer: "CREATE TABLE IF NOT EXISTS codecache (
+ specifier TEXT NOT NULL,
+ type TEXT NOT NULL,
+ source_hash TEXT NOT NULL,
+ data BLOB NOT NULL,
+ PRIMARY KEY (specifier, type)
+ );",
+ on_version_change: "DELETE FROM codecache;",
+ preheat_queries: &[],
+ on_failure: CacheFailure::Blackhole,
+};
+
+#[derive(Clone)]
+pub struct CodeCache {
+ inner: CodeCacheInner,
+}
+
+impl CodeCache {
+ pub fn new(db: CacheDB) -> Self {
+ Self {
+ inner: CodeCacheInner::new(db),
+ }
+ }
+
+ fn ensure_ok<T: Default>(res: Result<T, AnyError>) -> T {
+ match res {
+ Ok(x) => x,
+ Err(err) => {
+ // TODO(mmastrac): This behavior was inherited from before the refactoring but it probably makes sense to move it into the cache
+ // at some point.
+ // should never error here, but if it ever does don't fail
+ if cfg!(debug_assertions) {
+ panic!("Error using code cache: {err:#}");
+ } else {
+ log::debug!("Error using code cache: {:#}", err);
+ }
+ T::default()
+ }
+ }
+ }
+
+ pub fn get_sync(
+ &self,
+ specifier: &str,
+ code_cache_type: code_cache::CodeCacheType,
+ source_hash: &str,
+ ) -> Option<Vec<u8>> {
+ Self::ensure_ok(self.inner.get_sync(
+ specifier,
+ code_cache_type,
+ source_hash,
+ ))
+ }
+
+ pub fn set_sync(
+ &self,
+ specifier: &str,
+ code_cache_type: code_cache::CodeCacheType,
+ source_hash: &str,
+ data: &[u8],
+ ) {
+ Self::ensure_ok(self.inner.set_sync(
+ specifier,
+ code_cache_type,
+ source_hash,
+ data,
+ ));
+ }
+}
+
+impl code_cache::CodeCache for CodeCache {
+ fn get_sync(
+ &self,
+ specifier: &str,
+ code_cache_type: code_cache::CodeCacheType,
+ source_hash: &str,
+ ) -> Option<Vec<u8>> {
+ self.get_sync(specifier, code_cache_type, source_hash)
+ }
+
+ fn set_sync(
+ &self,
+ specifier: &str,
+ code_cache_type: code_cache::CodeCacheType,
+ source_hash: &str,
+ data: &[u8],
+ ) {
+ self.set_sync(specifier, code_cache_type, source_hash, data);
+ }
+}
+
+#[derive(Clone)]
+struct CodeCacheInner {
+ conn: CacheDB,
+}
+
+impl CodeCacheInner {
+ pub fn new(conn: CacheDB) -> Self {
+ Self { conn }
+ }
+
+ pub fn get_sync(
+ &self,
+ specifier: &str,
+ code_cache_type: code_cache::CodeCacheType,
+ source_hash: &str,
+ ) -> Result<Option<Vec<u8>>, AnyError> {
+ let query = "
+ SELECT
+ data
+ FROM
+ codecache
+ WHERE
+ specifier=?1 AND type=?2 AND source_hash=?3
+ LIMIT 1";
+ let params = params![specifier, code_cache_type.as_str(), source_hash,];
+ self.conn.query_row(query, params, |row| {
+ let value: Vec<u8> = row.get(0)?;
+ Ok(value)
+ })
+ }
+
+ pub fn set_sync(
+ &self,
+ specifier: &str,
+ code_cache_type: code_cache::CodeCacheType,
+ source_hash: &str,
+ data: &[u8],
+ ) -> Result<(), AnyError> {
+ let sql = "
+ INSERT OR REPLACE INTO
+ codecache (specifier, type, source_hash, data)
+ VALUES
+ (?1, ?2, ?3, ?4)";
+ let params =
+ params![specifier, code_cache_type.as_str(), source_hash, data];
+ self.conn.execute(sql, params)?;
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ pub fn end_to_end() {
+ let conn = CacheDB::in_memory(&CODE_CACHE_DB, "1.0.0");
+ let cache = CodeCacheInner::new(conn);
+
+ assert!(cache
+ .get_sync(
+ "file:///foo/bar.js",
+ code_cache::CodeCacheType::EsModule,
+ "hash",
+ )
+ .unwrap()
+ .is_none());
+ let data_esm = vec![1, 2, 3];
+ cache
+ .set_sync(
+ "file:///foo/bar.js",
+ code_cache::CodeCacheType::EsModule,
+ "hash",
+ &data_esm,
+ )
+ .unwrap();
+ assert_eq!(
+ cache
+ .get_sync(
+ "file:///foo/bar.js",
+ code_cache::CodeCacheType::EsModule,
+ "hash",
+ )
+ .unwrap()
+ .unwrap(),
+ data_esm
+ );
+
+ assert!(cache
+ .get_sync(
+ "file:///foo/bar.js",
+ code_cache::CodeCacheType::Script,
+ "hash",
+ )
+ .unwrap()
+ .is_none());
+ let data_script = vec![4, 5, 6];
+ cache
+ .set_sync(
+ "file:///foo/bar.js",
+ code_cache::CodeCacheType::Script,
+ "hash",
+ &data_script,
+ )
+ .unwrap();
+ assert_eq!(
+ cache
+ .get_sync(
+ "file:///foo/bar.js",
+ code_cache::CodeCacheType::Script,
+ "hash",
+ )
+ .unwrap()
+ .unwrap(),
+ data_script
+ );
+ assert_eq!(
+ cache
+ .get_sync(
+ "file:///foo/bar.js",
+ code_cache::CodeCacheType::EsModule,
+ "hash",
+ )
+ .unwrap()
+ .unwrap(),
+ data_esm
+ );
+ }
+}
diff --git a/cli/cache/deno_dir.rs b/cli/cache/deno_dir.rs
index ee8c35684..b56dfbc89 100644
--- a/cli/cache/deno_dir.rs
+++ b/cli/cache/deno_dir.rs
@@ -142,6 +142,12 @@ impl DenoDir {
self.root.join("npm")
}
+ /// Path for the V8 code cache.
+ pub fn code_cache_db_file_path(&self) -> PathBuf {
+ // bump this version name to invalidate the entire cache
+ self.root.join("v8_code_cache_v1")
+ }
+
/// Path used for the REPL history file.
/// Can be overridden or disabled by setting `DENO_REPL_HISTORY` environment variable.
pub fn repl_history_file_path(&self) -> Option<PathBuf> {
diff --git a/cli/cache/mod.rs b/cli/cache/mod.rs
index 229a9cb54..a51179213 100644
--- a/cli/cache/mod.rs
+++ b/cli/cache/mod.rs
@@ -25,6 +25,7 @@ use std::time::SystemTime;
mod cache_db;
mod caches;
mod check;
+mod code_cache;
mod common;
mod deno_dir;
mod disk_cache;
@@ -37,6 +38,7 @@ mod parsed_source;
pub use caches::Caches;
pub use check::TypeCheckCache;
+pub use code_cache::CodeCache;
pub use common::FastInsecureHasher;
pub use deno_dir::DenoDir;
pub use deno_dir::DenoDirProvider;
diff --git a/cli/cache/module_info.rs b/cli/cache/module_info.rs
index 6d317b216..2e9274160 100644
--- a/cli/cache/module_info.rs
+++ b/cli/cache/module_info.rs
@@ -39,6 +39,7 @@ pub static MODULE_INFO_CACHE_DB: CacheDBConfiguration = CacheDBConfiguration {
on_failure: CacheFailure::InMemory,
};
+#[derive(Debug)]
pub struct ModuleInfoCacheSourceHash(String);
impl ModuleInfoCacheSourceHash {
@@ -55,6 +56,12 @@ impl ModuleInfoCacheSourceHash {
}
}
+impl From<ModuleInfoCacheSourceHash> for String {
+ fn from(source_hash: ModuleInfoCacheSourceHash) -> String {
+ source_hash.0
+ }
+}
+
/// A cache of `deno_graph::ModuleInfo` objects. Using this leads to a considerable
/// performance improvement because when it exists we can skip parsing a module for
/// deno_graph.
@@ -80,6 +87,23 @@ impl ModuleInfoCache {
}
}
+ pub fn get_module_source_hash(
+ &self,
+ specifier: &ModuleSpecifier,
+ media_type: MediaType,
+ ) -> Result<Option<ModuleInfoCacheSourceHash>, AnyError> {
+ let query = "SELECT source_hash FROM moduleinfocache WHERE specifier=?1 AND media_type=?2";
+ let res = self.conn.query_row(
+ query,
+ params![specifier.as_str(), serialize_media_type(media_type)],
+ |row| {
+ let source_hash: String = row.get(0)?;
+ Ok(ModuleInfoCacheSourceHash(source_hash))
+ },
+ )?;
+ Ok(res)
+ }
+
pub fn get_module_info(
&self,
specifier: &ModuleSpecifier,
diff --git a/cli/factory.rs b/cli/factory.rs
index fd33d295c..2d685ce76 100644
--- a/cli/factory.rs
+++ b/cli/factory.rs
@@ -9,6 +9,7 @@ use crate::args::PackageJsonDepsProvider;
use crate::args::StorageKeyResolver;
use crate::args::TsConfigType;
use crate::cache::Caches;
+use crate::cache::CodeCache;
use crate::cache::DenoDir;
use crate::cache::DenoDirProvider;
use crate::cache::EmitCache;
@@ -178,6 +179,7 @@ struct CliFactoryServices {
cjs_resolutions: Deferred<Arc<CjsResolutionStore>>,
cli_node_resolver: Deferred<Arc<CliNodeResolver>>,
feature_checker: Deferred<Arc<FeatureChecker>>,
+ code_cache: Deferred<Arc<CodeCache>>,
}
pub struct CliFactory {
@@ -226,6 +228,9 @@ impl CliFactory {
_ = caches.fast_check_db();
_ = caches.type_checking_cache_db();
}
+ if self.options.code_cache_enabled() {
+ _ = caches.code_cache_db();
+ }
}
_ => {}
}
@@ -534,6 +539,12 @@ impl CliFactory {
})
}
+ pub fn code_cache(&self) -> Result<&Arc<CodeCache>, AnyError> {
+ self.services.code_cache.get_or_try_init(|| {
+ Ok(Arc::new(CodeCache::new(self.caches()?.code_cache_db())))
+ })
+ }
+
pub fn parsed_source_cache(&self) -> &Arc<ParsedSourceCache> {
self
.services
@@ -790,6 +801,12 @@ impl CliFactory {
fs.clone(),
cli_node_resolver.clone(),
),
+ if self.options.code_cache_enabled() {
+ Some(self.code_cache()?.clone())
+ } else {
+ None
+ },
+ self.module_info_cache()?.clone(),
)),
self.root_cert_store_provider().clone(),
self.fs().clone(),
@@ -804,6 +821,11 @@ impl CliFactory {
// self.options.disable_deprecated_api_warning,
true,
self.options.verbose_deprecated_api_warning,
+ if self.options.code_cache_enabled() {
+ Some(self.code_cache()?.clone())
+ } else {
+ None
+ },
))
}
diff --git a/cli/graph_util.rs b/cli/graph_util.rs
index f3b69b243..7363358f1 100644
--- a/cli/graph_util.rs
+++ b/cli/graph_util.rs
@@ -18,9 +18,9 @@ use crate::tools::check;
use crate::tools::check::TypeChecker;
use crate::util::file_watcher::WatcherCommunicator;
use crate::util::fs::canonicalize_path;
-use crate::util::path::specifier_to_file_path;
use crate::util::sync::TaskQueue;
use crate::util::sync::TaskQueuePermit;
+use deno_runtime::fs_util::specifier_to_file_path;
use deno_config::WorkspaceMemberConfig;
use deno_core::anyhow::bail;
diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs
index 9da3fcad7..9ea8d710e 100644
--- a/cli/lsp/analysis.rs
+++ b/cli/lsp/analysis.rs
@@ -10,7 +10,7 @@ use crate::args::jsr_url;
use crate::npm::CliNpmResolver;
use crate::resolver::CliNodeResolver;
use crate::tools::lint::create_linter;
-use crate::util::path::specifier_to_file_path;
+use deno_runtime::fs_util::specifier_to_file_path;
use deno_ast::SourceRange;
use deno_ast::SourceRangedForSpanned;
diff --git a/cli/lsp/cache.rs b/cli/lsp/cache.rs
index e0034207d..a1048dace 100644
--- a/cli/lsp/cache.rs
+++ b/cli/lsp/cache.rs
@@ -1,7 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use crate::cache::HttpCache;
-use crate::util::path::specifier_to_file_path;
+use deno_runtime::fs_util::specifier_to_file_path;
use deno_core::parking_lot::Mutex;
use deno_core::ModuleSpecifier;
diff --git a/cli/lsp/completions.rs b/cli/lsp/completions.rs
index 164b3b8c3..a4a7c81c1 100644
--- a/cli/lsp/completions.rs
+++ b/cli/lsp/completions.rs
@@ -15,7 +15,7 @@ use super::tsc;
use crate::jsr::JsrFetchResolver;
use crate::util::path::is_importable_ext;
use crate::util::path::relative_specifier;
-use crate::util::path::specifier_to_file_path;
+use deno_runtime::fs_util::specifier_to_file_path;
use deno_ast::LineAndColumnIndex;
use deno_ast::SourceTextInfo;
diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs
index 3e5460a1d..15bd93ced 100644
--- a/cli/lsp/config.rs
+++ b/cli/lsp/config.rs
@@ -10,7 +10,6 @@ use crate::lsp::logging::lsp_warn;
use crate::tools::lint::get_configured_rules;
use crate::tools::lint::ConfiguredRules;
use crate::util::fs::canonicalize_path_maybe_not_exists;
-use crate::util::path::specifier_to_file_path;
use deno_ast::MediaType;
use deno_config::FmtOptionsConfig;
use deno_config::TsConfig;
@@ -25,6 +24,7 @@ use deno_core::serde_json::Value;
use deno_core::ModuleSpecifier;
use deno_lockfile::Lockfile;
use deno_runtime::deno_node::PackageJson;
+use deno_runtime::fs_util::specifier_to_file_path;
use deno_runtime::permissions::PermissionsContainer;
use import_map::ImportMap;
use lsp::Url;
diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs
index e7ef048cf..154dfb5dc 100644
--- a/cli/lsp/documents.rs
+++ b/cli/lsp/documents.rs
@@ -20,7 +20,7 @@ use crate::resolver::CliNodeResolver;
use crate::resolver::SloppyImportsFsEntry;
use crate::resolver::SloppyImportsResolution;
use crate::resolver::SloppyImportsResolver;
-use crate::util::path::specifier_to_file_path;
+use deno_runtime::fs_util::specifier_to_file_path;
use dashmap::DashMap;
use deno_ast::MediaType;
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index 73847807b..86d7d65c5 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -120,10 +120,10 @@ use crate::tools::upgrade::check_for_upgrades_for_lsp;
use crate::tools::upgrade::upgrade_check_enabled;
use crate::util::fs::remove_dir_all_if_exists;
use crate::util::path::is_importable_ext;
-use crate::util::path::specifier_to_file_path;
use crate::util::path::to_percent_decoded_str;
use crate::util::progress_bar::ProgressBar;
use crate::util::progress_bar::ProgressBarStyle;
+use deno_runtime::fs_util::specifier_to_file_path;
struct LspRootCertStoreProvider(RootCertStore);
diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs
index 13da932f7..d36b59821 100644
--- a/cli/lsp/tsc.rs
+++ b/cli/lsp/tsc.rs
@@ -30,8 +30,8 @@ use crate::tsc;
use crate::tsc::ResolveArgs;
use crate::tsc::MISSING_DEPENDENCY_SPECIFIER;
use crate::util::path::relative_specifier;
-use crate::util::path::specifier_to_file_path;
use crate::util::path::to_percent_decoded_str;
+use deno_runtime::fs_util::specifier_to_file_path;
use dashmap::DashMap;
use deno_ast::MediaType;
diff --git a/cli/module_loader.rs b/cli/module_loader.rs
index 940cfbd8e..a6c8d1338 100644
--- a/cli/module_loader.rs
+++ b/cli/module_loader.rs
@@ -4,6 +4,8 @@ use crate::args::jsr_url;
use crate::args::CliOptions;
use crate::args::DenoSubcommand;
use crate::args::TsTypeLib;
+use crate::cache::CodeCache;
+use crate::cache::ModuleInfoCache;
use crate::cache::ParsedSourceCache;
use crate::emit::Emitter;
use crate::graph_util::graph_lock_or_exit;
@@ -50,7 +52,9 @@ use deno_graph::JsonModule;
use deno_graph::Module;
use deno_graph::Resolution;
use deno_lockfile::Lockfile;
+use deno_runtime::code_cache;
use deno_runtime::deno_node::NodeResolutionMode;
+use deno_runtime::fs_util::code_timestamp;
use deno_runtime::permissions::PermissionsContainer;
use deno_semver::npm::NpmPackageReqReference;
use deno_terminal::colors;
@@ -311,6 +315,8 @@ struct SharedCliModuleLoaderState {
resolver: Arc<CliGraphResolver>,
node_resolver: Arc<CliNodeResolver>,
npm_module_loader: NpmModuleLoader,
+ code_cache: Option<Arc<CodeCache>>,
+ module_info_cache: Arc<ModuleInfoCache>,
}
pub struct CliModuleLoaderFactory {
@@ -328,6 +334,8 @@ impl CliModuleLoaderFactory {
resolver: Arc<CliGraphResolver>,
node_resolver: Arc<CliNodeResolver>,
npm_module_loader: NpmModuleLoader,
+ code_cache: Option<Arc<CodeCache>>,
+ module_info_cache: Arc<ModuleInfoCache>,
) -> Self {
Self {
shared: Arc::new(SharedCliModuleLoaderState {
@@ -348,6 +356,8 @@ impl CliModuleLoaderFactory {
resolver,
node_resolver,
npm_module_loader,
+ code_cache,
+ module_info_cache,
}),
}
}
@@ -458,12 +468,40 @@ impl CliModuleLoader {
return Err(generic_error("Attempted to load JSON module without specifying \"type\": \"json\" attribute in the import statement."));
}
+ let code_cache = if module_type == ModuleType::JavaScript {
+ self.shared.code_cache.as_ref().and_then(|cache| {
+ let code_hash = self
+ .get_code_hash_or_timestamp(specifier, code_source.media_type)
+ .ok()
+ .flatten();
+ if let Some(code_hash) = code_hash {
+ cache
+ .get_sync(
+ specifier.as_str(),
+ code_cache::CodeCacheType::EsModule,
+ &code_hash,
+ )
+ .map(Cow::from)
+ .inspect(|_| {
+ // This log line is also used by tests.
+ log::debug!(
+ "V8 code cache hit for ES module: {specifier}, [{code_hash:?}]"
+ );
+ })
+ } else {
+ None
+ }
+ })
+ } else {
+ None
+ };
+
Ok(ModuleSource::new_with_redirect(
module_type,
ModuleSourceCode::String(code),
specifier,
&code_source.found_url,
- None,
+ code_cache,
))
}
@@ -603,6 +641,25 @@ impl CliModuleLoader {
resolution.map_err(|err| err.into())
}
+
+ fn get_code_hash_or_timestamp(
+ &self,
+ specifier: &ModuleSpecifier,
+ media_type: MediaType,
+ ) -> Result<Option<String>, AnyError> {
+ let hash = self
+ .shared
+ .module_info_cache
+ .get_module_source_hash(specifier, media_type)?;
+ if let Some(hash) = hash {
+ return Ok(Some(hash.into()));
+ }
+
+ // Use the modified timestamp from the local file system if we don't have a hash.
+ let timestamp = code_timestamp(specifier.as_str())
+ .map(|timestamp| timestamp.to_string())?;
+ Ok(Some(timestamp))
+ }
}
impl ModuleLoader for CliModuleLoader {
@@ -678,6 +735,33 @@ impl ModuleLoader for CliModuleLoader {
}
.boxed_local()
}
+
+ fn code_cache_ready(
+ &self,
+ specifier: &ModuleSpecifier,
+ code_cache: &[u8],
+ ) -> Pin<Box<dyn Future<Output = ()>>> {
+ if let Some(cache) = self.shared.code_cache.as_ref() {
+ let media_type = MediaType::from_specifier(specifier);
+ let code_hash = self
+ .get_code_hash_or_timestamp(specifier, media_type)
+ .ok()
+ .flatten();
+ if let Some(code_hash) = code_hash {
+ // This log line is also used by tests.
+ log::debug!(
+ "Updating V8 code cache for ES module: {specifier}, [{code_hash:?}]"
+ );
+ cache.set_sync(
+ specifier.as_str(),
+ code_cache::CodeCacheType::EsModule,
+ &code_hash,
+ code_cache,
+ );
+ }
+ }
+ async {}.boxed_local()
+ }
}
struct CliSourceMapGetter {
diff --git a/cli/npm/byonm.rs b/cli/npm/byonm.rs
index 1e61ce885..931745537 100644
--- a/cli/npm/byonm.rs
+++ b/cli/npm/byonm.rs
@@ -21,7 +21,7 @@ use crate::args::package_json::get_local_package_json_version_reqs;
use crate::args::NpmProcessState;
use crate::args::NpmProcessStateKind;
use crate::util::fs::canonicalize_path_maybe_not_exists_with_fs;
-use crate::util::path::specifier_to_file_path;
+use deno_runtime::fs_util::specifier_to_file_path;
use super::common::types_package_name;
use super::CliNpmResolver;
diff --git a/cli/resolver.rs b/cli/resolver.rs
index d5a85e001..ea12a6687 100644
--- a/cli/resolver.rs
+++ b/cli/resolver.rs
@@ -27,6 +27,7 @@ use deno_runtime::deno_node::NodeResolutionMode;
use deno_runtime::deno_node::NodeResolver;
use deno_runtime::deno_node::NpmResolver as DenoNodeNpmResolver;
use deno_runtime::deno_node::PackageJson;
+use deno_runtime::fs_util::specifier_to_file_path;
use deno_runtime::permissions::PermissionsContainer;
use deno_semver::npm::NpmPackageReqReference;
use deno_semver::package::PackageReq;
@@ -48,7 +49,6 @@ 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 fn format_range_with_colors(range: &deno_graph::Range) -> String {
diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs
index 043f05e13..003e2bf79 100644
--- a/cli/standalone/mod.rs
+++ b/cli/standalone/mod.rs
@@ -572,6 +572,8 @@ pub async fn run(
// metadata.disable_deprecated_api_warning,
true,
false,
+ // Code cache is not supported for standalone binary yet.
+ None,
);
// Initialize v8 once from the main thread.
diff --git a/cli/tools/vendor/mod.rs b/cli/tools/vendor/mod.rs
index 5a76365ee..2abdf6e99 100644
--- a/cli/tools/vendor/mod.rs
+++ b/cli/tools/vendor/mod.rs
@@ -24,7 +24,7 @@ use crate::tools::fmt::format_json;
use crate::util::fs::canonicalize_path;
use crate::util::fs::resolve_from_cwd;
use crate::util::path::relative_specifier;
-use crate::util::path::specifier_to_file_path;
+use deno_runtime::fs_util::specifier_to_file_path;
mod analyze;
mod build;
diff --git a/cli/util/path.rs b/cli/util/path.rs
index 144676c01..a3109ad04 100644
--- a/cli/util/path.rs
+++ b/cli/util/path.rs
@@ -9,8 +9,6 @@ use deno_ast::ModuleSpecifier;
use deno_config::glob::PathGlobMatch;
use deno_config::glob::PathOrPattern;
use deno_config::glob::PathOrPatternSet;
-use deno_core::error::uri_error;
-use deno_core::error::AnyError;
/// Checks if the path has an extension Deno supports for script execution.
pub fn is_script_ext(path: &Path) -> bool {
@@ -82,49 +80,6 @@ pub fn mapped_specifier_for_tsc(
}
}
-/// Attempts to convert a specifier to a file path. By default, uses the Url
-/// crate's `to_file_path()` method, but falls back to try and resolve unix-style
-/// paths on Windows.
-pub fn specifier_to_file_path(
- specifier: &ModuleSpecifier,
-) -> Result<PathBuf, AnyError> {
- let result = if specifier.scheme() != "file" {
- Err(())
- } else if cfg!(windows) {
- match specifier.to_file_path() {
- Ok(path) => Ok(path),
- Err(()) => {
- // This might be a unix-style path which is used in the tests even on Windows.
- // Attempt to see if we can convert it to a `PathBuf`. This code should be removed
- // once/if https://github.com/servo/rust-url/issues/730 is implemented.
- if specifier.scheme() == "file"
- && specifier.host().is_none()
- && specifier.port().is_none()
- && specifier.path_segments().is_some()
- {
- let path_str = specifier.path();
- match String::from_utf8(
- percent_encoding::percent_decode(path_str.as_bytes()).collect(),
- ) {
- Ok(path_str) => Ok(PathBuf::from(path_str)),
- Err(_) => Err(()),
- }
- } else {
- Err(())
- }
- }
- }
- } else {
- specifier.to_file_path()
- };
- match result {
- Ok(path) => Ok(path),
- Err(()) => Err(uri_error(format!(
- "Invalid file path.\n Specifier: {specifier}"
- ))),
- }
-}
-
/// `from.make_relative(to)` but with fixes.
pub fn relative_specifier(
from: &ModuleSpecifier,
@@ -331,24 +286,6 @@ mod test {
}
#[test]
- fn test_specifier_to_file_path() {
- run_success_test("file:///", "/");
- run_success_test("file:///test", "/test");
- run_success_test("file:///dir/test/test.txt", "/dir/test/test.txt");
- run_success_test(
- "file:///dir/test%20test/test.txt",
- "/dir/test test/test.txt",
- );
-
- fn run_success_test(specifier: &str, expected_path: &str) {
- let result =
- specifier_to_file_path(&ModuleSpecifier::parse(specifier).unwrap())
- .unwrap();
- assert_eq!(result, PathBuf::from(expected_path));
- }
- }
-
- #[test]
fn test_relative_specifier() {
let fixtures: Vec<(&str, &str, Option<&str>)> = vec![
("file:///from", "file:///to", Some("./to")),
diff --git a/cli/worker.rs b/cli/worker.rs
index 070671e60..7dbb9b177 100644
--- a/cli/worker.rs
+++ b/cli/worker.rs
@@ -22,6 +22,7 @@ use deno_core::PollEventLoopOptions;
use deno_core::SharedArrayBufferStore;
use deno_core::SourceMapGetter;
use deno_lockfile::Lockfile;
+use deno_runtime::code_cache;
use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel;
use deno_runtime::deno_fs;
use deno_runtime::deno_node;
@@ -140,6 +141,7 @@ struct SharedWorkerState {
enable_future_features: bool,
disable_deprecated_api_warning: bool,
verbose_deprecated_api_warning: bool,
+ code_cache: Option<Arc<dyn code_cache::CodeCache>>,
}
impl SharedWorkerState {
@@ -411,6 +413,7 @@ impl CliMainWorkerFactory {
enable_future_features: bool,
disable_deprecated_api_warning: bool,
verbose_deprecated_api_warning: bool,
+ code_cache: Option<Arc<dyn code_cache::CodeCache>>,
) -> Self {
Self {
shared: Arc::new(SharedWorkerState {
@@ -434,6 +437,7 @@ impl CliMainWorkerFactory {
enable_future_features,
disable_deprecated_api_warning,
verbose_deprecated_api_warning,
+ code_cache,
}),
}
}
@@ -628,6 +632,7 @@ impl CliMainWorkerFactory {
stdio,
feature_checker,
skip_op_registration: shared.options.skip_op_registration,
+ v8_code_cache: shared.code_cache.clone(),
};
let mut worker = MainWorker::bootstrap_from_options(
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml
index 97a6d1397..8028a570d 100644
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -112,6 +112,7 @@ log.workspace = true
netif = "0.1.6"
notify.workspace = true
once_cell.workspace = true
+percent-encoding.workspace = true
regex.workspace = true
ring.workspace = true
rustyline = { workspace = true, features = ["custom-bindings"] }
diff --git a/runtime/code_cache.rs b/runtime/code_cache.rs
new file mode 100644
index 000000000..ccc070365
--- /dev/null
+++ b/runtime/code_cache.rs
@@ -0,0 +1,31 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+pub enum CodeCacheType {
+ EsModule,
+ Script,
+}
+
+impl CodeCacheType {
+ pub fn as_str(&self) -> &str {
+ match self {
+ Self::EsModule => "esmodule",
+ Self::Script => "script",
+ }
+ }
+}
+
+pub trait CodeCache: Send + Sync {
+ fn get_sync(
+ &self,
+ specifier: &str,
+ code_cache_type: CodeCacheType,
+ source_hash: &str,
+ ) -> Option<Vec<u8>>;
+ fn set_sync(
+ &self,
+ specifier: &str,
+ code_cache_type: CodeCacheType,
+ source_hash: &str,
+ data: &[u8],
+ );
+}
diff --git a/runtime/fs_util.rs b/runtime/fs_util.rs
index f7c006a91..09b107300 100644
--- a/runtime/fs_util.rs
+++ b/runtime/fs_util.rs
@@ -1,6 +1,8 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+use deno_ast::ModuleSpecifier;
use deno_core::anyhow::Context;
+use deno_core::error::uri_error;
use deno_core::error::AnyError;
pub use deno_core::normalize_path;
use std::path::Path;
@@ -18,6 +20,60 @@ pub fn resolve_from_cwd(path: &Path) -> Result<PathBuf, AnyError> {
}
}
+/// Attempts to convert a specifier to a file path. By default, uses the Url
+/// crate's `to_file_path()` method, but falls back to try and resolve unix-style
+/// paths on Windows.
+pub fn specifier_to_file_path(
+ specifier: &ModuleSpecifier,
+) -> Result<PathBuf, AnyError> {
+ let result = if specifier.scheme() != "file" {
+ Err(())
+ } else if cfg!(windows) {
+ match specifier.to_file_path() {
+ Ok(path) => Ok(path),
+ Err(()) => {
+ // This might be a unix-style path which is used in the tests even on Windows.
+ // Attempt to see if we can convert it to a `PathBuf`. This code should be removed
+ // once/if https://github.com/servo/rust-url/issues/730 is implemented.
+ if specifier.scheme() == "file"
+ && specifier.host().is_none()
+ && specifier.port().is_none()
+ && specifier.path_segments().is_some()
+ {
+ let path_str = specifier.path();
+ match String::from_utf8(
+ percent_encoding::percent_decode(path_str.as_bytes()).collect(),
+ ) {
+ Ok(path_str) => Ok(PathBuf::from(path_str)),
+ Err(_) => Err(()),
+ }
+ } else {
+ Err(())
+ }
+ }
+ }
+ } else {
+ specifier.to_file_path()
+ };
+ match result {
+ Ok(path) => Ok(path),
+ Err(()) => Err(uri_error(format!(
+ "Invalid file path.\n Specifier: {specifier}"
+ ))),
+ }
+}
+
+pub fn code_timestamp(specifier: &str) -> Result<u64, AnyError> {
+ let specifier = ModuleSpecifier::parse(specifier)?;
+ let path = specifier_to_file_path(&specifier)?;
+ #[allow(clippy::disallowed_methods)]
+ let timestamp = std::fs::metadata(path)?
+ .modified()?
+ .duration_since(std::time::UNIX_EPOCH)?
+ .as_millis() as u64;
+ Ok(timestamp)
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -69,4 +125,22 @@ mod tests {
let absolute_expected = cwd.join(expected);
assert_eq!(resolve_from_cwd(expected).unwrap(), absolute_expected);
}
+
+ #[test]
+ fn test_specifier_to_file_path() {
+ run_success_test("file:///", "/");
+ run_success_test("file:///test", "/test");
+ run_success_test("file:///dir/test/test.txt", "/dir/test/test.txt");
+ run_success_test(
+ "file:///dir/test%20test/test.txt",
+ "/dir/test test/test.txt",
+ );
+
+ fn run_success_test(specifier: &str, expected_path: &str) {
+ let result =
+ specifier_to_file_path(&ModuleSpecifier::parse(specifier).unwrap())
+ .unwrap();
+ assert_eq!(result, PathBuf::from(expected_path));
+ }
+ }
}
diff --git a/runtime/lib.rs b/runtime/lib.rs
index 72fa1cef8..f33e9b7e3 100644
--- a/runtime/lib.rs
+++ b/runtime/lib.rs
@@ -26,6 +26,7 @@ pub use deno_webidl;
pub use deno_websocket;
pub use deno_webstorage;
+pub mod code_cache;
pub mod errors;
pub mod fmt_errors;
pub mod fs_util;
diff --git a/runtime/worker.rs b/runtime/worker.rs
index 956fa2d32..ee6b256ff 100644
--- a/runtime/worker.rs
+++ b/runtime/worker.rs
@@ -1,4 +1,5 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+use std::borrow::Cow;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::atomic::AtomicBool;
@@ -41,6 +42,9 @@ use deno_tls::RootCertStoreProvider;
use deno_web::BlobStore;
use log::debug;
+use crate::code_cache::CodeCache;
+use crate::code_cache::CodeCacheType;
+use crate::fs_util::code_timestamp;
use crate::inspector_server::InspectorServer;
use crate::ops;
use crate::permissions::PermissionsContainer;
@@ -193,6 +197,9 @@ pub struct WorkerOptions {
pub compiled_wasm_module_store: Option<CompiledWasmModuleStore>,
pub stdio: Stdio,
pub feature_checker: Arc<FeatureChecker>,
+
+ /// V8 code cache for module and script source code.
+ pub v8_code_cache: Option<Arc<dyn CodeCache>>,
}
impl Default for WorkerOptions {
@@ -227,6 +234,7 @@ impl Default for WorkerOptions {
bootstrap: Default::default(),
stdio: Default::default(),
feature_checker: Default::default(),
+ v8_code_cache: Default::default(),
}
}
}
@@ -296,6 +304,51 @@ pub fn create_op_metrics(
(op_summary_metrics, op_metrics_factory_fn)
}
+fn get_code_cache(
+ code_cache: Arc<dyn CodeCache>,
+ specifier: &str,
+) -> Option<Vec<u8>> {
+ // Code hashes are not maintained for op_eval_context scripts. Instead we use
+ // the modified timestamp from the local file system.
+ if let Ok(code_timestamp) = code_timestamp(specifier) {
+ code_cache
+ .get_sync(
+ specifier,
+ CodeCacheType::Script,
+ code_timestamp.to_string().as_str(),
+ )
+ .inspect(|_| {
+ // This log line is also used by tests.
+ log::debug!(
+ "V8 code cache hit for script: {specifier}, [{code_timestamp}]"
+ );
+ })
+ } else {
+ None
+ }
+}
+
+fn set_code_cache(
+ code_cache: Arc<dyn CodeCache>,
+ specifier: &str,
+ data: &[u8],
+) {
+ // Code hashes are not maintained for op_eval_context scripts. Instead we use
+ // the modified timestamp from the local file system.
+ if let Ok(code_timestamp) = code_timestamp(specifier) {
+ // This log line is also used by tests.
+ log::debug!(
+ "Updating V8 code cache for script: {specifier}, [{code_timestamp}]",
+ );
+ code_cache.set_sync(
+ specifier,
+ CodeCacheType::Script,
+ code_timestamp.to_string().as_str(),
+ data,
+ );
+ }
+}
+
impl MainWorker {
pub fn bootstrap_from_options(
main_module: ModuleSpecifier,
@@ -495,6 +548,18 @@ impl MainWorker {
validate_import_attributes_cb: Some(Box::new(
validate_import_attributes_callback,
)),
+ enable_code_cache: options.v8_code_cache.is_some(),
+ eval_context_code_cache_cbs: options.v8_code_cache.map(|cache| {
+ let cache_clone = cache.clone();
+ (
+ Box::new(move |specifier: &str| {
+ Ok(get_code_cache(cache.clone(), specifier).map(Cow::Owned))
+ }) as Box<dyn Fn(&_) -> _>,
+ Box::new(move |specifier: &str, data: &[u8]| {
+ set_code_cache(cache_clone.clone(), specifier, data);
+ }) as Box<dyn Fn(&_, &_)>,
+ )
+ }),
..Default::default()
});
diff --git a/tests/integration/run_tests.rs b/tests/integration/run_tests.rs
index 31596c1bb..e477a98d6 100644
--- a/tests/integration/run_tests.rs
+++ b/tests/integration/run_tests.rs
@@ -5177,3 +5177,205 @@ fn run_etag_delete_source_cache() {
"[WILDCARD]Cache body not found. Trying again without etag.[WILDCARD]",
);
}
+
+#[test]
+fn code_cache_test() {
+ let deno_dir = TempDir::new();
+ let test_context = TestContextBuilder::new().use_temp_cwd().build();
+ let temp_dir = test_context.temp_dir();
+ temp_dir.write("main.js", "console.log('Hello World - A');");
+
+ // First run with no prior cache.
+ {
+ let output = test_context
+ .new_command()
+ .env("DENO_DIR", deno_dir.path())
+ .arg("run")
+ .arg("-Ldebug")
+ .arg("main.js")
+ .split_output()
+ .run();
+
+ output
+ .assert_stdout_matches_text("Hello World - A[WILDCARD]")
+ .assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for ES module: file:///[WILDCARD]/main.js[WILDCARD]");
+ assert!(!output.stderr().contains("V8 code cache hit"));
+
+ // Check that the code cache database exists.
+ let code_cache_path = deno_dir.path().join("v8_code_cache_v1");
+ assert!(code_cache_path.exists());
+ }
+
+ // 2nd run with cache.
+ {
+ let output = test_context
+ .new_command()
+ .env("DENO_DIR", deno_dir.path())
+ .arg("run")
+ .arg("-Ldebug")
+ .arg("main.js")
+ .split_output()
+ .run();
+
+ output
+ .assert_stdout_matches_text("Hello World - A[WILDCARD]")
+ .assert_stderr_matches_text("[WILDCARD]V8 code cache hit for ES module: file:///[WILDCARD]/main.js[WILDCARD]");
+ assert!(!output.stderr().contains("Updating V8 code cache"));
+ }
+
+ // Rerun with --no-code-cache.
+ {
+ let output = test_context
+ .new_command()
+ .env("DENO_DIR", deno_dir.path())
+ .arg("run")
+ .arg("-Ldebug")
+ .arg("--no-code-cache")
+ .arg("main.js")
+ .split_output()
+ .run();
+
+ output
+ .assert_stdout_matches_text("Hello World - A[WILDCARD]")
+ .skip_stderr_check();
+ assert!(!output.stderr().contains("V8 code cache"));
+ }
+
+ // Modify the script, and make sure that the cache is rejected.
+ temp_dir.write("main.js", "console.log('Hello World - B');");
+ {
+ let output = test_context
+ .new_command()
+ .env("DENO_DIR", deno_dir.path())
+ .arg("run")
+ .arg("-Ldebug")
+ .arg("main.js")
+ .split_output()
+ .run();
+
+ output
+ .assert_stdout_matches_text("Hello World - B[WILDCARD]")
+ .assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for ES module: file:///[WILDCARD]/main.js[WILDCARD]");
+ assert!(!output.stderr().contains("V8 code cache hit"));
+ }
+}
+
+#[test]
+fn code_cache_npm_test() {
+ let deno_dir = TempDir::new();
+ let test_context = TestContextBuilder::new()
+ .use_temp_cwd()
+ .use_http_server()
+ .build();
+ let temp_dir = test_context.temp_dir();
+ temp_dir.write(
+ "main.js",
+ "import chalk from \"npm:chalk@5\";console.log(chalk('Hello World'));",
+ );
+
+ // First run with no prior cache.
+ {
+ let output = test_context
+ .new_command()
+ .env("DENO_DIR", deno_dir.path())
+ .envs(env_vars_for_npm_tests())
+ .arg("run")
+ .arg("-Ldebug")
+ .arg("-A")
+ .arg("main.js")
+ .split_output()
+ .run();
+
+ output
+ .assert_stdout_matches_text("Hello World[WILDCARD]")
+ .assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for ES module: file:///[WILDCARD]/main.js[WILDCARD]")
+ .assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for ES module: file:///[WILDCARD]/npm/registry/chalk/5.[WILDCARD]/source/index.js[WILDCARD]");
+ assert!(!output.stderr().contains("V8 code cache hit"));
+
+ // Check that the code cache database exists.
+ let code_cache_path = deno_dir.path().join("v8_code_cache_v1");
+ assert!(code_cache_path.exists());
+ }
+
+ // 2nd run with cache.
+ {
+ let output = test_context
+ .new_command()
+ .env("DENO_DIR", deno_dir.path())
+ .envs(env_vars_for_npm_tests())
+ .arg("run")
+ .arg("-Ldebug")
+ .arg("-A")
+ .arg("main.js")
+ .split_output()
+ .run();
+
+ output
+ .assert_stdout_matches_text("Hello World[WILDCARD]")
+ .assert_stderr_matches_text("[WILDCARD]V8 code cache hit for ES module: file:///[WILDCARD]/main.js[WILDCARD]")
+ .assert_stderr_matches_text("[WILDCARD]V8 code cache hit for ES module: file:///[WILDCARD]/npm/registry/chalk/5.[WILDCARD]/source/index.js[WILDCARD]");
+ assert!(!output.stderr().contains("Updating V8 code cache"));
+ }
+}
+
+#[test]
+fn code_cache_npm_with_require_test() {
+ let deno_dir = TempDir::new();
+ let test_context = TestContextBuilder::new()
+ .use_temp_cwd()
+ .use_http_server()
+ .build();
+ let temp_dir = test_context.temp_dir();
+ temp_dir.write(
+ "main.js",
+ "import fraction from \"npm:autoprefixer\";console.log(typeof fraction);",
+ );
+
+ // First run with no prior cache.
+ {
+ let output = test_context
+ .new_command()
+ .env("DENO_DIR", deno_dir.path())
+ .envs(env_vars_for_npm_tests())
+ .arg("run")
+ .arg("-Ldebug")
+ .arg("-A")
+ .arg("main.js")
+ .split_output()
+ .run();
+
+ output
+ .assert_stdout_matches_text("function[WILDCARD]")
+ .assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for ES module: file:///[WILDCARD]/main.js[WILDCARD]")
+ .assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for ES module: file:///[WILDCARD]/npm/registry/autoprefixer/[WILDCARD]/autoprefixer.js[WILDCARD]")
+ .assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for script: file:///[WILDCARD]/npm/registry/autoprefixer/[WILDCARD]/autoprefixer.js[WILDCARD]")
+ .assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for script: file:///[WILDCARD]/npm/registry/browserslist/[WILDCARD]/index.js[WILDCARD]");
+ assert!(!output.stderr().contains("V8 code cache hit"));
+
+ // Check that the code cache database exists.
+ let code_cache_path = deno_dir.path().join("v8_code_cache_v1");
+ assert!(code_cache_path.exists());
+ }
+
+ // 2nd run with cache.
+ {
+ let output = test_context
+ .new_command()
+ .env("DENO_DIR", deno_dir.path())
+ .envs(env_vars_for_npm_tests())
+ .arg("run")
+ .arg("-Ldebug")
+ .arg("-A")
+ .arg("main.js")
+ .split_output()
+ .run();
+
+ output
+ .assert_stdout_matches_text("function[WILDCARD]")
+ .assert_stderr_matches_text("[WILDCARD]V8 code cache hit for ES module: file:///[WILDCARD]/main.js[WILDCARD]")
+ .assert_stderr_matches_text("[WILDCARD]V8 code cache hit for ES module: file:///[WILDCARD]/npm/registry/autoprefixer/[WILDCARD]/autoprefixer.js[WILDCARD]")
+ .assert_stderr_matches_text("[WILDCARD]V8 code cache hit for script: file:///[WILDCARD]/npm/registry/autoprefixer/[WILDCARD]/autoprefixer.js[WILDCARD]")
+ .assert_stderr_matches_text("[WILDCARD]V8 code cache hit for script: file:///[WILDCARD]/npm/registry/browserslist/[WILDCARD]/index.js[WILDCARD]");
+ assert!(!output.stderr().contains("Updating V8 code cache"));
+ }
+}
diff --git a/tests/testdata/run/rejection_handled.ts b/tests/testdata/run/rejection_handled.ts
index f058ff966..c29ae7089 100644
--- a/tests/testdata/run/rejection_handled.ts
+++ b/tests/testdata/run/rejection_handled.ts
@@ -14,4 +14,4 @@ setTimeout(async () => {
setTimeout(() => {
console.log("Success");
-}, 50);
+}, 200);