mirror of
https://github.com/denoland/deno.git
synced 2025-02-08 07:16:56 -05:00
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.
This commit is contained in:
parent
886dbe6be1
commit
ad50c0df34
2 changed files with 56 additions and 110 deletions
99
cli/build.rs
99
cli/build.rs
|
@ -13,48 +13,13 @@ mod ts {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use deno_core::op2;
|
use deno_core::op2;
|
||||||
|
use deno_core::v8;
|
||||||
use deno_core::OpState;
|
use deno_core::OpState;
|
||||||
use deno_error::JsErrorBox;
|
use deno_error::JsErrorBox;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct BuildInfoResponse {
|
|
||||||
build_specifier: String,
|
|
||||||
libs: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[op2]
|
|
||||||
#[serde]
|
|
||||||
fn op_build_info(state: &mut OpState) -> BuildInfoResponse {
|
|
||||||
let build_specifier = "asset:///bootstrap.ts".to_string();
|
|
||||||
let build_libs = state
|
|
||||||
.borrow::<Vec<&str>>()
|
|
||||||
.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<Option<String>, JsErrorBox> {
|
|
||||||
Ok(Some("1".to_string()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct LoadResponse {
|
struct LoadResponse {
|
||||||
|
@ -74,19 +39,10 @@ mod ts {
|
||||||
let op_crate_libs = state.borrow::<HashMap<&str, PathBuf>>();
|
let op_crate_libs = state.borrow::<HashMap<&str, PathBuf>>();
|
||||||
let path_dts = state.borrow::<PathBuf>();
|
let path_dts = state.borrow::<PathBuf>();
|
||||||
let re_asset = lazy_regex::regex!(r"asset:/{3}lib\.(\S+)\.d\.ts");
|
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.
|
// specifiers come across as `asset:///lib.{lib_name}.d.ts` and we need to
|
||||||
if load_specifier == build_specifier {
|
// parse out just the name so we can lookup the asset.
|
||||||
Ok(LoadResponse {
|
if let Some(caps) = re_asset.captures(load_specifier) {
|
||||||
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 let Some(lib) = caps.get(1).map(|m| m.as_str()) {
|
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
|
// if it comes from an op crate, we were supplied with the path to the
|
||||||
// file.
|
// file.
|
||||||
|
@ -100,28 +56,25 @@ mod ts {
|
||||||
};
|
};
|
||||||
let data =
|
let data =
|
||||||
std::fs::read_to_string(path).map_err(JsErrorBox::from_err)?;
|
std::fs::read_to_string(path).map_err(JsErrorBox::from_err)?;
|
||||||
Ok(LoadResponse {
|
return Ok(LoadResponse {
|
||||||
data,
|
data,
|
||||||
version: "1".to_string(),
|
version: "1".to_string(),
|
||||||
// this corresponds to `ts.ScriptKind.TypeScript`
|
// this corresponds to `ts.ScriptKind.TypeScript`
|
||||||
script_kind: 3,
|
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,
|
deno_core::extension!(deno_tsc,
|
||||||
ops = [op_build_info, op_is_node_file, op_load, op_script_version],
|
ops = [
|
||||||
|
op_load,
|
||||||
|
],
|
||||||
esm_entry_point = "ext:deno_tsc/99_main_compiler.js",
|
esm_entry_point = "ext:deno_tsc/99_main_compiler.js",
|
||||||
esm = [
|
esm = [
|
||||||
dir "tsc",
|
dir "tsc",
|
||||||
|
@ -277,6 +230,28 @@ mod ts {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.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<v8::Function> =
|
||||||
|
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(
|
let output = create_snapshot(
|
||||||
CreateSnapshotOptions {
|
CreateSnapshotOptions {
|
||||||
cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"),
|
cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"),
|
||||||
|
@ -287,7 +262,7 @@ mod ts {
|
||||||
path_dts,
|
path_dts,
|
||||||
)],
|
)],
|
||||||
extension_transpiler: None,
|
extension_transpiler: None,
|
||||||
with_runtime_cb: None,
|
with_runtime_cb: Some(runtime_cb),
|
||||||
skip_op_registration: false,
|
skip_op_registration: false,
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
|
|
|
@ -22,7 +22,6 @@ import {
|
||||||
getAssets,
|
getAssets,
|
||||||
host,
|
host,
|
||||||
setLogDebug,
|
setLogDebug,
|
||||||
SOURCE_FILE_CACHE,
|
|
||||||
} from "./97_ts_host.js";
|
} from "./97_ts_host.js";
|
||||||
import { serverMainLoop } from "./98_lsp.js";
|
import { serverMainLoop } from "./98_lsp.js";
|
||||||
|
|
||||||
|
@ -39,16 +38,6 @@ const ops = core.ops;
|
||||||
/** @type {Map<string, string>} */
|
/** @type {Map<string, string>} */
|
||||||
const normalizedToOriginalMap = new 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]>} */
|
/** @type {Array<[string, number]>} */
|
||||||
const stats = [];
|
const stats = [];
|
||||||
let statsStart = 0;
|
let statsStart = 0;
|
||||||
|
@ -225,44 +214,26 @@ function exec({ config, debug: debugFlag, rootNames, localOnly }) {
|
||||||
debug("<<< exec stop");
|
debug("<<< exec stop");
|
||||||
}
|
}
|
||||||
|
|
||||||
// A build time only op that provides some setup information that is used to
|
globalThis.snapshot = function (libs) {
|
||||||
// ensure the snapshot is setup properly.
|
for (const lib of libs) {
|
||||||
/** @type {{ buildSpecifier: string; libs: string[]; nodeBuiltInModuleNames: string[] }} */
|
const specifier = `lib.${lib}.d.ts`;
|
||||||
const { buildSpecifier, libs } = ops.op_build_info();
|
// we are using internal APIs here to "inject" our custom libraries into
|
||||||
|
// tsc, so things like `"lib": [ "deno.ns" ]` are supported.
|
||||||
for (const lib of libs) {
|
if (!ts.libs.includes(lib)) {
|
||||||
const specifier = `lib.${lib}.d.ts`;
|
ts.libs.push(lib);
|
||||||
// we are using internal APIs here to "inject" our custom libraries into
|
ts.libMap.set(lib, `lib.${lib}.d.ts`);
|
||||||
// tsc, so things like `"lib": [ "deno.ns" ]` are supported.
|
}
|
||||||
if (!ts.libs.includes(lib)) {
|
// we are caching in memory common type libraries that will be re-used by
|
||||||
ts.libs.push(lib);
|
// tsc on when the snapshot is restored
|
||||||
ts.libMap.set(lib, `lib.${lib}.d.ts`);
|
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",
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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
|
// exposes the functions that are called by `tsc::exec()` when type
|
||||||
// checking TypeScript.
|
// checking TypeScript.
|
||||||
|
|
Loading…
Add table
Reference in a new issue