跳至主要内容
Deno 2 终于发布啦 🎉️
了解更多
Roll your own javascript runtime, pt3.

自己动手打造 JavaScript 运行时,第 3 部分

这篇文章是 自己动手打造 JavaScript 运行时自己动手打造 JavaScript 运行时,第 2 部分 的延续。

更新于 2024-09-26:已将代码示例更新到最新版本的 deno_core

我们对这个关于自己动手打造自定义 JavaScript 运行时的系列文章的积极反响感到高兴。 有些人表示有兴趣 了解如何使用快照来获得更快的启动时间。 快照可以在文件大小(通常)略微增加的情况下提供更好的性能。

在这篇博文中,我们将基于 第一部分第二部分,在构建脚本中创建 runtime.js 的快照,然后在 main.rs 中加载该快照,以加快自定义运行时的启动时间。

Screenshot of the video walkthrough from Andy and Leo

观看视频演示在这里查看源代码

开始设置

如果您按照 第一篇第二篇博客文章 的步骤操作,您的项目应该包含三个文件

  • 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_DIRRUNJS_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
}

我们将删除 esmesm_entry_point 声明,因为它们已移至 build.rs 脚本。 然后,我们将添加一行来加载快照。

最后,要加载快照,我们将添加 startup_snapshotRuntimeOptions 中,它指向 RUNTIME_SNAPSHOTRUNTIME_SNAPSHOTrun_js 之上定义为我们在 build.rs 中创建的快照的静态字节切片。

就是这样! 让我们尝试运行以下命令

cargo run -- example.ts

它应该可以正常工作!

接下来做什么?

快照是帮助提高自定义运行时启动速度的绝佳工具。 这是一个非常简单的示例,但我们希望它能帮助您了解 Deno 如何使用快照来优化性能。

通过这个系列,我们展示了如何构建您自己的自定义 JavaScript 运行时,添加像 fetch 这样的 API,现在通过快照来加快启动时间。 我们乐于收到您的反馈,如果您想让我们涵盖任何内容,请在 TwitterYouTubeDiscord 上告诉我们。

不要错过任何更新 - 在 Twitter 上关注我们。