From 5a30ae448332a7ec0290da91e3553844da1abeef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 24 Jan 2025 00:51:35 +0100 Subject: [PATCH 1/3] leave only 1 op for tsc build --- cli/build.rs | 128 ++++++++++++++++++++++-------------- cli/lsp/tsc.rs | 8 ++- cli/tsc/99_main_compiler.js | 62 +++++++++-------- cli/tsc/mod.rs | 37 ++++------- foo.ts | 5 ++ 5 files changed, 133 insertions(+), 107 deletions(-) create mode 100644 foo.ts diff --git a/cli/build.rs b/cli/build.rs index 742f227ec9dc5c..755a70539e95f7 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -13,47 +13,51 @@ mod ts { use std::path::PathBuf; use deno_core::op2; + use deno_core::v8; use deno_core::OpState; use deno_error::JsErrorBox; use serde::Serialize; use super::*; - #[derive(Debug, Serialize)] - #[serde(rename_all = "camelCase")] - struct BuildInfoResponse { - build_specifier: String, - libs: Vec, - } - - #[op2] - #[serde] - fn op_build_info(state: &mut OpState) -> BuildInfoResponse { - let build_specifier = "asset:///bootstrap.ts".to_string(); - let build_libs = state - .borrow::>() - .iter() - .map(|s| s.to_string()) - .collect(); - BuildInfoResponse { - build_specifier, - libs: build_libs, - } - } - - #[op2(fast)] - fn op_is_node_file() -> bool { - false - } - - #[op2] - #[string] - fn op_script_version( - _state: &mut OpState, - #[string] _arg: &str, - ) -> Result, JsErrorBox> { - Ok(Some("1".to_string())) - } + // #[derive(Debug, Serialize)] + // #[serde(rename_all = "camelCase")] + // struct BuildInfoResponse { + // build_specifier: String, + // libs: Vec, + // } + + // #[op2] + // #[serde] + // fn op_build_info(state: &mut OpState) -> BuildInfoResponse { + // eprintln!("op_build_info"); + // let build_specifier = "asset:///bootstrap.ts".to_string(); + // let build_libs = state + // .borrow::>() + // .iter() + // .map(|s| s.to_string()) + // .collect(); + // BuildInfoResponse { + // build_specifier, + // libs: build_libs, + // } + // } + + // #[op2(fast)] + // fn op_is_node_file() -> bool { + // eprintln!("op_is_node_file"); + // false + // } + + // #[op2] + // #[string] + // fn op_script_version( + // _state: &mut OpState, + // #[string] _arg: &str, + // ) -> Result, JsErrorBox> { + // eprintln!("op_script_version"); + // Ok(Some("1".to_string())) + // } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] @@ -71,22 +75,26 @@ mod ts { state: &mut OpState, #[string] load_specifier: &str, ) -> Result { + eprintln!("op_load {}", load_specifier); + // let op_crate_libs = state.borrow::>(); let path_dts = state.borrow::(); let re_asset = lazy_regex::regex!(r"asset:/{3}lib\.(\S+)\.d\.ts"); - let build_specifier = "asset:///bootstrap.ts"; + // let build_specifier = "asset:///bootstrap.ts"; // we need a basic file to send to tsc to warm it up. - if load_specifier == build_specifier { - Ok(LoadResponse { - data: r#"Deno.writeTextFile("hello.txt", "hello deno!");"#.to_string(), - version: "1".to_string(), - // this corresponds to `ts.ScriptKind.TypeScript` - script_kind: 3, - }) - // specifiers come across as `asset:///lib.{lib_name}.d.ts` and we need to - // parse out just the name so we can lookup the asset. - } else if let Some(caps) = re_asset.captures(load_specifier) { + // if load_specifier == build_specifier { + // return Ok(LoadResponse { + // data: r#"Deno.writeTextFile("hello.txt", "hello deno!");"#.to_string(), + // version: "1".to_string(), + // // this corresponds to `ts.ScriptKind.TypeScript` + // script_kind: 3, + // }); + // } + + // specifiers come across as `asset:///lib.{lib_name}.d.ts` and we need to + // parse out just the name so we can lookup the asset. + 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. @@ -121,7 +129,12 @@ mod ts { } deno_core::extension!(deno_tsc, - ops = [op_build_info, op_is_node_file, op_load, op_script_version], + ops = [ + // op_build_info, + // op_is_node_file, + op_load, + // op_script_version + ], esm_entry_point = "ext:deno_tsc/99_main_compiler.js", esm = [ dir "tsc", @@ -277,6 +290,7 @@ mod ts { ) .unwrap(); + let build_libs_ = Box::leak(Box::new(build_libs.clone())); let output = create_snapshot( CreateSnapshotOptions { cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"), @@ -287,7 +301,25 @@ mod ts { path_dts, )], extension_transpiler: None, - with_runtime_cb: None, + with_runtime_cb: Some(Box::new(|rt| { + let scope = &mut rt.handle_scope(); + + let context = scope.get_current_context(); + let global = context.global(scope); + + let name = v8::String::new(scope, "snapshot").unwrap(); + let snapshot_fn_val = global.get(scope, name.into()).unwrap(); + let snapshot_fn: v8::Local = + snapshot_fn_val.try_into().unwrap(); + let undefined = v8::undefined(scope); + let build_libs = build_libs_.clone(); + let build_libs_v8 = + deno_core::serde_v8::to_v8(scope, build_libs).unwrap(); + + snapshot_fn + .call(scope, undefined.into(), &[build_libs_v8]) + .unwrap(); + })), skip_op_registration: false, }, None, diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index e1b3691c0aa452..96efba315c9c87 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -4919,12 +4919,14 @@ fn run_tsc_thread( deno_core::extension!(deno_tsc, ops = [ - op_is_cancelled, + op_resolve, + op_respond, + op_is_node_file, op_load, + + op_is_cancelled, op_release, - op_resolve, - op_respond, op_script_names, op_script_version, op_project_version, diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js index c99cfce9a238c3..f7d8baa3fba5ef 100644 --- a/cli/tsc/99_main_compiler.js +++ b/cli/tsc/99_main_compiler.js @@ -225,44 +225,42 @@ function exec({ config, debug: debugFlag, rootNames, localOnly }) { debug("<<< exec stop"); } -// A build time only op that provides some setup information that is used to -// ensure the snapshot is setup properly. -/** @type {{ buildSpecifier: string; libs: string[]; nodeBuiltInModuleNames: string[] }} */ -const { buildSpecifier, libs } = ops.op_build_info(); - -for (const lib of libs) { - const specifier = `lib.${lib}.d.ts`; - // we are using internal APIs here to "inject" our custom libraries into - // tsc, so things like `"lib": [ "deno.ns" ]` are supported. - if (!ts.libs.includes(lib)) { - ts.libs.push(lib); - ts.libMap.set(lib, `lib.${lib}.d.ts`); +globalThis.snapshot = function (libs) { + for (const lib of libs) { + const specifier = `lib.${lib}.d.ts`; + // we are using internal APIs here to "inject" our custom libraries into + // tsc, so things like `"lib": [ "deno.ns" ]` are supported. + if (!ts.libs.includes(lib)) { + ts.libs.push(lib); + 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}'`, + ); } - // 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}'`, - ); -} +}; + // this helps ensure as much as possible is in memory that is re-usable // before the snapshotting is done, which helps unsure fast "startup" for // subsequent uses of tsc in Deno. -const TS_SNAPSHOT_PROGRAM = ts.createProgram({ - rootNames: [buildSpecifier], - options: SNAPSHOT_COMPILE_OPTIONS, - host, -}); -assert( - ts.getPreEmitDiagnostics(TS_SNAPSHOT_PROGRAM).length === 0, - "lib.d.ts files have errors", -); +// const TS_SNAPSHOT_PROGRAM = ts.createProgram({ +// rootNames: [buildSpecifier], +// options: SNAPSHOT_COMPILE_OPTIONS, +// host, +// }); +// assert( +// ts.getPreEmitDiagnostics(TS_SNAPSHOT_PROGRAM).length === 0, +// "lib.d.ts files have errors", +// ); // remove this now that we don't need it anymore for warming up tsc -SOURCE_FILE_CACHE.delete(buildSpecifier); +// SOURCE_FILE_CACHE.delete(buildSpecifier); // exposes the functions that are called by `tsc::exec()` when type // checking TypeScript. diff --git a/cli/tsc/mod.rs b/cli/tsc/mod.rs index 868610cb40a535..f358024c64578e 100644 --- a/cli/tsc/mod.rs +++ b/cli/tsc/mod.rs @@ -465,33 +465,23 @@ fn op_create_hash_inner(s: &mut OpState, text: &str) -> String { get_hash(text, state.hash_data) } -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct EmitArgs { - /// The text data/contents of the file. - data: String, - /// The _internal_ filename for the file. This will be used to determine how - /// the file is cached and stored. - file_name: String, -} - #[op2(fast)] fn op_emit( state: &mut OpState, #[string] data: String, - #[string] file_name: String, + #[string] file_name: &str, ) -> bool { - op_emit_inner(state, EmitArgs { data, file_name }) + op_emit_inner(state, data, file_name) } #[inline] -fn op_emit_inner(state: &mut OpState, args: EmitArgs) -> bool { +fn op_emit_inner(state: &mut OpState, data: String, file_name: &str) -> bool { let state = state.borrow_mut::(); - match args.file_name.as_ref() { - "internal:///.tsbuildinfo" => state.maybe_tsbuildinfo = Some(args.data), + match file_name { + "internal:///.tsbuildinfo" => state.maybe_tsbuildinfo = Some(data), _ => { if cfg!(debug_assertions) { - panic!("Unhandled emit write: {}", args.file_name); + panic!("Unhandled emit write: {}", file_name); } } } @@ -1107,13 +1097,14 @@ pub fn exec(request: Request) -> Result { deno_core::extension!(deno_cli_tsc, ops = [ - op_create_hash, - op_emit, + op_resolve, + op_respond, op_is_node_file, op_load, + + op_create_hash, + op_emit, op_remap_specifier, - op_resolve, - op_respond, ], options = { request: Request, @@ -1344,10 +1335,8 @@ mod tests { let mut state = setup(None, None, None).await; let actual = op_emit_inner( &mut state, - EmitArgs { - data: "some file content".to_string(), - file_name: "internal:///.tsbuildinfo".to_string(), - }, + "some file content".to_string(), + "internal:///.tsbuildinfo", ); assert!(actual); let state = state.borrow::(); diff --git a/foo.ts b/foo.ts new file mode 100644 index 00000000000000..c236135c28fed6 --- /dev/null +++ b/foo.ts @@ -0,0 +1,5 @@ +function foo(a: number) { + // +} + +foo("sadf"); From 76fe8094d6c90650899013ee127f02770c623753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 24 Jan 2025 02:09:54 +0100 Subject: [PATCH 2/3] refactor(tsc): remove TS program creation during snapshotting --- cli/build.rs | 115 +++++++++--------------------------- cli/tsc/99_main_compiler.js | 27 --------- foo.ts | 5 -- 3 files changed, 29 insertions(+), 118 deletions(-) delete mode 100644 foo.ts diff --git a/cli/build.rs b/cli/build.rs index 755a70539e95f7..c8e156a265d300 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -20,45 +20,6 @@ mod ts { use super::*; - // #[derive(Debug, Serialize)] - // #[serde(rename_all = "camelCase")] - // struct BuildInfoResponse { - // build_specifier: String, - // libs: Vec, - // } - - // #[op2] - // #[serde] - // fn op_build_info(state: &mut OpState) -> BuildInfoResponse { - // eprintln!("op_build_info"); - // let build_specifier = "asset:///bootstrap.ts".to_string(); - // let build_libs = state - // .borrow::>() - // .iter() - // .map(|s| s.to_string()) - // .collect(); - // BuildInfoResponse { - // build_specifier, - // libs: build_libs, - // } - // } - - // #[op2(fast)] - // fn op_is_node_file() -> bool { - // eprintln!("op_is_node_file"); - // false - // } - - // #[op2] - // #[string] - // fn op_script_version( - // _state: &mut OpState, - // #[string] _arg: &str, - // ) -> Result, JsErrorBox> { - // eprintln!("op_script_version"); - // Ok(Some("1".to_string())) - // } - #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct LoadResponse { @@ -75,22 +36,9 @@ mod ts { state: &mut OpState, #[string] load_specifier: &str, ) -> Result { - eprintln!("op_load {}", load_specifier); - // let op_crate_libs = state.borrow::>(); let path_dts = state.borrow::(); let re_asset = lazy_regex::regex!(r"asset:/{3}lib\.(\S+)\.d\.ts"); - // let build_specifier = "asset:///bootstrap.ts"; - - // we need a basic file to send to tsc to warm it up. - // if load_specifier == build_specifier { - // return Ok(LoadResponse { - // data: r#"Deno.writeTextFile("hello.txt", "hello deno!");"#.to_string(), - // version: "1".to_string(), - // // this corresponds to `ts.ScriptKind.TypeScript` - // script_kind: 3, - // }); - // } // specifiers come across as `asset:///lib.{lib_name}.d.ts` and we need to // parse out just the name so we can lookup the asset. @@ -108,32 +56,24 @@ mod ts { }; let data = std::fs::read_to_string(path).map_err(JsErrorBox::from_err)?; - Ok(LoadResponse { + return Ok(LoadResponse { data, version: "1".to_string(), // this corresponds to `ts.ScriptKind.TypeScript` script_kind: 3, - }) - } else { - Err(JsErrorBox::new( - "InvalidSpecifier", - format!("An invalid specifier was requested: {}", load_specifier), - )) + }); } - } else { - Err(JsErrorBox::new( - "InvalidSpecifier", - format!("An invalid specifier was requested: {}", load_specifier), - )) } + + Err(JsErrorBox::new( + "InvalidSpecifier", + format!("An invalid specifier was requested: {}", load_specifier), + )) } deno_core::extension!(deno_tsc, ops = [ - // op_build_info, - // op_is_node_file, op_load, - // op_script_version ], esm_entry_point = "ext:deno_tsc/99_main_compiler.js", esm = [ @@ -290,7 +230,28 @@ mod ts { ) .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(); + + let context = scope.get_current_context(); + let global = context.global(scope); + + let name = v8::String::new(scope, "snapshot").unwrap(); + let snapshot_fn_val = global.get(scope, name.into()).unwrap(); + let snapshot_fn: v8::Local = + snapshot_fn_val.try_into().unwrap(); + let undefined = v8::undefined(scope); + let build_libs = build_libs_.clone(); + let build_libs_v8 = + 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"), @@ -301,25 +262,7 @@ mod ts { path_dts, )], extension_transpiler: None, - with_runtime_cb: Some(Box::new(|rt| { - let scope = &mut rt.handle_scope(); - - let context = scope.get_current_context(); - let global = context.global(scope); - - let name = v8::String::new(scope, "snapshot").unwrap(); - let snapshot_fn_val = global.get(scope, name.into()).unwrap(); - let snapshot_fn: v8::Local = - snapshot_fn_val.try_into().unwrap(); - let undefined = v8::undefined(scope); - let build_libs = build_libs_.clone(); - let build_libs_v8 = - deno_core::serde_v8::to_v8(scope, build_libs).unwrap(); - - snapshot_fn - .call(scope, undefined.into(), &[build_libs_v8]) - .unwrap(); - })), + with_runtime_cb: Some(runtime_cb), skip_op_registration: false, }, None, diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js index f7d8baa3fba5ef..872b9c5479e5a9 100644 --- a/cli/tsc/99_main_compiler.js +++ b/cli/tsc/99_main_compiler.js @@ -22,7 +22,6 @@ import { getAssets, host, setLogDebug, - SOURCE_FILE_CACHE, } from "./97_ts_host.js"; import { serverMainLoop } from "./98_lsp.js"; @@ -39,16 +38,6 @@ const ops = core.ops; /** @type {Map} */ const normalizedToOriginalMap = new Map(); -const SNAPSHOT_COMPILE_OPTIONS = { - esModuleInterop: true, - jsx: ts.JsxEmit.React, - module: ts.ModuleKind.ESNext, - noEmit: true, - strict: true, - target: ts.ScriptTarget.ESNext, - lib: ["lib.deno.window.d.ts"], -}; - /** @type {Array<[string, number]>} */ const stats = []; let statsStart = 0; @@ -246,22 +235,6 @@ globalThis.snapshot = function (libs) { } }; -// this helps ensure as much as possible is in memory that is re-usable -// before the snapshotting is done, which helps unsure fast "startup" for -// subsequent uses of tsc in Deno. -// const TS_SNAPSHOT_PROGRAM = ts.createProgram({ -// rootNames: [buildSpecifier], -// options: SNAPSHOT_COMPILE_OPTIONS, -// host, -// }); -// assert( -// ts.getPreEmitDiagnostics(TS_SNAPSHOT_PROGRAM).length === 0, -// "lib.d.ts files have errors", -// ); - -// remove this now that we don't need it anymore for warming up tsc -// SOURCE_FILE_CACHE.delete(buildSpecifier); - // exposes the functions that are called by `tsc::exec()` when type // checking TypeScript. globalThis.exec = exec; diff --git a/foo.ts b/foo.ts deleted file mode 100644 index c236135c28fed6..00000000000000 --- a/foo.ts +++ /dev/null @@ -1,5 +0,0 @@ -function foo(a: number) { - // -} - -foo("sadf"); From 826847d395a884b3763fe161528c4ae255c9dde1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 24 Jan 2025 02:11:45 +0100 Subject: [PATCH 3/3] revert --- cli/lsp/tsc.rs | 8 +++----- cli/tsc/mod.rs | 37 ++++++++++++++++++++++++------------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index 96efba315c9c87..e1b3691c0aa452 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -4919,14 +4919,12 @@ fn run_tsc_thread( deno_core::extension!(deno_tsc, ops = [ - op_resolve, - op_respond, - + op_is_cancelled, op_is_node_file, op_load, - - op_is_cancelled, op_release, + op_resolve, + op_respond, op_script_names, op_script_version, op_project_version, diff --git a/cli/tsc/mod.rs b/cli/tsc/mod.rs index f358024c64578e..868610cb40a535 100644 --- a/cli/tsc/mod.rs +++ b/cli/tsc/mod.rs @@ -465,23 +465,33 @@ fn op_create_hash_inner(s: &mut OpState, text: &str) -> String { get_hash(text, state.hash_data) } +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct EmitArgs { + /// The text data/contents of the file. + data: String, + /// The _internal_ filename for the file. This will be used to determine how + /// the file is cached and stored. + file_name: String, +} + #[op2(fast)] fn op_emit( state: &mut OpState, #[string] data: String, - #[string] file_name: &str, + #[string] file_name: String, ) -> bool { - op_emit_inner(state, data, file_name) + op_emit_inner(state, EmitArgs { data, file_name }) } #[inline] -fn op_emit_inner(state: &mut OpState, data: String, file_name: &str) -> bool { +fn op_emit_inner(state: &mut OpState, args: EmitArgs) -> bool { let state = state.borrow_mut::(); - match file_name { - "internal:///.tsbuildinfo" => state.maybe_tsbuildinfo = Some(data), + match args.file_name.as_ref() { + "internal:///.tsbuildinfo" => state.maybe_tsbuildinfo = Some(args.data), _ => { if cfg!(debug_assertions) { - panic!("Unhandled emit write: {}", file_name); + panic!("Unhandled emit write: {}", args.file_name); } } } @@ -1097,14 +1107,13 @@ pub fn exec(request: Request) -> Result { deno_core::extension!(deno_cli_tsc, ops = [ - op_resolve, - op_respond, - op_is_node_file, - op_load, - op_create_hash, op_emit, + op_is_node_file, + op_load, op_remap_specifier, + op_resolve, + op_respond, ], options = { request: Request, @@ -1335,8 +1344,10 @@ mod tests { let mut state = setup(None, None, None).await; let actual = op_emit_inner( &mut state, - "some file content".to_string(), - "internal:///.tsbuildinfo", + EmitArgs { + data: "some file content".to_string(), + file_name: "internal:///.tsbuildinfo".to_string(), + }, ); assert!(actual); let state = state.borrow::();