自己动手构建 JavaScript 运行时,第三部分
本文是以下文章的延续:构建你自己的 JavaScript 运行时 和 构建你自己的 JavaScript 运行时,第二部分。
更新于 2024年9月26日:代码示例已更新至最新版本的 deno_core
我们很高兴看到大家对“构建自定义 JavaScript 运行时”系列文章的积极反响。其中一个大家感兴趣的方面是如何使用快照来加快启动时间。快照可以提供改进的性能,同时(通常)文件大小的增加可以忽略不计。
在这篇博文中,我们将基于第一部分和第二部分来构建,通过在构建脚本中创建 runtime.js
的快照,然后在 main.rs
中加载该快照,以加快我们自定义运行时的启动时间。
开始设置
example.ts
:我们打算用自定义运行时执行的 JavaScript 文件src/main.rs
:创建JsRuntime
实例的异步 Rust 函数,它负责 JavaScript 的执行src/runtime.js
:运行时接口,定义并提供与main.rs
中的JsRuntime
进行互操作的 API
我们来编写一个 build.rs
文件,它将创建自定义运行时 runjs
的快照。
build.rs
中创建快照
在 在创建 build.rs
文件之前,我们首先在 Cargo.toml
中添加 deno_core
作为构建依赖项。
[package]
name = "runjs"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.net.cn/cargo/reference/manifest.html
[dependencies]
deno_ast = { version = "0.42", features = ["transpiling"] }
deno_core = "0.311"
reqwest = "0.12"
tokio = { version = "1.40", features = ["full"] }
+ [build-dependencies]
+ deno_core = "0.311"
接下来,在项目的根目录中创建一个 build.rs
文件。在此文件中,我们需要执行以下步骤:
- 创建一个小型扩展,基于
src/runtime.js
- 构建快照的文件路径
- 创建快照
将以上步骤转化为代码,您的 build.rs
脚本应如下所示:
use deno_core::extension;
use std::env;
use std::path::PathBuf;
fn main() {
extension!(
// extension name
runjs,
// list of all JS files in the extension
esm_entry_point = "ext:runjs/src/runtime.js",
// the entrypoint to our extension
esm = ["src/runtime.js"]
);
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let snapshot_path = out_dir.join("RUNJS_SNAPSHOT.bin");
let snapshot = deno_core::snapshot::create_snapshot(
deno_core::snapshot::CreateSnapshotOptions {
cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"),
startup_snapshot: None,
skip_op_registration: false,
extensions: vec![runjs::init_ops_and_esm()],
with_runtime_cb: None,
extension_transpiler: None,
},
None,
)
.unwrap();
std::fs::write(snapshot_path, snapshot.output).unwrap();
}
主函数是 create_snapshot
,它接受几个选项。我们将在下一节中介绍它们。
CreateSnapshotOptions
深入了解 create_snapshot
函数是一个很好的抽象层,它使用一个选项结构来确定快照的创建方式。我们将使用以下选项来配置它:
cargo_manifest_dir
:Cargo 将所有内容编译到的目录。我们通过解析OUT_DIR
和RUNJS_SNAPSHOT.bin
来定义快照路径。extensions
:要在生成的快照中包含的扩展。我们传入runjs
扩展,它由src/runtime.js
构建而成。
main.rs
中加载快照
在 目前,main.rs
文件的 run_js
函数加载 runjs
扩展。我们将修改此函数,改为加载我们在 build.rs
中创建的快照。
+ use deno_core::Snapshot;
// Other stuff…
+ static RUNTIME_SNAPSHOT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/RUNJS_SNAPSHOT.bin"));
extension!(
runjs,
ops = [
op_read_file,
op_write_file,
op_remove_file,
op_fetch,
],
- esm_entry_point = "ext:runjs/runtime.js",
- esm = [dir "src", "runtime.js"],
)
async fn run_js(file_path: &str) -> Result<(), AnyError> {
let main_module = deno_core::resolve_path(file_path)?;
let mut js_runtime = deno_core::JsRuntime::new(deno_core::RuntimeOptions {
module_loader: Some(Rc::new(TsModuleLoader)),
+ startup_snapshot: Some(Snapshot::Static(RUNTIME_SNAPSHOT)),
- extensions: vec![runjs::init_ops_and_esm()],
+ extensions: vec![runjs::init_ops()],
..Default::default()
});
let mod_id = js_runtime.load_main_module(&main_module, None).await?;
let result = js_runtime.mod_evaluate(mod_id);
js_runtime
.run_event_loop(Default::default())
.await?;
result.await
}
我们将删除 esm
和 esm_entry_point
声明,因为它们已移至 build.rs
脚本。然后,我们将添加一行代码来加载快照。
最后,为了加载快照,我们将在 RuntimeOptions
中添加 startup_snapshot
,它指向 RUNTIME_SNAPSHOT
,该 RUNTIME_SNAPSHOT
在 run_js
上方被定义为我们在 build.rs
中创建的快照的静态字节切片。
就这样!让我们尝试运行:
cargo run -- example.ts
它应该能正常工作!
接下来是什么?
快照是帮助提高自定义运行时启动速度的绝佳工具。这是一个非常简单的例子,但我们希望它能阐明 Deno 如何使用快照来优化性能。
通过本系列文章,我们展示了如何构建您自己的自定义 JavaScript 运行时,添加 fetch
等 API,以及现在如何通过快照来加快启动时间。我们乐于听取您的意见,如果您希望我们涵盖任何主题,请通过以下方式告知我们:Twitter、YouTube 或 Discord。
不要错过任何更新 — 在 Twitter 上关注我们。