Skip to content

Commit ad50c0d

Browse files
authored
refactor(tsc): remove TS program creation during snapshotting (#27797)
This commit refactors how a snapshot is created for the TypeScript compiler. Instead of having 4 ops, only a single op ("op_load") is left. This is achieved by not creating a "ts.Program" during snapshotting, that during benchmarking doesn't provide much benefit. This greatly simplifies build script for the TS snapshot and opens up way to simplify it even further in follow up PRs.
1 parent 886dbe6 commit ad50c0d

File tree

2 files changed

+57
-111
lines changed

2 files changed

+57
-111
lines changed

cli/build.rs

+38-63
Original file line numberDiff line numberDiff line change
@@ -13,48 +13,13 @@ mod ts {
1313
use std::path::PathBuf;
1414

1515
use deno_core::op2;
16+
use deno_core::v8;
1617
use deno_core::OpState;
1718
use deno_error::JsErrorBox;
1819
use serde::Serialize;
1920

2021
use super::*;
2122

22-
#[derive(Debug, Serialize)]
23-
#[serde(rename_all = "camelCase")]
24-
struct BuildInfoResponse {
25-
build_specifier: String,
26-
libs: Vec<String>,
27-
}
28-
29-
#[op2]
30-
#[serde]
31-
fn op_build_info(state: &mut OpState) -> BuildInfoResponse {
32-
let build_specifier = "asset:///bootstrap.ts".to_string();
33-
let build_libs = state
34-
.borrow::<Vec<&str>>()
35-
.iter()
36-
.map(|s| s.to_string())
37-
.collect();
38-
BuildInfoResponse {
39-
build_specifier,
40-
libs: build_libs,
41-
}
42-
}
43-
44-
#[op2(fast)]
45-
fn op_is_node_file() -> bool {
46-
false
47-
}
48-
49-
#[op2]
50-
#[string]
51-
fn op_script_version(
52-
_state: &mut OpState,
53-
#[string] _arg: &str,
54-
) -> Result<Option<String>, JsErrorBox> {
55-
Ok(Some("1".to_string()))
56-
}
57-
5823
#[derive(Debug, Serialize)]
5924
#[serde(rename_all = "camelCase")]
6025
struct LoadResponse {
@@ -74,19 +39,10 @@ mod ts {
7439
let op_crate_libs = state.borrow::<HashMap<&str, PathBuf>>();
7540
let path_dts = state.borrow::<PathBuf>();
7641
let re_asset = lazy_regex::regex!(r"asset:/{3}lib\.(\S+)\.d\.ts");
77-
let build_specifier = "asset:///bootstrap.ts";
78-
79-
// we need a basic file to send to tsc to warm it up.
80-
if load_specifier == build_specifier {
81-
Ok(LoadResponse {
82-
data: r#"Deno.writeTextFile("hello.txt", "hello deno!");"#.to_string(),
83-
version: "1".to_string(),
84-
// this corresponds to `ts.ScriptKind.TypeScript`
85-
script_kind: 3,
86-
})
87-
// specifiers come across as `asset:///lib.{lib_name}.d.ts` and we need to
88-
// parse out just the name so we can lookup the asset.
89-
} else if let Some(caps) = re_asset.captures(load_specifier) {
42+
43+
// specifiers come across as `asset:///lib.{lib_name}.d.ts` and we need to
44+
// parse out just the name so we can lookup the asset.
45+
if let Some(caps) = re_asset.captures(load_specifier) {
9046
if let Some(lib) = caps.get(1).map(|m| m.as_str()) {
9147
// if it comes from an op crate, we were supplied with the path to the
9248
// file.
@@ -100,28 +56,25 @@ mod ts {
10056
};
10157
let data =
10258
std::fs::read_to_string(path).map_err(JsErrorBox::from_err)?;
103-
Ok(LoadResponse {
59+
return Ok(LoadResponse {
10460
data,
10561
version: "1".to_string(),
10662
// this corresponds to `ts.ScriptKind.TypeScript`
10763
script_kind: 3,
108-
})
109-
} else {
110-
Err(JsErrorBox::new(
111-
"InvalidSpecifier",
112-
format!("An invalid specifier was requested: {}", load_specifier),
113-
))
64+
});
11465
}
115-
} else {
116-
Err(JsErrorBox::new(
117-
"InvalidSpecifier",
118-
format!("An invalid specifier was requested: {}", load_specifier),
119-
))
12066
}
67+
68+
Err(JsErrorBox::new(
69+
"InvalidSpecifier",
70+
format!("An invalid specifier was requested: {}", load_specifier),
71+
))
12172
}
12273

12374
deno_core::extension!(deno_tsc,
124-
ops = [op_build_info, op_is_node_file, op_load, op_script_version],
75+
ops = [
76+
op_load,
77+
],
12578
esm_entry_point = "ext:deno_tsc/99_main_compiler.js",
12679
esm = [
12780
dir "tsc",
@@ -277,6 +230,28 @@ mod ts {
277230
)
278231
.unwrap();
279232

233+
// Leak to satisfy type-checker. It's okay since it's only run once for a build script.
234+
let build_libs_ = Box::leak(Box::new(build_libs.clone()));
235+
let runtime_cb = Box::new(|rt: &mut deno_core::JsRuntimeForSnapshot| {
236+
let scope = &mut rt.handle_scope();
237+
238+
let context = scope.get_current_context();
239+
let global = context.global(scope);
240+
241+
let name = v8::String::new(scope, "snapshot").unwrap();
242+
let snapshot_fn_val = global.get(scope, name.into()).unwrap();
243+
let snapshot_fn: v8::Local<v8::Function> =
244+
snapshot_fn_val.try_into().unwrap();
245+
let undefined = v8::undefined(scope);
246+
let build_libs = build_libs_.clone();
247+
let build_libs_v8 =
248+
deno_core::serde_v8::to_v8(scope, build_libs).unwrap();
249+
250+
snapshot_fn
251+
.call(scope, undefined.into(), &[build_libs_v8])
252+
.unwrap();
253+
});
254+
280255
let output = create_snapshot(
281256
CreateSnapshotOptions {
282257
cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"),
@@ -287,7 +262,7 @@ mod ts {
287262
path_dts,
288263
)],
289264
extension_transpiler: None,
290-
with_runtime_cb: None,
265+
with_runtime_cb: Some(runtime_cb),
291266
skip_op_registration: false,
292267
},
293268
None,

cli/tsc/99_main_compiler.js

+19-48
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
getAssets,
2323
host,
2424
setLogDebug,
25-
SOURCE_FILE_CACHE,
2625
} from "./97_ts_host.js";
2726
import { serverMainLoop } from "./98_lsp.js";
2827

@@ -39,16 +38,6 @@ const ops = core.ops;
3938
/** @type {Map<string, string>} */
4039
const normalizedToOriginalMap = new Map();
4140

42-
const SNAPSHOT_COMPILE_OPTIONS = {
43-
esModuleInterop: true,
44-
jsx: ts.JsxEmit.React,
45-
module: ts.ModuleKind.ESNext,
46-
noEmit: true,
47-
strict: true,
48-
target: ts.ScriptTarget.ESNext,
49-
lib: ["lib.deno.window.d.ts"],
50-
};
51-
5241
/** @type {Array<[string, number]>} */
5342
const stats = [];
5443
let statsStart = 0;
@@ -225,44 +214,26 @@ function exec({ config, debug: debugFlag, rootNames, localOnly }) {
225214
debug("<<< exec stop");
226215
}
227216

228-
// A build time only op that provides some setup information that is used to
229-
// ensure the snapshot is setup properly.
230-
/** @type {{ buildSpecifier: string; libs: string[]; nodeBuiltInModuleNames: string[] }} */
231-
const { buildSpecifier, libs } = ops.op_build_info();
232-
233-
for (const lib of libs) {
234-
const specifier = `lib.${lib}.d.ts`;
235-
// we are using internal APIs here to "inject" our custom libraries into
236-
// tsc, so things like `"lib": [ "deno.ns" ]` are supported.
237-
if (!ts.libs.includes(lib)) {
238-
ts.libs.push(lib);
239-
ts.libMap.set(lib, `lib.${lib}.d.ts`);
217+
globalThis.snapshot = function (libs) {
218+
for (const lib of libs) {
219+
const specifier = `lib.${lib}.d.ts`;
220+
// we are using internal APIs here to "inject" our custom libraries into
221+
// tsc, so things like `"lib": [ "deno.ns" ]` are supported.
222+
if (!ts.libs.includes(lib)) {
223+
ts.libs.push(lib);
224+
ts.libMap.set(lib, `lib.${lib}.d.ts`);
225+
}
226+
// we are caching in memory common type libraries that will be re-used by
227+
// tsc on when the snapshot is restored
228+
assert(
229+
!!host.getSourceFile(
230+
`${ASSETS_URL_PREFIX}${specifier}`,
231+
ts.ScriptTarget.ESNext,
232+
),
233+
`failed to load '${ASSETS_URL_PREFIX}${specifier}'`,
234+
);
240235
}
241-
// we are caching in memory common type libraries that will be re-used by
242-
// tsc on when the snapshot is restored
243-
assert(
244-
!!host.getSourceFile(
245-
`${ASSETS_URL_PREFIX}${specifier}`,
246-
ts.ScriptTarget.ESNext,
247-
),
248-
`failed to load '${ASSETS_URL_PREFIX}${specifier}'`,
249-
);
250-
}
251-
// this helps ensure as much as possible is in memory that is re-usable
252-
// before the snapshotting is done, which helps unsure fast "startup" for
253-
// subsequent uses of tsc in Deno.
254-
const TS_SNAPSHOT_PROGRAM = ts.createProgram({
255-
rootNames: [buildSpecifier],
256-
options: SNAPSHOT_COMPILE_OPTIONS,
257-
host,
258-
});
259-
assert(
260-
ts.getPreEmitDiagnostics(TS_SNAPSHOT_PROGRAM).length === 0,
261-
"lib.d.ts files have errors",
262-
);
263-
264-
// remove this now that we don't need it anymore for warming up tsc
265-
SOURCE_FILE_CACHE.delete(buildSpecifier);
236+
};
266237

267238
// exposes the functions that are called by `tsc::exec()` when type
268239
// checking TypeScript.

0 commit comments

Comments
 (0)