0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-02-15 01:57:09 -05:00

refactor: remove tsc snapshot (#27987)

Removes the TSC snapshot to unblock the V8 upgrade (which enables shared
RO heap, and is incompatible with multiple snapshots).


this compresses the sources and declaration files as well, which leads
to a roughly 4.2MB reduction in binary size.

this currently adds about 80ms to deno check times, but code cache isn't
wired up for the extension code (namely `00_typescript.js`) yet

```
❯ hyperfine --warmup 5 -p "rm -rf ~/Library/Caches/deno/check_cache_v2" "../../deno/target/release-lite/deno check main.ts" "deno check main.ts"
Benchmark 1: ../../deno/target/release-lite/deno check main.ts
  Time (mean ± σ):     184.2 ms ±   2.2 ms    [User: 378.3 ms, System: 48.2 ms]
  Range (min … max):   181.5 ms … 189.9 ms    15 runs

Benchmark 2: deno check main.ts
  Time (mean ± σ):     107.4 ms ±   1.2 ms    [User: 155.3 ms, System: 23.9 ms]
  Range (min … max):   105.3 ms … 109.6 ms    26 runs

Summary
  deno check main.ts ran
    1.72 ± 0.03 times faster than ../../deno/target/release-lite/deno check main.ts
```

---------

Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
Nathan Whitaker 2025-02-10 17:39:18 +01:00 committed by GitHub
parent 7bd210f9e8
commit 174e496847
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 599 additions and 580 deletions

View file

@ -1,318 +1,183 @@
// Copyright 2018-2025 the Deno authors. MIT license. // Copyright 2018-2025 the Deno authors. MIT license.
use std::env; use std::env;
use std::path::PathBuf; use std::io::Write;
use std::path::Path;
use deno_core::snapshot::*;
use deno_runtime::*; use deno_runtime::*;
mod ts { fn compress_decls(out_dir: &Path) {
use std::collections::HashMap; let decls = [
use std::io::Write; "lib.deno_webgpu.d.ts",
use std::path::Path; "lib.deno.ns.d.ts",
use std::path::PathBuf; "lib.deno.unstable.d.ts",
"lib.deno.window.d.ts",
use deno_core::op2; "lib.deno.worker.d.ts",
use deno_core::v8; "lib.deno.shared_globals.d.ts",
use deno_core::OpState; "lib.deno.ns.d.ts",
use deno_error::JsErrorBox; "lib.deno.unstable.d.ts",
use serde::Serialize; "lib.decorators.d.ts",
"lib.decorators.legacy.d.ts",
use super::*; "lib.dom.asynciterable.d.ts",
"lib.dom.d.ts",
#[derive(Debug, Serialize)] "lib.dom.extras.d.ts",
#[serde(rename_all = "camelCase")] "lib.dom.iterable.d.ts",
struct LoadResponse { "lib.es2015.collection.d.ts",
data: String, "lib.es2015.core.d.ts",
version: String, "lib.es2015.d.ts",
script_kind: i32, "lib.es2015.generator.d.ts",
"lib.es2015.iterable.d.ts",
"lib.es2015.promise.d.ts",
"lib.es2015.proxy.d.ts",
"lib.es2015.reflect.d.ts",
"lib.es2015.symbol.d.ts",
"lib.es2015.symbol.wellknown.d.ts",
"lib.es2016.array.include.d.ts",
"lib.es2016.d.ts",
"lib.es2016.full.d.ts",
"lib.es2016.intl.d.ts",
"lib.es2017.arraybuffer.d.ts",
"lib.es2017.d.ts",
"lib.es2017.date.d.ts",
"lib.es2017.full.d.ts",
"lib.es2017.intl.d.ts",
"lib.es2017.object.d.ts",
"lib.es2017.sharedmemory.d.ts",
"lib.es2017.string.d.ts",
"lib.es2017.typedarrays.d.ts",
"lib.es2018.asyncgenerator.d.ts",
"lib.es2018.asynciterable.d.ts",
"lib.es2018.d.ts",
"lib.es2018.full.d.ts",
"lib.es2018.intl.d.ts",
"lib.es2018.promise.d.ts",
"lib.es2018.regexp.d.ts",
"lib.es2019.array.d.ts",
"lib.es2019.d.ts",
"lib.es2019.full.d.ts",
"lib.es2019.intl.d.ts",
"lib.es2019.object.d.ts",
"lib.es2019.string.d.ts",
"lib.es2019.symbol.d.ts",
"lib.es2020.bigint.d.ts",
"lib.es2020.d.ts",
"lib.es2020.date.d.ts",
"lib.es2020.full.d.ts",
"lib.es2020.intl.d.ts",
"lib.es2020.number.d.ts",
"lib.es2020.promise.d.ts",
"lib.es2020.sharedmemory.d.ts",
"lib.es2020.string.d.ts",
"lib.es2020.symbol.wellknown.d.ts",
"lib.es2021.d.ts",
"lib.es2021.full.d.ts",
"lib.es2021.intl.d.ts",
"lib.es2021.promise.d.ts",
"lib.es2021.string.d.ts",
"lib.es2021.weakref.d.ts",
"lib.es2022.array.d.ts",
"lib.es2022.d.ts",
"lib.es2022.error.d.ts",
"lib.es2022.full.d.ts",
"lib.es2022.intl.d.ts",
"lib.es2022.object.d.ts",
"lib.es2022.regexp.d.ts",
"lib.es2022.string.d.ts",
"lib.es2023.array.d.ts",
"lib.es2023.collection.d.ts",
"lib.es2023.d.ts",
"lib.es2023.full.d.ts",
"lib.es2023.intl.d.ts",
"lib.es2024.arraybuffer.d.ts",
"lib.es2024.collection.d.ts",
"lib.es2024.d.ts",
"lib.es2024.full.d.ts",
"lib.es2024.object.d.ts",
"lib.es2024.promise.d.ts",
"lib.es2024.regexp.d.ts",
"lib.es2024.sharedmemory.d.ts",
"lib.es2024.string.d.ts",
"lib.es5.d.ts",
"lib.es6.d.ts",
"lib.esnext.array.d.ts",
"lib.esnext.collection.d.ts",
"lib.esnext.d.ts",
"lib.esnext.decorators.d.ts",
"lib.esnext.disposable.d.ts",
"lib.esnext.full.d.ts",
"lib.esnext.intl.d.ts",
"lib.esnext.iterator.d.ts",
"lib.scripthost.d.ts",
"lib.webworker.asynciterable.d.ts",
"lib.webworker.d.ts",
"lib.webworker.importscripts.d.ts",
"lib.webworker.iterable.d.ts",
];
for decl in decls {
let file = format!("./tsc/dts/{decl}");
compress_source(out_dir, &file);
} }
let ext_decls = [
#[op2] "console/lib.deno_console.d.ts",
#[serde] "url/lib.deno_url.d.ts",
// using the same op that is used in `tsc.rs` for loading modules and reading "web/lib.deno_web.d.ts",
// files, but a slightly different implementation at build time. "fetch/lib.deno_fetch.d.ts",
fn op_load( "websocket/lib.deno_websocket.d.ts",
state: &mut OpState, "webstorage/lib.deno_webstorage.d.ts",
#[string] load_specifier: &str, "canvas/lib.deno_canvas.d.ts",
) -> Result<LoadResponse, JsErrorBox> { "crypto/lib.deno_crypto.d.ts",
let op_crate_libs = state.borrow::<HashMap<&str, PathBuf>>(); "cache/lib.deno_cache.d.ts",
let path_dts = state.borrow::<PathBuf>(); "net/lib.deno_net.d.ts",
let re_asset = lazy_regex::regex!(r"asset:/{3}lib\.(\S+)\.d\.ts"); "broadcast_channel/lib.deno_broadcast_channel.d.ts",
];
// specifiers come across as `asset:///lib.{lib_name}.d.ts` and we need to for ext_decl in ext_decls {
// parse out just the name so we can lookup the asset. let file = format!("../ext/{ext_decl}");
if let Some(caps) = re_asset.captures(load_specifier) { compress_source(out_dir, &file);
if let Some(lib) = caps.get(1).map(|m| m.as_str()) {
// if it comes from an op crate, we were supplied with the path to the
// file.
let path = if let Some(op_crate_lib) = op_crate_libs.get(lib) {
PathBuf::from(op_crate_lib)
.canonicalize()
.map_err(JsErrorBox::from_err)?
// otherwise we will generate the path ourself
} else {
path_dts.join(format!("lib.{lib}.d.ts"))
};
let data =
std::fs::read_to_string(path).map_err(JsErrorBox::from_err)?;
return Ok(LoadResponse {
data,
version: "1".to_string(),
// this corresponds to `ts.ScriptKind.TypeScript`
script_kind: 3,
});
}
}
Err(JsErrorBox::new(
"InvalidSpecifier",
format!("An invalid specifier was requested: {}", load_specifier),
))
} }
}
deno_core::extension!(deno_tsc, fn compress_source(out_dir: &Path, file: &str) {
ops = [ let path = Path::new(file)
op_load, .canonicalize()
], .unwrap_or_else(|_| panic!("expected file \"{file}\" to exist"));
esm_entry_point = "ext:deno_tsc/99_main_compiler.js", let contents = std::fs::read(&path).unwrap();
esm = [
dir "tsc",
"97_ts_host.js",
"98_lsp.js",
"99_main_compiler.js",
],
js = [
dir "tsc",
"00_typescript.js",
],
options = {
op_crate_libs: HashMap<&'static str, PathBuf>,
build_libs: Vec<&'static str>,
path_dts: PathBuf,
},
state = |state, options| {
state.put(options.op_crate_libs);
state.put(options.build_libs);
state.put(options.path_dts);
},
);
pub fn create_compiler_snapshot(snapshot_path: PathBuf, cwd: &Path) { println!("cargo:rerun-if-changed={}", path.display());
// libs that are being provided by op crates.
let mut op_crate_libs = HashMap::new();
op_crate_libs.insert("deno.cache", deno_cache::get_declaration());
op_crate_libs.insert("deno.console", deno_console::get_declaration());
op_crate_libs.insert("deno.url", deno_url::get_declaration());
op_crate_libs.insert("deno.web", deno_web::get_declaration());
op_crate_libs.insert("deno.fetch", deno_fetch::get_declaration());
op_crate_libs.insert("deno.webgpu", deno_webgpu_get_declaration());
op_crate_libs.insert("deno.websocket", deno_websocket::get_declaration());
op_crate_libs.insert("deno.webstorage", deno_webstorage::get_declaration());
op_crate_libs.insert("deno.canvas", deno_canvas::get_declaration());
op_crate_libs.insert("deno.crypto", deno_crypto::get_declaration());
op_crate_libs.insert(
"deno.broadcast_channel",
deno_broadcast_channel::get_declaration(),
);
op_crate_libs.insert("deno.net", deno_net::get_declaration());
// ensure we invalidate the build properly. let compressed = zstd::bulk::compress(&contents, 19).unwrap();
for (_, path) in op_crate_libs.iter() { let mut out = out_dir.join(file.trim_start_matches("../"));
println!("cargo:rerun-if-changed={}", path.display()); let mut ext = out
} .extension()
.map(|s| s.to_string_lossy())
// libs that should be loaded into the isolate before snapshotting. .unwrap_or_default()
let libs = vec![ .into_owned();
// Deno custom type libraries ext.push_str(".zstd");
"deno.window", out.set_extension(ext);
"deno.worker", std::fs::create_dir_all(out.parent().unwrap()).unwrap();
"deno.shared_globals", let mut file = std::fs::OpenOptions::new()
"deno.ns", .create(true)
"deno.unstable", .truncate(true)
// Deno built-in type libraries .write(true)
"decorators", .open(out)
"decorators.legacy", .unwrap();
"dom.asynciterable", file
"dom", .write_all(&(contents.len() as u32).to_le_bytes())
"dom.extras",
"dom.iterable",
"es2015.collection",
"es2015.core",
"es2015",
"es2015.generator",
"es2015.iterable",
"es2015.promise",
"es2015.proxy",
"es2015.reflect",
"es2015.symbol",
"es2015.symbol.wellknown",
"es2016.array.include",
"es2016",
"es2016.full",
"es2016.intl",
"es2017.arraybuffer",
"es2017",
"es2017.date",
"es2017.full",
"es2017.intl",
"es2017.object",
"es2017.sharedmemory",
"es2017.string",
"es2017.typedarrays",
"es2018.asyncgenerator",
"es2018.asynciterable",
"es2018",
"es2018.full",
"es2018.intl",
"es2018.promise",
"es2018.regexp",
"es2019.array",
"es2019",
"es2019.full",
"es2019.intl",
"es2019.object",
"es2019.string",
"es2019.symbol",
"es2020.bigint",
"es2020",
"es2020.date",
"es2020.full",
"es2020.intl",
"es2020.number",
"es2020.promise",
"es2020.sharedmemory",
"es2020.string",
"es2020.symbol.wellknown",
"es2021",
"es2021.full",
"es2021.intl",
"es2021.promise",
"es2021.string",
"es2021.weakref",
"es2022.array",
"es2022",
"es2022.error",
"es2022.full",
"es2022.intl",
"es2022.object",
"es2022.regexp",
"es2022.string",
"es2023.array",
"es2023.collection",
"es2023",
"es2023.full",
"es2023.intl",
"es2024.arraybuffer",
"es2024.collection",
"es2024",
"es2024.full",
"es2024.object",
"es2024.promise",
"es2024.regexp",
"es2024.sharedmemory",
"es2024.string",
"es5",
"es6",
"esnext.array",
"esnext.collection",
"esnext",
"esnext.decorators",
"esnext.disposable",
"esnext.full",
"esnext.intl",
"esnext.iterator",
"scripthost",
"webworker.asynciterable",
"webworker",
"webworker.importscripts",
"webworker.iterable",
];
let path_dts = cwd.join("tsc/dts");
// ensure we invalidate the build properly.
for name in libs.iter() {
println!(
"cargo:rerun-if-changed={}",
path_dts.join(format!("lib.{name}.d.ts")).display()
);
}
// create a copy of the vector that includes any op crate libs to be passed
// to the JavaScript compiler to build into the snapshot
let mut build_libs = libs.clone();
for (op_lib, _) in op_crate_libs.iter() {
build_libs.push(op_lib.to_owned());
}
// used in the tests to verify that after snapshotting it has the same number
// of lib files loaded and hasn't included any ones lazily loaded from Rust
std::fs::write(
PathBuf::from(env::var_os("OUT_DIR").unwrap())
.join("lib_file_names.json"),
serde_json::to_string(&build_libs).unwrap(),
)
.unwrap(); .unwrap();
// Leak to satisfy type-checker. It's okay since it's only run once for a build script. file.write_all(&compressed).unwrap();
let build_libs_ = Box::leak(Box::new(build_libs.clone())); }
let runtime_cb = Box::new(|rt: &mut deno_core::JsRuntimeForSnapshot| {
let scope = &mut rt.handle_scope();
let context = scope.get_current_context(); fn compress_sources(out_dir: &Path) {
let global = context.global(scope); compress_decls(out_dir);
let name = v8::String::new(scope, "snapshot").unwrap(); let ext_sources = [
let snapshot_fn_val = global.get(scope, name.into()).unwrap(); "./tsc/99_main_compiler.js",
let snapshot_fn: v8::Local<v8::Function> = "./tsc/97_ts_host.js",
snapshot_fn_val.try_into().unwrap(); "./tsc/98_lsp.js",
let undefined = v8::undefined(scope); "./tsc/00_typescript.js",
let build_libs = build_libs_.clone(); ];
let build_libs_v8 = for ext_source in ext_sources {
deno_core::serde_v8::to_v8(scope, build_libs).unwrap(); compress_source(out_dir, ext_source);
snapshot_fn
.call(scope, undefined.into(), &[build_libs_v8])
.unwrap();
});
let output = create_snapshot(
CreateSnapshotOptions {
cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"),
startup_snapshot: None,
extensions: vec![deno_tsc::init_ops_and_esm(
op_crate_libs,
build_libs,
path_dts,
)],
extension_transpiler: None,
with_runtime_cb: Some(runtime_cb),
skip_op_registration: false,
},
None,
)
.unwrap();
// NOTE(bartlomieju): Compressing the TSC snapshot in debug build took
// ~45s on M1 MacBook Pro; without compression it took ~1s.
// Thus we're not using compressed snapshot, trading off
// a lot of build time for some startup time in debug build.
let mut file = std::fs::File::create(snapshot_path).unwrap();
if cfg!(debug_assertions) {
file.write_all(&output.output).unwrap();
} else {
let mut vec = Vec::with_capacity(output.output.len());
vec.extend((output.output.len() as u32).to_le_bytes());
vec.extend_from_slice(
&zstd::bulk::compress(&output.output, 22)
.expect("snapshot compression failed"),
);
file.write_all(&vec).unwrap();
}
for path in output.files_loaded_during_snapshot {
println!("cargo:rerun-if-changed={}", path.display());
}
} }
} }
@ -337,6 +202,12 @@ fn main() {
// To debug snapshot issues uncomment: // To debug snapshot issues uncomment:
// op_fetch_asset::trace_serializer(); // op_fetch_asset::trace_serializer();
if !cfg!(debug_assertions) {
let out_dir =
std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
compress_sources(&out_dir);
}
if let Ok(c) = env::var("DENO_CANARY") { if let Ok(c) = env::var("DENO_CANARY") {
println!("cargo:rustc-env=DENO_CANARY={c}"); println!("cargo:rustc-env=DENO_CANARY={c}");
} }
@ -345,12 +216,6 @@ fn main() {
println!("cargo:rustc-env=TARGET={}", env::var("TARGET").unwrap()); println!("cargo:rustc-env=TARGET={}", env::var("TARGET").unwrap());
println!("cargo:rustc-env=PROFILE={}", env::var("PROFILE").unwrap()); println!("cargo:rustc-env=PROFILE={}", env::var("PROFILE").unwrap());
let c = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
let o = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let compiler_snapshot_path = o.join("COMPILER_SNAPSHOT.bin");
ts::create_compiler_snapshot(compiler_snapshot_path, &c);
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
let mut res = winres::WindowsResource::new(); let mut res = winres::WindowsResource::new();
@ -362,11 +227,3 @@ fn main() {
res.compile().unwrap(); res.compile().unwrap();
} }
} }
fn deno_webgpu_get_declaration() -> PathBuf {
let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
manifest_dir
.join("tsc")
.join("dts")
.join("lib.deno_webgpu.d.ts")
}

