Roll your personal JavaScript runtime, pt. 3

This publish is a continuation of
Roll your own JavaScript runtime and
Roll your own JavaScript runtime, pt. 2.
We have been delighted by the optimistic response to this sequence on rolling your personal
customized JavaScript runtime. One space that
some expressed interest in is
tips on how to use snapshots to get quicker startup occasions. Snapshots can present improved
efficiency at a (sometimes) negligible improve in filesize.
On this weblog publish, we’ll construct on the
first and
second part by making a snapshot
of runtime.js
in a construct script, then loading that snapshot in predominant.rs
to
pace up begin time for our customized runtime.
Watch the video demo or
view source code here.
Getting setup
For those who adopted the first and
second blog post, your challenge
ought to have three recordsdata:
instance.ts
: the JavaScript file we intend to execute with the customized runtimesrc/predominant.rs
: the asynchronous Rust perform that creates an occasion of
JsRuntime
,
which is chargeable for JavaScript executionsrc/runtime.js
: the runtime interface that defines and supplies the API that
will interop with theJsRuntime
frompredominant.rs
Let’s write a construct.rs
file that can create a snapshot of the customized runtime,
runjs
.
Making a snapshot in construct.rs
Earlier than create a construct.rs
file, let’s first add deno_core
as a construct
dependency in Cargo.toml
:
[package]
title = "runjs"
model = "0.1.0"
version = "2021"
# See extra keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
deno_ast = { model = "0.22.0", options = ["transpiling"] }
deno_core = "0.174.0"
reqwest = "0.11.14"
tokio = { model = "1.25.0", options = ["full"] }
+ [build-dependencies]
+ deno_core = "0.174.0"
Subsequent, let’s create a construct.rs
file within the root of your challenge. On this file,
we’ll must do the next steps:
- Create a small extension of
src/runtime.js
- Construct a file path to the snapshot
- Create the snapshot
Placing the above steps into code, your construct.rs
script ought to seem like:
use std::env;
use std::path::PathBuf;
use deno_core::{Extension, include_js_files};
fn predominant() {
let runjs_extension = Extensions::builder("runjs")
.esm(include_js_files!(
"src/runtime.js",
))
.construct();
let o = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let snapshot_path = o.be a part of("RUNJS_SNAPSHOT.bin");
deno_core::snapshot_util::create_snapshot(deno_core::snapshot_util::CreateSnapshotOptions {
cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"),
snapshot_path: snapshot_path,
startup_snapshot: None,
extensions: vec!(runjs_extension),
compression_cb, None,
Snapshot_module_load_cb: None,
})
}
The principle perform is create_snapshot
, which accepts a number of choices. Let’s go
over them within the subsequent part.
Diving into CreateSnapshotOptions
The create_snapshot
perform is a pleasant abstraction layer that makes use of an choices
struct to find out how the snapshot is created. Let’s go over the totally different
fields which can be obtainable in CreateSnapshotOptions
.
pub struct CreateSnapshotOptions {
pub cargo_manifest_dir: &'static str,
pub snapshot_path: PathBuf,
pub startup_snapshot: Possibility<Snapshot>,
pub extensions: Vec<Extensions>,
pub compression_cb: Possibility<Field<CompressionCb>>,
pub snapshot_module_load_cb: Possibility<ExtModuleLoaderCb>,
}
cargo_manifest_dir
: the listing by which Cargo will likely be compiling
every part into. This could at all times be theCARGO_MANIFEST_DIR
setting
variable.snapshot_path
: the trail the place we write the snapshot to.startup_snapshot
: you may cross in a snapshot, if you wish to construct a
snapshot from one other snapshot.extensions
: any extensions that you just want to add on high of the snapshotcompression_cb
: you may set which compression methodology to make use of to additional
cut back the filesize.snapshot_module_load_cb
: you may cross a module loader that can rework
recordsdata included within the snapshot. For instance, you may transpile TypeScript to
JavaScript.
On this instance, we solely use:
snapshot_path
: we outline the snapshot path by resolvingOUT_DIR
and
RUNJS_SNAPSHOT.bin
.extensions
: we cross therunjs_extension
, which is constructed from
src/runtime.js
.
Loading the snapshot in predominant.rs
Presently, the predominant.rs
file’s run_js
perform masses the runjs_extension
.
We’ll modify this perform to as a substitute load the snapshot we created in
construct.rs
:
- use deno_core::include_js_files;
+ use deno_core::Snapshot;
// Different stuff…
+ static RUNTIME_SNAPSHOT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/RUNJS_SNAPSHOT.bin"));
async fn run_js(file_path: &str) -> End result<(), AnyError> {
let main_module = deno_core::resolve_path(file_path)?;
let runjs_extension = Extension::builder("runjs")
- .esm(include_js_files!(
- "runtime.js",
- ))
.ops(vec![
op_read_file::decl(),
op_write_file::decl(),
op_remove_file::decl(),
op_fetch::decl(),
op_set_timeout::decl(),
])
.construct();
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_extension],
..Default::default()
});
let mod_id = js_runtime.load_main_module(&main_module, None).await?;
let consequence = js_runtime.mod_evaluate(mod_id);
js_runtime.run_event_loop(false).await?;
consequence.await?
}
We’ll take away the .esm()
perform, since that was moved to construct.rs
script.
Then, we’ll add a line to load the snapshot.
Lastly, to load the snapshot, we’ll add startup_snapshot
in RuntimeOptions
that factors to the RUNTIME_SNAPSHOT
, which is outlined above run_js
as a
static slice of bytes of the snapshot we created in construct.rs
.
And that is it! Let’s attempt working with:
cargo run -- instance.ts
It ought to work!
What’s subsequent?
Snapshotting is a wonderful software to assist enhance startup speeds for a customized
runtime. That is an very simple instance, however we hope that it sheds some
mild into how Deno makes use of snapshots to optimize efficiency.
By this sequence, we have proven tips on how to construct your personal customized JavaScript
runtime, add APIs like fetch
, and now pace up startup occasions by way of snapshotting.
We love listening to from you so if there’s something you need us to cowl, please let
us know on Twitter,
YouTube, or
Discord.
Do not miss any updates — observe us on
Twitter.