Revert "refactor: remove tsc snapshot (#27987)" (#28052)

This reverts commit 174e496847.

That commit caused panic on startup like
https://github.com/denoland/deno/issues/28047
or https://github.com/denoland/deno/issues/28044.

Reverting for now, until we figure out what's going wrong.
This commit is contained in:
Bartek Iwańczuk 2025-02-11 12:18:32 +01:00 committed by GitHub
parent 0945634127
commit acdc7dcdcf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 580 additions and 599 deletions

View file

@ -1,183 +1,318 @@
// Copyright 2018-2025 the Deno authors. MIT license. // Copyright 2018-2025 the Deno authors. MIT license.
use std::env; use std::env;
use std::io::Write; use std::path::PathBuf;
use std::path::Path;
use deno_core::snapshot::*;
use deno_runtime::*; use deno_runtime::*;
fn compress_decls(out_dir: &Path) { mod ts {
let decls = [ use std::collections::HashMap;
"lib.deno_webgpu.d.ts", use std::io::Write;
"lib.deno.ns.d.ts", use std::path::Path;
"lib.deno.unstable.d.ts", use std::path::PathBuf;
"lib.deno.window.d.ts",
"lib.deno.worker.d.ts", use deno_core::op2;
"lib.deno.shared_globals.d.ts", use deno_core::v8;
"lib.deno.ns.d.ts", use deno_core::OpState;
"lib.deno.unstable.d.ts", use deno_error::JsErrorBox;
"lib.decorators.d.ts", use serde::Serialize;
"lib.decorators.legacy.d.ts",
"lib.dom.asynciterable.d.ts", use super::*;
"lib.dom.d.ts",
"lib.dom.extras.d.ts", #[derive(Debug, Serialize)]
"lib.dom.iterable.d.ts", #[serde(rename_all = "camelCase")]
"lib.es2015.collection.d.ts", struct LoadResponse {
"lib.es2015.core.d.ts", data: String,
"lib.es2015.d.ts", version: String,
"lib.es2015.generator.d.ts", script_kind: i32,
"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 = [
"console/lib.deno_console.d.ts", #[op2]
"url/lib.deno_url.d.ts", #[serde]
"web/lib.deno_web.d.ts", // using the same op that is used in `tsc.rs` for loading modules and reading
"fetch/lib.deno_fetch.d.ts", // files, but a slightly different implementation at build time.
"websocket/lib.deno_websocket.d.ts", fn op_load(
"webstorage/lib.deno_webstorage.d.ts", state: &mut OpState,
"canvas/lib.deno_canvas.d.ts", #[string] load_specifier: &str,
"crypto/lib.deno_crypto.d.ts", ) -> Result<LoadResponse, JsErrorBox> {
"cache/lib.deno_cache.d.ts", let op_crate_libs = state.borrow::<HashMap<&str, PathBuf>>();
"net/lib.deno_net.d.ts", let path_dts = state.borrow::<PathBuf>();
"broadcast_channel/lib.deno_broadcast_channel.d.ts", let re_asset = lazy_regex::regex!(r"asset:/{3}lib\.(\S+)\.d\.ts");
];
for ext_decl in ext_decls { // specifiers come across as `asset:///lib.{lib_name}.d.ts` and we need to
let file = format!("../ext/{ext_decl}"); // parse out just the name so we can lookup the asset.
compress_source(out_dir, &file); if let Some(caps) = re_asset.captures(load_specifier) {
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),
))
} }
}
fn compress_source(out_dir: &Path, file: &str) { deno_core::extension!(deno_tsc,
let path = Path::new(file) ops = [
.canonicalize() op_load,
.unwrap_or_else(|_| panic!("expected file \"{file}\" to exist")); ],
let contents = std::fs::read(&path).unwrap(); esm_entry_point = "ext:deno_tsc/99_main_compiler.js",
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);
},
);
println!("cargo:rerun-if-changed={}", path.display()); pub fn create_compiler_snapshot(snapshot_path: PathBuf, cwd: &Path) {
// 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());
let compressed = zstd::bulk::compress(&contents, 19).unwrap(); // ensure we invalidate the build properly.
let mut out = out_dir.join(file.trim_start_matches("../")); for (_, path) in op_crate_libs.iter() {
let mut ext = out println!("cargo:rerun-if-changed={}", path.display());
.extension() }
.map(|s| s.to_string_lossy())
.unwrap_or_default() // libs that should be loaded into the isolate before snapshotting.
.into_owned(); let libs = vec![
ext.push_str(".zstd"); // Deno custom type libraries
out.set_extension(ext); "deno.window",
std::fs::create_dir_all(out.parent().unwrap()).unwrap(); "deno.worker",
let mut file = std::fs::OpenOptions::new() "deno.shared_globals",
.create(true) "deno.ns",
.truncate(true) "deno.unstable",
.write(true) // Deno built-in type libraries
.open(out) "decorators",
.unwrap(); "decorators.legacy",
file "dom.asynciterable",
.write_all(&(contents.len() as u32).to_le_bytes()) "dom",
"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();
file.write_all(&compressed).unwrap(); // Leak to satisfy type-checker. It's okay since it's only run once for a build script.
} 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();
fn compress_sources(out_dir: &Path) { let context = scope.get_current_context();
compress_decls(out_dir); let global = context.global(scope);
let ext_sources = [ let name = v8::String::new(scope, "snapshot").unwrap();
"./tsc/99_main_compiler.js", let snapshot_fn_val = global.get(scope, name.into()).unwrap();
"./tsc/97_ts_host.js", let snapshot_fn: v8::Local<v8::Function> =
"./tsc/98_lsp.js", snapshot_fn_val.try_into().unwrap();
"./tsc/00_typescript.js", let undefined = v8::undefined(scope);
]; let build_libs = build_libs_.clone();
for ext_source in ext_sources { let build_libs_v8 =
compress_source(out_dir, ext_source); deno_core::serde_v8::to_v8(scope, build_libs).unwrap();
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());
}
} }
} }
@ -202,12 +337,6 @@ 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}");
} }
@ -216,6 +345,12 @@ 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();
@ -227,3 +362,11 @@ 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(); let assets = Assets::new(ts_server.clone());
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,6 +913,7 @@ 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.get()); let asset = AssetDocument::new(specifier.clone(), v);
(specifier, asset) (specifier, asset)
}) })
.collect::<AssetsMap>(); .collect::<AssetsMap>();
@ -1430,16 +1430,29 @@ 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() -> Self { pub fn new(ts_server: Arc<TsServer>) -> 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
@ -1464,6 +1477,39 @@ 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,
@ -4501,21 +4547,6 @@ 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}")]
@ -4916,13 +4947,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_and_esm( extensions: vec![deno_tsc::init_ops(
performance, performance,
specifier_map, specifier_map,
request_rx, request_rx,
)], )],
create_params: create_isolate_create_params(), create_params: create_isolate_create_params(),
startup_snapshot: None, startup_snapshot: Some(tsc::compiler_snapshot()),
inspector: has_inspector_server, inspector: has_inspector_server,
..Default::default() ..Default::default()
}); });
@ -5004,7 +5035,6 @@ 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>,
@ -5019,14 +5049,6 @@ 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)]
@ -5406,6 +5428,7 @@ 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
@ -5611,6 +5634,7 @@ 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)
@ -5655,6 +5679,7 @@ impl TscRequest {
TscRequest::GetSignatureHelpItems(_) => "getSignatureHelpItems", TscRequest::GetSignatureHelpItems(_) => "getSignatureHelpItems",
TscRequest::GetNavigateToItems(_) => "getNavigateToItems", TscRequest::GetNavigateToItems(_) => "getNavigateToItems",
TscRequest::ProvideInlayHints(_) => "provideInlayHints", TscRequest::ProvideInlayHints(_) => "provideInlayHints",
TscRequest::GetAssets => "$getAssets",
} }
} }
} }
@ -6040,6 +6065,52 @@ 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,3 +855,17 @@ 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,6 +8,7 @@ import {
error, error,
filterMapDiagnostic, filterMapDiagnostic,
fromTypeScriptDiagnostics, fromTypeScriptDiagnostics,
getAssets,
getCreateSourceFileOptions, getCreateSourceFileOptions,
host, host,
IS_NODE_SOURCE_FILE_CACHE, IS_NODE_SOURCE_FILE_CACHE,
@ -445,6 +446,9 @@ 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,10 +13,13 @@
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";
@ -211,20 +214,31 @@ function exec({ config, debug: debugFlag, rootNames, localOnly }) {
debug("<<< exec stop"); debug("<<< exec stop");
} }
const libs = ops.op_libs(); globalThis.snapshot = function (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, specifier); ts.libMap.set(lib, `lib.${lib}.d.ts`);
}
// 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,6 +18,7 @@ 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;
@ -58,8 +59,33 @@ 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() let mut assets = get_asset_texts_from_new_runtime()
.unwrap() .unwrap()
.into_iter() .into_iter()
.map(|a| (a.specifier, a.text)) .map(|a| (a.specifier, a.text))
@ -94,146 +120,41 @@ pub fn get_types_declaration_file_text() -> String {
.join("\n") .join("\n")
} }
fn get_asset_texts() -> Result<Vec<AssetText>, AnyError> { fn get_asset_texts_from_new_runtime() -> Result<Vec<AssetText>, AnyError> {
let mut out = Vec::with_capacity(LAZILY_LOADED_STATIC_ASSETS.len()); deno_core::extension!(
for (name, text) in LAZILY_LOADED_STATIC_ASSETS.iter() { deno_cli_tsc,
out.push(AssetText { ops = [
specifier: format!("asset:///{name}"), op_create_hash,
text: text.to_string(), op_emit,
}); op_is_node_file,
} op_load,
Ok(out) op_remap_specifier,
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)?)
} }
macro_rules! maybe_compressed_source { pub fn compiler_snapshot() -> &'static [u8] {
($file: expr) => {{ &COMPILER_SNAPSHOT
#[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! maybe_compressed_lib { macro_rules! inc {
($name: expr, $file: expr) => { ($e:expr) => {
($name, maybe_compressed_source!(concat!("tsc/dts/", $file))) include_str!(concat!("./dts/", $e))
}; };
($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.
@ -241,154 +162,40 @@ impl StaticAssetSource {
/// 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, StaticAssetSource>, HashMap<&'static str, &'static str>,
> = Lazy::new(|| { > = Lazy::new(|| {
([ ([
// compressed in build.rs (
maybe_compressed_ext_lib!( "lib.dom.asynciterable.d.ts",
"lib.deno.console.d.ts", inc!("lib.dom.asynciterable.d.ts"),
"console/lib.deno_console.d.ts"
), ),
maybe_compressed_ext_lib!("lib.deno.url.d.ts", "url/lib.deno_url.d.ts"), ("lib.dom.d.ts", inc!("lib.dom.d.ts")),
maybe_compressed_ext_lib!("lib.deno.web.d.ts", "web/lib.deno_web.d.ts"), ("lib.dom.extras.d.ts", inc!("lib.dom.extras.d.ts")),
maybe_compressed_ext_lib!( ("lib.dom.iterable.d.ts", inc!("lib.dom.iterable.d.ts")),
"lib.deno.fetch.d.ts", ("lib.es6.d.ts", inc!("lib.es6.d.ts")),
"fetch/lib.deno_fetch.d.ts" ("lib.es2016.full.d.ts", inc!("lib.es2016.full.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.deno.websocket.d.ts", "lib.webworker.iterable.d.ts",
"websocket/lib.deno_websocket.d.ts" inc!("lib.webworker.iterable.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",
StaticAssetSource::Uncompressed( "/// <reference types=\"npm:@types/node\" />\n",
"/// <reference types=\"npm:@types/node\" />\n",
),
), ),
]) ])
.iter() .iter()
@ -438,8 +245,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<MaybeStaticSource> { fn get_lazily_loaded_asset(asset: &str) -> Option<&'static str> {
LAZILY_LOADED_STATIC_ASSETS.get(asset).map(|s| s.get()) LAZILY_LOADED_STATIC_ASSETS.get(asset).map(|s| s.to_owned())
} }
fn get_maybe_hash( fn get_maybe_hash(
@ -796,10 +603,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).map(Cow::from); let maybe_source = get_lazily_loaded_asset(name);
hash = get_maybe_hash(maybe_source.as_deref(), state.hash_data); hash = get_maybe_hash(maybe_source, state.hash_data);
media_type = MediaType::from_str(load_specifier); media_type = MediaType::from_str(load_specifier);
maybe_source maybe_source.map(Cow::Borrowed)
} 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)
@ -941,20 +748,6 @@ 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(
@ -1294,84 +1087,6 @@ 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.
@ -1402,6 +1117,36 @@ 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,
@ -1411,7 +1156,8 @@ 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 {
extensions: vec![deno_cli_tsc::init_ops_and_esm( startup_snapshot: Some(compiler_snapshot()),
extensions: vec![deno_cli_tsc::init_ops(
request, request,
root_map, root_map,
remapped_specifiers, remapped_specifiers,
@ -1573,21 +1319,7 @@ 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: None, startup_snapshot: Some(compiler_snapshot()),
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
@ -1670,7 +1402,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.to_string()); assert_eq!(actual.data, expected);
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,12 +11612,14 @@ 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",