惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Apple Machine Learning Research
Apple Machine Learning Research
aimingoo的专栏
aimingoo的专栏
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 聂微东
Engineering at Meta
Engineering at Meta
N
Netflix TechBlog - Medium
Blog — PlanetScale
Blog — PlanetScale
大猫的无限游戏
大猫的无限游戏
Vercel News
Vercel News
D
DataBreaches.Net
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
WordPress大学
WordPress大学
L
LangChain Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
F
Fortinet All Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
J
Java Code Geeks
Recent Announcements
Recent Announcements
Jina AI
Jina AI
G
Google Developers Blog
腾讯CDC
博客园_首页
博客园 - 【当耐特】

Deno

Deno 2.8 | Deno Claw Patrol: an open-source security firewall for agents | Deno Fresh 2.3: Zero JS by default, View Transitions, and Temporal support | Deno Deno 2.7: Temporal API, Windows ARM, and npm overrides | Deno Build a dinosaur runner game with Deno, pt. 6 | Deno Build a dinosaur runner game with Deno, pt. 5 | Deno Deno Deploy is Generally Available | Deno Introducing Deno Sandbox | Deno Build a dinosaur runner game with Deno, pt. 4 | Deno Build a dinosaur runner game with Deno, pt. 3 | Deno Build a dinosaur runner game with Deno, pt. 2 | Deno React / Next.js Denial-of-Service Vulnerability: Deno Deploy users protected | Deno Deno 2.6: dx is the new npx | Deno Build a dinosaur runner game with Deno, pt. 1 | Deno React Server Functions / Next.js Vulnerability: Deno Deploy users protected | Deno My highlights from the new Deno Deploy | Deno Deno's Other Open Source Projects | Deno How Deno protects against npm exploits | Deno Help Us Raise $200k to Free JavaScript from Oracle | Deno Deno 2.5: Permissions in the config file | Deno Fresh 2.0 Graduates to Beta, Adds Vite Support | Deno Deno 2.4: deno bundle is back | Deno JavaScript™ Trademark Update | Deno What's coming to JavaScript | Deno A brief history of JavaScript | Deno Reports of Deno's Demise Have Been Greatly Exaggerated | Deno An Update on Fresh | Deno How Plaid migrated 100 services to a new database platform 5x faster with Deno | Deno Deno 2.3: Improved deno compile, local npm packages, and more | Deno Add JSR packages with pnpm and Yarn | Deno
Roll your own JavaScript runtime, pt. 3 | Deno
2023-05-04 · via Deno

This post is a continuation of Roll your own JavaScript runtime and Roll your own JavaScript runtime, pt. 2.

Update 2024-09-26: updated the code samples to the latest version of deno_core

We’ve been delighted by the positive response to this series on rolling your own custom JavaScript runtime. One area that some expressed interest in is how to use snapshots to get faster startup times. Snapshots can provide improved performance at a (typically) negligible increase in filesize.

In this blog post, we’ll build on the first and second part by creating a snapshot of runtime.js in a build script, then loading that snapshot in main.rs to speed up start time for our custom runtime.

Screenshot of the video walkthrough from Andy and Leo

Watch the video demo or view source code here.

Getting setup

If you followed the first and second blog post, your project should have three files:

  • example.ts: the JavaScript file we intend to execute with the custom runtime
  • src/main.rs: the asynchronous Rust function that creates an instance of JsRuntime, which is responsible for JavaScript execution
  • src/runtime.js: the runtime interface that defines and provides the API that will interop with the JsRuntime from main.rs

Let’s write a build.rs file that will create a snapshot of the custom runtime, runjs.

Creating a snapshot in build.rs

Before create a build.rs file, let’s first add deno_core as a build dependency in Cargo.toml:

[package]
name = "runjs"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/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"

Next, let’s create a build.rs file in the root of your project. In this file, we’ll have to do the following steps:

  • Create a small extension of src/runtime.js
  • Build a file path to the snapshot
  • Create the snapshot

Putting the above steps into code, your build.rs script should look like:

use deno_core::extension;
use std::env;
use std::path::PathBuf;

fn main() {
  extension!(
    
    runjs,
    
    esm_entry_point = "ext:runjs/src/runtime.js",
    
    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();
}

The main function is create_snapshot, which accepts several options. Let’s go over them in the next section.

Diving into CreateSnapshotOptions

The create_snapshot function is a nice abstraction layer that uses an options struct to determine how the snapshot is created. We will use the following options to configure it:

  • cargo_manifest_dir: the directory in which Cargo will be compiling everything into. We define the snapshot path by resolving OUT_DIR and RUNJS_SNAPSHOT.bin.
  • extensions: extensions to include within the generated snapshot. we pass the runjs extension, which is built from src/runtime.js.

Loading the snapshot in main.rs

Currently, the main.rs file’s run_js function loads the runjs extension. We’ll modify this function to instead load the snapshot we created in 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
}

We’ll remove the esm and esm_entry_point declarations, since that was moved to build.rs script. Then, we’ll add a line to load the snapshot.

Finally, to load the snapshot, we’ll add startup_snapshot in RuntimeOptions that points to the RUNTIME_SNAPSHOT, which is defined above run_js as a static slice of bytes of the snapshot we created in build.rs.

And that’s it! Let’s try running with:

cargo run -- example.ts

It should work!

What’s next?

Snapshotting is an excellent tool to help improve startup speeds for a custom runtime. This is an extremely simple example, but we hope that it sheds some light into how Deno uses snapshots to optimize performance.

Through this series, we’ve shown how to build your own custom JavaScript runtime, add APIs like fetch, and now speed up startup times via snapshotting. We love hearing from you so if there’s anything you want us to cover, please let us know on Twitter, YouTube, or Discord.

Don’t miss any updates — follow us on Twitter.