View file

@ -606,7 +606,7 @@ impl Inner {
ts_server.clone(), ts_server.clone(),
diagnostics_state.clone(), diagnostics_state.clone(),
); );
let assets = Assets::new(ts_server.clone()); let assets = Assets::new();
let initial_cwd = std::env::current_dir().unwrap_or_else(|_| { let initial_cwd = std::env::current_dir().unwrap_or_else(|_| {
panic!("Could not resolve current working directory") panic!("Could not resolve current working directory")
}); });
@ -913,7 +913,6 @@ impl Inner {
.await?; .await?;
self.ts_fixable_diagnostics = fixable_diagnostics; self.ts_fixable_diagnostics = fixable_diagnostics;
} }
self.assets.initialize(self.snapshot()).await;
self.performance.measure(mark); self.performance.measure(mark);
Ok(InitializeResult { Ok(InitializeResult {

View file

@ -1399,7 +1399,7 @@ fn new_assets_map() -> Arc<Mutex<AssetsMap>> {
.map(|(k, v)| { .map(|(k, v)| {
let url_str = format!("asset:///{k}"); let url_str = format!("asset:///{k}");
let specifier = resolve_url(&url_str).unwrap(); let specifier = resolve_url(&url_str).unwrap();
let asset = AssetDocument::new(specifier.clone(), v); let asset = AssetDocument::new(specifier.clone(), v.get());
(specifier, asset) (specifier, asset)
}) })
.collect::<AssetsMap>(); .collect::<AssetsMap>();
@ -1430,29 +1430,16 @@ impl AssetsSnapshot {
/// multiple threads without needing to worry about race conditions. /// multiple threads without needing to worry about race conditions.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Assets { pub struct Assets {
ts_server: Arc<TsServer>,
assets: Arc<Mutex<AssetsMap>>, assets: Arc<Mutex<AssetsMap>>,
} }
impl Assets { impl Assets {
pub fn new(ts_server: Arc<TsServer>) -> Self { pub fn new() -> Self {
Self { Self {
ts_server,
assets: new_assets_map(), assets: new_assets_map(),
} }
} }
/// Initializes with the assets in the isolate.
pub async fn initialize(&self, state_snapshot: Arc<StateSnapshot>) {
let assets = get_isolate_assets(&self.ts_server, state_snapshot).await;
let mut assets_map = self.assets.lock();
for asset in assets {
if !assets_map.contains_key(asset.specifier()) {
assets_map.insert(asset.specifier().clone(), asset);
}
}
}
pub fn snapshot(&self) -> AssetsSnapshot { pub fn snapshot(&self) -> AssetsSnapshot {
// it's ok to not make a complete copy for snapshotting purposes // it's ok to not make a complete copy for snapshotting purposes
// because assets are static // because assets are static
@ -1477,39 +1464,6 @@ impl Assets {
} }
} }
/// Get all the assets stored in the tsc isolate.
async fn get_isolate_assets(
ts_server: &TsServer,
state_snapshot: Arc<StateSnapshot>,
) -> Vec<AssetDocument> {
let req = TscRequest::GetAssets;
let res: Value = ts_server
.request(state_snapshot, req, None, &Default::default())
.await
.unwrap();
let response_assets = match res {
Value::Array(value) => value,
_ => unreachable!(),
};
let mut assets = Vec::with_capacity(response_assets.len());
for asset in response_assets {
let mut obj = match asset {
Value::Object(obj) => obj,
_ => unreachable!(),
};
let specifier_str = obj.get("specifier").unwrap().as_str().unwrap();
let specifier = ModuleSpecifier::parse(specifier_str).unwrap();
let text = match obj.remove("text").unwrap() {
Value::String(text) => text,
_ => unreachable!(),
};
assets.push(AssetDocument::new(specifier, text));
}
assets
}
fn get_tag_body_text( fn get_tag_body_text(
tag: &JsDocTagInfo, tag: &JsDocTagInfo,
language_server: &language_server::Inner, language_server: &language_server::Inner,
@ -4547,6 +4501,21 @@ fn op_is_node_file(state: &mut OpState, #[string] path: String) -> bool {
r r
} }
#[op2]
#[serde]
fn op_libs() -> Vec<String> {
let mut out =
Vec::with_capacity(crate::tsc::LAZILY_LOADED_STATIC_ASSETS.len());
for key in crate::tsc::LAZILY_LOADED_STATIC_ASSETS.keys() {
let lib = key
.replace("lib.", "")
.replace(".d.ts", "")
.replace("deno_", "deno.");
out.push(lib);
}
out
}
#[derive(Debug, thiserror::Error, deno_error::JsError)] #[derive(Debug, thiserror::Error, deno_error::JsError)]
enum LoadError { enum LoadError {
#[error("{0}")] #[error("{0}")]
@ -4947,13 +4916,13 @@ fn run_tsc_thread(
// supplied snapshot is an isolate that contains the TypeScript language // supplied snapshot is an isolate that contains the TypeScript language
// server. // server.
let mut tsc_runtime = JsRuntime::new(RuntimeOptions { let mut tsc_runtime = JsRuntime::new(RuntimeOptions {
extensions: vec![deno_tsc::init_ops( extensions: vec![deno_tsc::init_ops_and_esm(
performance, performance,
specifier_map, specifier_map,
request_rx, request_rx,
)], )],
create_params: create_isolate_create_params(), create_params: create_isolate_create_params(),
startup_snapshot: Some(tsc::compiler_snapshot()), startup_snapshot: None,
inspector: has_inspector_server, inspector: has_inspector_server,
..Default::default() ..Default::default()
}); });
@ -5035,6 +5004,7 @@ deno_core::extension!(deno_tsc,
op_script_version, op_script_version,
op_project_version, op_project_version,
op_poll_requests, op_poll_requests,
op_libs,
], ],
options = { options = {
performance: Arc<Performance>, performance: Arc<Performance>,
@ -5049,6 +5019,14 @@ deno_core::extension!(deno_tsc,
options.request_rx, options.request_rx,
)); ));
}, },
customizer = |ext: &mut deno_core::Extension| {
use deno_core::ExtensionFileSource;
ext.esm_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_tsc/99_main_compiler.js", crate::tsc::MAIN_COMPILER_SOURCE.get().into()));
ext.esm_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_tsc/97_ts_host.js", crate::tsc::TS_HOST_SOURCE.get().into()));
ext.esm_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_tsc/98_lsp.js", crate::tsc::LSP_SOURCE.get().into()));
ext.js_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_cli_tsc/00_typescript.js", crate::tsc::TYPESCRIPT_SOURCE.get().into()));
ext.esm_entry_point = Some("ext:deno_tsc/99_main_compiler.js");
}
); );
#[derive(Debug, Clone, Deserialize_repr, Serialize_repr)] #[derive(Debug, Clone, Deserialize_repr, Serialize_repr)]
@ -5428,7 +5406,6 @@ pub struct JsNull;
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub enum TscRequest { pub enum TscRequest {
GetDiagnostics((Vec<String>, usize)), GetDiagnostics((Vec<String>, usize)),
GetAssets,
CleanupSemanticCache, CleanupSemanticCache,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6230 // https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6230
@ -5634,7 +5611,6 @@ impl TscRequest {
("provideInlayHints", Some(serde_v8::to_v8(scope, args)?)) ("provideInlayHints", Some(serde_v8::to_v8(scope, args)?))
} }
TscRequest::CleanupSemanticCache => ("cleanupSemanticCache", None), TscRequest::CleanupSemanticCache => ("cleanupSemanticCache", None),
TscRequest::GetAssets => ("$getAssets", None),
}; };
Ok(args) Ok(args)
@ -5679,7 +5655,6 @@ impl TscRequest {
TscRequest::GetSignatureHelpItems(_) => "getSignatureHelpItems", TscRequest::GetSignatureHelpItems(_) => "getSignatureHelpItems",
TscRequest::GetNavigateToItems(_) => "getNavigateToItems", TscRequest::GetNavigateToItems(_) => "getNavigateToItems",
TscRequest::ProvideInlayHints(_) => "provideInlayHints", TscRequest::ProvideInlayHints(_) => "provideInlayHints",
TscRequest::GetAssets => "$getAssets",
} }
} }
} }
@ -6065,52 +6040,6 @@ mod tests {
); );
} }
#[tokio::test]
async fn test_request_assets() {
let (_, ts_server, snapshot, _) = setup(json!({}), &[]).await;
let assets = get_isolate_assets(&ts_server, snapshot).await;
let mut asset_names = assets
.iter()
.map(|a| {
a.specifier()
.to_string()
.replace("asset:///lib.", "")
.replace(".d.ts", "")
})
.collect::<Vec<_>>();
let mut expected_asset_names: Vec<String> = serde_json::from_str(
include_str!(concat!(env!("OUT_DIR"), "/lib_file_names.json")),
)
.unwrap();
asset_names.sort();
// if this test fails, update build.rs
expected_asset_names.sort();
assert_eq!(asset_names, expected_asset_names);
// get some notification when the size of the assets grows
let mut total_size = 0;
for asset in &assets {
total_size += asset.text().len();
}
assert!(total_size > 0);
#[allow(clippy::print_stderr)]
// currently as of TS 5.7, it's 3MB
if total_size > 3_500_000 {
let mut sizes = Vec::new();
for asset in &assets {
sizes.push((asset.specifier(), asset.text().len()));
}
sizes.sort_by_cached_key(|(_, size)| *size);
sizes.reverse();
for (specifier, size) in &sizes {
eprintln!("{}: {}", specifier, size);
}
eprintln!("Total size: {}", total_size);
panic!("Assets were quite large.");
}
}
#[tokio::test] #[tokio::test]
async fn test_modify_sources() { async fn test_modify_sources() {
let (temp_dir, ts_server, snapshot, cache) = setup( let (temp_dir, ts_server, snapshot, cache) = setup(

View file

@ -855,17 +855,3 @@ const setTypesNodeIgnorableNames = new Set([
"WritableStreamDefaultWriter", "WritableStreamDefaultWriter",
]); ]);
ts.deno.setTypesNodeIgnorableNames(setTypesNodeIgnorableNames); ts.deno.setTypesNodeIgnorableNames(setTypesNodeIgnorableNames);
export function getAssets() {
/** @type {{ specifier: string; text: string; }[]} */
const assets = [];
for (const sourceFile of SOURCE_FILE_CACHE.values()) {
if (sourceFile.fileName.startsWith(ASSETS_URL_PREFIX)) {
assets.push({
specifier: sourceFile.fileName,
text: sourceFile.text,
});
}
}
return assets;
}

View file

@ -8,7 +8,6 @@ import {
error, error,
filterMapDiagnostic, filterMapDiagnostic,
fromTypeScriptDiagnostics, fromTypeScriptDiagnostics,
getAssets,
getCreateSourceFileOptions, getCreateSourceFileOptions,
host, host,
IS_NODE_SOURCE_FILE_CACHE, IS_NODE_SOURCE_FILE_CACHE,
@ -446,9 +445,6 @@ function serverRequest(id, method, args, scope, maybeChange) {
ts.getSupportedCodeFixes(), ts.getSupportedCodeFixes(),
); );
} }
case "$getAssets": {
return respond(id, getAssets());
}
case "$getDiagnostics": { case "$getDiagnostics": {
const projectVersion = args[1]; const projectVersion = args[1];
// there's a possibility that we receive a change notification // there's a possibility that we receive a change notification

View file

@ -13,13 +13,10 @@
delete Object.prototype.__proto__; delete Object.prototype.__proto__;
import { import {
assert,
AssertionError, AssertionError,
ASSETS_URL_PREFIX,
debug, debug,
filterMapDiagnostic, filterMapDiagnostic,
fromTypeScriptDiagnostics, fromTypeScriptDiagnostics,
getAssets,
host, host,
setLogDebug, setLogDebug,
} from "./97_ts_host.js"; } from "./97_ts_host.js";
@ -214,31 +211,20 @@ function exec({ config, debug: debugFlag, rootNames, localOnly }) {
debug("<<< exec stop"); debug("<<< exec stop");
} }
globalThis.snapshot = function (libs) { const libs = ops.op_libs();
for (const lib of libs) { for (const lib of libs) {
const specifier = `lib.${lib}.d.ts`; const specifier = `lib.${lib}.d.ts`;
// we are using internal APIs here to "inject" our custom libraries into // we are using internal APIs here to "inject" our custom libraries into
// tsc, so things like `"lib": [ "deno.ns" ]` are supported. // tsc, so things like `"lib": [ "deno.ns" ]` are supported.
if (!ts.libs.includes(lib)) { if (!ts.libs.includes(lib)) {
ts.libs.push(lib); ts.libs.push(lib);
ts.libMap.set(lib, `lib.${lib}.d.ts`); ts.libMap.set(lib, specifier);
}
// we are caching in memory common type libraries that will be re-used by
// tsc on when the snapshot is restored
assert(
!!host.getSourceFile(
`${ASSETS_URL_PREFIX}${specifier}`,
ts.ScriptTarget.ESNext,
),
`failed to load '${ASSETS_URL_PREFIX}${specifier}'`,
);
} }
}; }
// exposes the functions that are called by `tsc::exec()` when type // exposes the functions that are called by `tsc::exec()` when type
// checking TypeScript. // checking TypeScript.
globalThis.exec = exec; globalThis.exec = exec;
globalThis.getAssets = getAssets;
// exposes the functions that are called when the compiler is used as a // exposes the functions that are called when the compiler is used as a
// language service. // language service.

View file

@ -5,10 +5,10 @@ use std::collections::HashMap;
use std::fmt; use std::fmt;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use std::sync::OnceLock;
use deno_ast::MediaType; use deno_ast::MediaType;
use deno_core::anyhow::Context; use deno_core::anyhow::Context;
use deno_core::ascii_str;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::located_script_name; use deno_core::located_script_name;
use deno_core::op2; use deno_core::op2;
@ -18,7 +18,6 @@ use deno_core::serde::Deserializer;
use deno_core::serde::Serialize; use deno_core::serde::Serialize;
use deno_core::serde::Serializer; use deno_core::serde::Serializer;
use deno_core::serde_json::json; use deno_core::serde_json::json;
use deno_core::serde_v8;
use deno_core::url::Url; use deno_core::url::Url;
use deno_core::JsRuntime; use deno_core::JsRuntime;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
@ -59,33 +58,8 @@ pub use self::diagnostics::DiagnosticCategory;
pub use self::diagnostics::Diagnostics; pub use self::diagnostics::Diagnostics;
pub use self::diagnostics::Position; pub use self::diagnostics::Position;
pub static COMPILER_SNAPSHOT: Lazy<Box<[u8]>> = Lazy::new(
#[cold]
#[inline(never)]
|| {
static COMPRESSED_COMPILER_SNAPSHOT: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/COMPILER_SNAPSHOT.bin"));
// NOTE(bartlomieju): Compressing the TSC snapshot in debug build took
// ~45s on M1 MacBook Pro; without compression it took ~1s.
// Thus we're not using compressed snapshot, trading off
// a lot of build time for some startup time in debug build.
#[cfg(debug_assertions)]
return COMPRESSED_COMPILER_SNAPSHOT.to_vec().into_boxed_slice();
#[cfg(not(debug_assertions))]
zstd::bulk::decompress(
&COMPRESSED_COMPILER_SNAPSHOT[4..],
u32::from_le_bytes(COMPRESSED_COMPILER_SNAPSHOT[0..4].try_into().unwrap())
as usize,
)
.unwrap()
.into_boxed_slice()
},
);
pub fn get_types_declaration_file_text() -> String { pub fn get_types_declaration_file_text() -> String {
let mut assets = get_asset_texts_from_new_runtime() let mut assets = get_asset_texts()
.unwrap() .unwrap()
.into_iter() .into_iter()
.map(|a| (a.specifier, a.text)) .map(|a| (a.specifier, a.text))
@ -120,41 +94,146 @@ pub fn get_types_declaration_file_text() -> String {
.join("\n") .join("\n")
} }
fn get_asset_texts_from_new_runtime() -> Result<Vec<AssetText>, AnyError> { fn get_asset_texts() -> Result<Vec<AssetText>, AnyError> {
deno_core::extension!( let mut out = Vec::with_capacity(LAZILY_LOADED_STATIC_ASSETS.len());
deno_cli_tsc, for (name, text) in LAZILY_LOADED_STATIC_ASSETS.iter() {
ops = [ out.push(AssetText {
op_create_hash, specifier: format!("asset:///{name}"),
op_emit, text: text.to_string(),
op_is_node_file, });
op_load, }
op_remap_specifier, Ok(out)
op_resolve,
op_respond,
]
);
// the assets are stored within the typescript isolate, so take them out of there
let mut runtime = JsRuntime::new(RuntimeOptions {
startup_snapshot: Some(compiler_snapshot()),
extensions: vec![deno_cli_tsc::init_ops()],
..Default::default()
});
let global = runtime
.execute_script("get_assets.js", ascii_str!("globalThis.getAssets()"))?;
let scope = &mut runtime.handle_scope();
let local = deno_core::v8::Local::new(scope, global);
Ok(serde_v8::from_v8::<Vec<AssetText>>(scope, local)?)
} }
pub fn compiler_snapshot() -> &'static [u8] { macro_rules! maybe_compressed_source {
&COMPILER_SNAPSHOT ($file: expr) => {{
#[cfg(debug_assertions)]
{
StaticAssetSource::Uncompressed(include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/",
$file
)))
}
#[cfg(not(debug_assertions))]
{
StaticAssetSource::Compressed(CompressedSource::new(include_bytes!(
concat!(env!("OUT_DIR"), "/", $file, ".zstd")
)))
}
}};
(compressed = $comp: expr, uncompressed = $uncomp: expr) => {{
#[cfg(debug_assertions)]
{
StaticAssetSource::Uncompressed(include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/",
$uncomp
)))
}
#[cfg(not(debug_assertions))]
{
StaticAssetSource::Compressed(CompressedSource::new(include_bytes!(
concat!(env!("OUT_DIR"), "/", $comp, ".zstd")
)))
}
}};
} }
macro_rules! inc { macro_rules! maybe_compressed_lib {
($e:expr) => { ($name: expr, $file: expr) => {
include_str!(concat!("./dts/", $e)) ($name, maybe_compressed_source!(concat!("tsc/dts/", $file)))
}; };
($e: expr) => {
maybe_compressed_lib!($e, $e)
};
}
macro_rules! maybe_compressed_ext_lib {
($name: expr, $file: expr) => {
(
$name,
maybe_compressed_source!(
compressed = concat!("ext/", $file),
uncompressed = concat!("../ext/", $file)
),
)
};
}
#[derive(Clone)]
pub enum StaticAssetSource {
#[cfg_attr(debug_assertions, allow(dead_code))]
Compressed(CompressedSource),
Uncompressed(&'static str),
}
/// Like a `Cow` but the owned form is an `Arc<str>` instead of `String`
#[derive(Debug, Clone)]
pub enum MaybeStaticSource {
Computed(Arc<str>),
Static(&'static str),
}
impl std::fmt::Display for MaybeStaticSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MaybeStaticSource::Computed(arc) => write!(f, "{}", arc),
MaybeStaticSource::Static(s) => write!(f, "{}", s),
}
}
}
impl From<MaybeStaticSource> for Cow<'static, str> {
fn from(value: MaybeStaticSource) -> Self {
match value {
MaybeStaticSource::Computed(arc) => Cow::Owned(arc.to_string()),
MaybeStaticSource::Static(s) => Cow::Borrowed(s),
}
}
}
impl AsRef<str> for MaybeStaticSource {
fn as_ref(&self) -> &str {
match self {
MaybeStaticSource::Computed(arc) => arc.as_ref(),
MaybeStaticSource::Static(s) => s,
}
}
}
impl From<MaybeStaticSource> for String {
fn from(value: MaybeStaticSource) -> Self {
match value {
MaybeStaticSource::Computed(arc) => arc.to_string(),
MaybeStaticSource::Static(s) => s.into(),
}
}
}
impl From<MaybeStaticSource> for Arc<str> {
fn from(value: MaybeStaticSource) -> Self {
match value {
MaybeStaticSource::Computed(arc) => arc,
MaybeStaticSource::Static(s) => Arc::from(s),
}
}
}
impl StaticAssetSource {
pub fn get(&self) -> MaybeStaticSource {
match self {
StaticAssetSource::Compressed(compressed_source) => {
MaybeStaticSource::Computed(compressed_source.get())
}
StaticAssetSource::Uncompressed(src) => MaybeStaticSource::Static(src),
}
}
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.get().into()
}
} }
/// Contains static assets that are not preloaded in the compiler snapshot. /// Contains static assets that are not preloaded in the compiler snapshot.
@ -162,40 +241,154 @@ macro_rules! inc {
/// We lazily load these because putting them in the compiler snapshot will /// We lazily load these because putting them in the compiler snapshot will
/// increase memory usage when not used (last time checked by about 0.5MB). /// increase memory usage when not used (last time checked by about 0.5MB).
pub static LAZILY_LOADED_STATIC_ASSETS: Lazy< pub static LAZILY_LOADED_STATIC_ASSETS: Lazy<
HashMap<&'static str, &'static str>, HashMap<&'static str, StaticAssetSource>,
> = Lazy::new(|| { > = Lazy::new(|| {
([ ([
( // compressed in build.rs
"lib.dom.asynciterable.d.ts", maybe_compressed_ext_lib!(
inc!("lib.dom.asynciterable.d.ts"), "lib.deno.console.d.ts",
"console/lib.deno_console.d.ts"
), ),
("lib.dom.d.ts", inc!("lib.dom.d.ts")), maybe_compressed_ext_lib!("lib.deno.url.d.ts", "url/lib.deno_url.d.ts"),
("lib.dom.extras.d.ts", inc!("lib.dom.extras.d.ts")), maybe_compressed_ext_lib!("lib.deno.web.d.ts", "web/lib.deno_web.d.ts"),
("lib.dom.iterable.d.ts", inc!("lib.dom.iterable.d.ts")), maybe_compressed_ext_lib!(
("lib.es6.d.ts", inc!("lib.es6.d.ts")), "lib.deno.fetch.d.ts",
("lib.es2016.full.d.ts", inc!("lib.es2016.full.d.ts")), "fetch/lib.deno_fetch.d.ts"
("lib.es2017.full.d.ts", inc!("lib.es2017.full.d.ts")),
("lib.es2018.full.d.ts", inc!("lib.es2018.full.d.ts")),
("lib.es2019.full.d.ts", inc!("lib.es2019.full.d.ts")),
("lib.es2020.full.d.ts", inc!("lib.es2020.full.d.ts")),
("lib.es2021.full.d.ts", inc!("lib.es2021.full.d.ts")),
("lib.es2022.full.d.ts", inc!("lib.es2022.full.d.ts")),
("lib.esnext.full.d.ts", inc!("lib.esnext.full.d.ts")),
("lib.scripthost.d.ts", inc!("lib.scripthost.d.ts")),
("lib.webworker.d.ts", inc!("lib.webworker.d.ts")),
(
"lib.webworker.importscripts.d.ts",
inc!("lib.webworker.importscripts.d.ts"),
), ),
( maybe_compressed_ext_lib!(
"lib.webworker.iterable.d.ts", "lib.deno.websocket.d.ts",
inc!("lib.webworker.iterable.d.ts"), "websocket/lib.deno_websocket.d.ts"
), ),
maybe_compressed_ext_lib!(
"lib.deno.webstorage.d.ts",
"webstorage/lib.deno_webstorage.d.ts"
),
maybe_compressed_ext_lib!(
"lib.deno.canvas.d.ts",
"canvas/lib.deno_canvas.d.ts"
),
maybe_compressed_ext_lib!(
"lib.deno.crypto.d.ts",
"crypto/lib.deno_crypto.d.ts"
),
maybe_compressed_ext_lib!(
"lib.deno.broadcast_channel.d.ts",
"broadcast_channel/lib.deno_broadcast_channel.d.ts"
),
maybe_compressed_ext_lib!("lib.deno.net.d.ts", "net/lib.deno_net.d.ts"),
maybe_compressed_ext_lib!(
"lib.deno.cache.d.ts",
"cache/lib.deno_cache.d.ts"
),
maybe_compressed_lib!("lib.deno.webgpu.d.ts", "lib.deno_webgpu.d.ts"),
maybe_compressed_lib!("lib.deno.window.d.ts"),
maybe_compressed_lib!("lib.deno.worker.d.ts"),
maybe_compressed_lib!("lib.deno.shared_globals.d.ts"),
maybe_compressed_lib!("lib.deno.ns.d.ts"),
maybe_compressed_lib!("lib.deno.unstable.d.ts"),
maybe_compressed_lib!("lib.decorators.d.ts"),
maybe_compressed_lib!("lib.decorators.legacy.d.ts"),
maybe_compressed_lib!("lib.dom.asynciterable.d.ts"),
maybe_compressed_lib!("lib.dom.d.ts"),
maybe_compressed_lib!("lib.dom.extras.d.ts"),
maybe_compressed_lib!("lib.dom.iterable.d.ts"),
maybe_compressed_lib!("lib.es2015.collection.d.ts"),
maybe_compressed_lib!("lib.es2015.core.d.ts"),
maybe_compressed_lib!("lib.es2015.d.ts"),
maybe_compressed_lib!("lib.es2015.generator.d.ts"),
maybe_compressed_lib!("lib.es2015.iterable.d.ts"),
maybe_compressed_lib!("lib.es2015.promise.d.ts"),
maybe_compressed_lib!("lib.es2015.proxy.d.ts"),
maybe_compressed_lib!("lib.es2015.reflect.d.ts"),
maybe_compressed_lib!("lib.es2015.symbol.d.ts"),
maybe_compressed_lib!("lib.es2015.symbol.wellknown.d.ts"),
maybe_compressed_lib!("lib.es2016.array.include.d.ts"),
maybe_compressed_lib!("lib.es2016.d.ts"),
maybe_compressed_lib!("lib.es2016.full.d.ts"),
maybe_compressed_lib!("lib.es2016.intl.d.ts"),
maybe_compressed_lib!("lib.es2017.arraybuffer.d.ts"),
maybe_compressed_lib!("lib.es2017.d.ts"),
maybe_compressed_lib!("lib.es2017.date.d.ts"),
maybe_compressed_lib!("lib.es2017.full.d.ts"),
maybe_compressed_lib!("lib.es2017.intl.d.ts"),
maybe_compressed_lib!("lib.es2017.object.d.ts"),
maybe_compressed_lib!("lib.es2017.sharedmemory.d.ts"),
maybe_compressed_lib!("lib.es2017.string.d.ts"),
maybe_compressed_lib!("lib.es2017.typedarrays.d.ts"),
maybe_compressed_lib!("lib.es2018.asyncgenerator.d.ts"),
maybe_compressed_lib!("lib.es2018.asynciterable.d.ts"),
maybe_compressed_lib!("lib.es2018.d.ts"),
maybe_compressed_lib!("lib.es2018.full.d.ts"),
maybe_compressed_lib!("lib.es2018.intl.d.ts"),
maybe_compressed_lib!("lib.es2018.promise.d.ts"),
maybe_compressed_lib!("lib.es2018.regexp.d.ts"),
maybe_compressed_lib!("lib.es2019.array.d.ts"),
maybe_compressed_lib!("lib.es2019.d.ts"),
maybe_compressed_lib!("lib.es2019.full.d.ts"),
maybe_compressed_lib!("lib.es2019.intl.d.ts"),
maybe_compressed_lib!("lib.es2019.object.d.ts"),
maybe_compressed_lib!("lib.es2019.string.d.ts"),
maybe_compressed_lib!("lib.es2019.symbol.d.ts"),
maybe_compressed_lib!("lib.es2020.bigint.d.ts"),
maybe_compressed_lib!("lib.es2020.d.ts"),
maybe_compressed_lib!("lib.es2020.date.d.ts"),
maybe_compressed_lib!("lib.es2020.full.d.ts"),
maybe_compressed_lib!("lib.es2020.intl.d.ts"),
maybe_compressed_lib!("lib.es2020.number.d.ts"),
maybe_compressed_lib!("lib.es2020.promise.d.ts"),
maybe_compressed_lib!("lib.es2020.sharedmemory.d.ts"),
maybe_compressed_lib!("lib.es2020.string.d.ts"),
maybe_compressed_lib!("lib.es2020.symbol.wellknown.d.ts"),
maybe_compressed_lib!("lib.es2021.d.ts"),
maybe_compressed_lib!("lib.es2021.full.d.ts"),
maybe_compressed_lib!("lib.es2021.intl.d.ts"),
maybe_compressed_lib!("lib.es2021.promise.d.ts"),
maybe_compressed_lib!("lib.es2021.string.d.ts"),
maybe_compressed_lib!("lib.es2021.weakref.d.ts"),
maybe_compressed_lib!("lib.es2022.array.d.ts"),
maybe_compressed_lib!("lib.es2022.d.ts"),
maybe_compressed_lib!("lib.es2022.error.d.ts"),
maybe_compressed_lib!("lib.es2022.full.d.ts"),
maybe_compressed_lib!("lib.es2022.intl.d.ts"),
maybe_compressed_lib!("lib.es2022.object.d.ts"),
maybe_compressed_lib!("lib.es2022.regexp.d.ts"),
maybe_compressed_lib!("lib.es2022.string.d.ts"),
maybe_compressed_lib!("lib.es2023.array.d.ts"),
maybe_compressed_lib!("lib.es2023.collection.d.ts"),
maybe_compressed_lib!("lib.es2023.d.ts"),
maybe_compressed_lib!("lib.es2023.full.d.ts"),
maybe_compressed_lib!("lib.es2023.intl.d.ts"),
maybe_compressed_lib!("lib.es2024.arraybuffer.d.ts"),
maybe_compressed_lib!("lib.es2024.collection.d.ts"),
maybe_compressed_lib!("lib.es2024.d.ts"),
maybe_compressed_lib!("lib.es2024.full.d.ts"),
maybe_compressed_lib!("lib.es2024.object.d.ts"),
maybe_compressed_lib!("lib.es2024.promise.d.ts"),
maybe_compressed_lib!("lib.es2024.regexp.d.ts"),
maybe_compressed_lib!("lib.es2024.sharedmemory.d.ts"),
maybe_compressed_lib!("lib.es2024.string.d.ts"),
maybe_compressed_lib!("lib.es5.d.ts"),
maybe_compressed_lib!("lib.es6.d.ts"),
maybe_compressed_lib!("lib.esnext.array.d.ts"),
maybe_compressed_lib!("lib.esnext.collection.d.ts"),
maybe_compressed_lib!("lib.esnext.d.ts"),
maybe_compressed_lib!("lib.esnext.decorators.d.ts"),
maybe_compressed_lib!("lib.esnext.disposable.d.ts"),
maybe_compressed_lib!("lib.esnext.full.d.ts"),
maybe_compressed_lib!("lib.esnext.intl.d.ts"),
maybe_compressed_lib!("lib.esnext.iterator.d.ts"),
maybe_compressed_lib!("lib.scripthost.d.ts"),
maybe_compressed_lib!("lib.webworker.asynciterable.d.ts"),
maybe_compressed_lib!("lib.webworker.d.ts"),
maybe_compressed_lib!("lib.webworker.importscripts.d.ts"),
maybe_compressed_lib!("lib.webworker.iterable.d.ts"),
( (
// Special file that can be used to inject the @types/node package. // Special file that can be used to inject the @types/node package.
// This is used for `node:` specifiers. // This is used for `node:` specifiers.
"node_types.d.ts", "node_types.d.ts",
"/// <reference types=\"npm:@types/node\" />\n", StaticAssetSource::Uncompressed(
"/// <reference types=\"npm:@types/node\" />\n",
),
), ),
]) ])
.iter() .iter()
@ -245,8 +438,8 @@ pub struct AssetText {
} }
/// Retrieve a static asset that are included in the binary. /// Retrieve a static asset that are included in the binary.
fn get_lazily_loaded_asset(asset: &str) -> Option<&'static str> { fn get_lazily_loaded_asset(asset: &str) -> Option<MaybeStaticSource> {
LAZILY_LOADED_STATIC_ASSETS.get(asset).map(|s| s.to_owned()) LAZILY_LOADED_STATIC_ASSETS.get(asset).map(|s| s.get())
} }
fn get_maybe_hash( fn get_maybe_hash(
@ -603,10 +796,10 @@ fn op_load_inner(
} else if load_specifier == MISSING_DEPENDENCY_SPECIFIER { } else if load_specifier == MISSING_DEPENDENCY_SPECIFIER {
None None
} else if let Some(name) = load_specifier.strip_prefix("asset:///") { } else if let Some(name) = load_specifier.strip_prefix("asset:///") {
let maybe_source = get_lazily_loaded_asset(name); let maybe_source = get_lazily_loaded_asset(name).map(Cow::from);
hash = get_maybe_hash(maybe_source, state.hash_data); hash = get_maybe_hash(maybe_source.as_deref(), state.hash_data);
media_type = MediaType::from_str(load_specifier); media_type = MediaType::from_str(load_specifier);
maybe_source.map(Cow::Borrowed) maybe_source
} else { } else {
let specifier = if let Some(remapped_specifier) = let specifier = if let Some(remapped_specifier) =
state.maybe_remapped_specifier(load_specifier) state.maybe_remapped_specifier(load_specifier)
@ -748,6 +941,20 @@ fn op_remap_specifier(
.map(|url| url.to_string()) .map(|url| url.to_string())
} }
#[op2]
#[serde]
fn op_libs() -> Vec<String> {
let mut out = Vec::with_capacity(LAZILY_LOADED_STATIC_ASSETS.len());
for key in LAZILY_LOADED_STATIC_ASSETS.keys() {
let lib = key
.replace("lib.", "")
.replace(".d.ts", "")
.replace("deno_", "deno.");
out.push(lib);
}
out
}
#[op2] #[op2]
#[serde] #[serde]
fn op_resolve( fn op_resolve(
@ -1087,6 +1294,84 @@ pub enum ExecError {
Core(deno_core::error::CoreError), Core(deno_core::error::CoreError),
} }
#[derive(Clone)]
pub(crate) struct CompressedSource {
bytes: &'static [u8],
uncompressed: OnceLock<Arc<str>>,
}
impl CompressedSource {
#[cfg_attr(debug_assertions, allow(dead_code))]
pub(crate) const fn new(bytes: &'static [u8]) -> Self {
Self {
bytes,
uncompressed: OnceLock::new(),
}
}
pub(crate) fn get(&self) -> Arc<str> {
self
.uncompressed
.get_or_init(|| decompress_source(self.bytes))
.clone()
}
}
pub(crate) static MAIN_COMPILER_SOURCE: StaticAssetSource =
maybe_compressed_source!("tsc/99_main_compiler.js");
pub(crate) static LSP_SOURCE: StaticAssetSource =
maybe_compressed_source!("tsc/98_lsp.js");
pub(crate) static TS_HOST_SOURCE: StaticAssetSource =
maybe_compressed_source!("tsc/97_ts_host.js");
pub(crate) static TYPESCRIPT_SOURCE: StaticAssetSource =
maybe_compressed_source!("tsc/00_typescript.js");
pub(crate) fn decompress_source(contents: &[u8]) -> Arc<str> {
let len_bytes = contents[0..4].try_into().unwrap();
let len = u32::from_le_bytes(len_bytes);
let uncompressed =
zstd::bulk::decompress(&contents[4..], len as usize).unwrap();
String::from_utf8(uncompressed).unwrap().into()
}
deno_core::extension!(deno_cli_tsc,
ops = [
op_create_hash,
op_emit,
op_is_node_file,
op_load,
op_remap_specifier,
op_resolve,
op_respond,
op_libs,
],
options = {
request: Request,
root_map: HashMap<String, Url>,
remapped_specifiers: HashMap<String, Url>,
},
state = |state, options| {
state.put(State::new(
options.request.graph,
options.request.hash_data,
options.request.maybe_npm,
options.request.maybe_tsbuildinfo,
options.root_map,
options.remapped_specifiers,
std::env::current_dir()
.context("Unable to get CWD")
.unwrap(),
));
},
customizer = |ext: &mut deno_core::Extension| {
use deno_core::ExtensionFileSource;
ext.esm_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_cli_tsc/99_main_compiler.js", crate::tsc::MAIN_COMPILER_SOURCE.get().into()));
ext.esm_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_cli_tsc/97_ts_host.js", crate::tsc::TS_HOST_SOURCE.get().into()));
ext.esm_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_cli_tsc/98_lsp.js", crate::tsc::LSP_SOURCE.get().into()));
ext.js_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_cli_tsc/00_typescript.js", crate::tsc::TYPESCRIPT_SOURCE.get().into()));
ext.esm_entry_point = Some("ext:deno_cli_tsc/99_main_compiler.js");
}
);
/// Execute a request on the supplied snapshot, returning a response which /// Execute a request on the supplied snapshot, returning a response which
/// contains information, like any emitted files, diagnostics, statistics and /// contains information, like any emitted files, diagnostics, statistics and
/// optionally an updated TypeScript build info. /// optionally an updated TypeScript build info.
@ -1117,36 +1402,6 @@ pub fn exec(request: Request) -> Result<Response, ExecError> {
}) })
.collect(); .collect();
deno_core::extension!(deno_cli_tsc,
ops = [
op_create_hash,
op_emit,
op_is_node_file,
op_load,
op_remap_specifier,
op_resolve,
op_respond,
],
options = {
request: Request,
root_map: HashMap<String, Url>,
remapped_specifiers: HashMap<String, Url>,
},
state = |state, options| {
state.put(State::new(
options.request.graph,
options.request.hash_data,
options.request.maybe_npm,
options.request.maybe_tsbuildinfo,
options.root_map,
options.remapped_specifiers,
std::env::current_dir()
.context("Unable to get CWD")
.unwrap(),
));
},
);
let request_value = json!({ let request_value = json!({
"config": request.config, "config": request.config,
"debug": request.debug, "debug": request.debug,
@ -1156,8 +1411,7 @@ pub fn exec(request: Request) -> Result<Response, ExecError> {
let exec_source = format!("globalThis.exec({request_value})"); let exec_source = format!("globalThis.exec({request_value})");
let mut runtime = JsRuntime::new(RuntimeOptions { let mut runtime = JsRuntime::new(RuntimeOptions {
startup_snapshot: Some(compiler_snapshot()), extensions: vec![deno_cli_tsc::init_ops_and_esm(
extensions: vec![deno_cli_tsc::init_ops(
request, request,
root_map, root_map,
remapped_specifiers, remapped_specifiers,
@ -1319,7 +1573,21 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_compiler_snapshot() { async fn test_compiler_snapshot() {
let mut js_runtime = JsRuntime::new(RuntimeOptions { let mut js_runtime = JsRuntime::new(RuntimeOptions {
startup_snapshot: Some(compiler_snapshot()), startup_snapshot: None,
extensions: vec![super::deno_cli_tsc::init_ops_and_esm(
Request {
check_mode: TypeCheckMode::All,
config: Arc::new(TsConfig(json!({}))),
debug: false,
graph: Arc::new(ModuleGraph::new(GraphKind::TypesOnly)),
hash_data: 0,
maybe_npm: None,
maybe_tsbuildinfo: None,
root_names: vec![],
},
HashMap::new(),
HashMap::new(),
)],
..Default::default() ..Default::default()
}); });
js_runtime js_runtime
@ -1402,7 +1670,7 @@ mod tests {
.expect("should have invoked op") .expect("should have invoked op")
.expect("load should have succeeded"); .expect("load should have succeeded");
let expected = get_lazily_loaded_asset("lib.dom.d.ts").unwrap(); let expected = get_lazily_loaded_asset("lib.dom.d.ts").unwrap();
assert_eq!(actual.data, expected); assert_eq!(actual.data, expected.to_string());
assert!(actual.version.is_some()); assert!(actual.version.is_some());
assert_eq!(actual.script_kind, 3); assert_eq!(actual.script_kind, 3);
} }

View file

@ -11612,14 +11612,12 @@ fn lsp_performance() {
"lsp.update_diagnostics_lint", "lsp.update_diagnostics_lint",
"lsp.update_diagnostics_ts", "lsp.update_diagnostics_ts",
"lsp.update_global_cache", "lsp.update_global_cache",
"tsc.host.$getAssets",
"tsc.host.$getDiagnostics", "tsc.host.$getDiagnostics",
"tsc.host.$getSupportedCodeFixes", "tsc.host.$getSupportedCodeFixes",
"tsc.host.getQuickInfoAtPosition", "tsc.host.getQuickInfoAtPosition",
"tsc.op.op_is_node_file", "tsc.op.op_is_node_file",
"tsc.op.op_load", "tsc.op.op_load",
"tsc.op.op_script_names", "tsc.op.op_script_names",
"tsc.request.$getAssets",
"tsc.request.$getDiagnostics", "tsc.request.$getDiagnostics",
"tsc.request.$getSupportedCodeFixes", "tsc.request.$getSupportedCodeFixes",
"tsc.request.getQuickInfoAtPosition", "tsc.request.getQuickInfoAtPosition",