Skip to content
This repository was archived by the owner on Apr 2, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
resolver = "2"
members = [
"core",
"core/examples/snapshot",
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the only way I found to get this example crate to run build.rs, which is required for generating the V8 Snapshot. But I'd be happy to learn that there's a better way.

"dcore",
"ops",
"ops/compile_test_runner",
Expand Down
19 changes: 19 additions & 0 deletions core/examples/snapshot/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
# Note: Since Cargo "example" targets don't discover/use `build.rs` files, this
# example is a member of the root `deno_core` workspace. That means it will
# compile with `cargo build` in the root, so that this example/documentation
# stays in-sync with development.

[package]
name = "build-your-own-js-snapshot"
version = "0.1.0"
edition = "2021"
build = "build.rs"


[dependencies]
deno_core.workspace = true
tokio.workspace = true

[build-dependencies]
deno_core.workspace = true
27 changes: 27 additions & 0 deletions core/examples/snapshot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Snapshot Example

This example roughly follows the blog post
[Roll Your Own JavaScript Runtime: Part 3][blog] to create a `JsRuntime` with an
embedded startup snapshot.

That blog post and the two that preceded it were no longer accurate. By
including this example in the repository, it will continually be built, so it
will hopefully stay up-to-date.

Comment thread
devsnek marked this conversation as resolved.
## Differences

Differences from those blog posts:

- The `create_snapshot()` API has changed in various ways.
- New API features for extensions:
- `#[op2]` ([read more][op2])
- `extension!(...)` macro replaces `Extension::builder()`
- ESM-based extensions.

Missing features vs. those blog posts:

- Does not implement [TsModuleLoader], to keep this example more concise.

[blog]: https://deno.com/blog/roll-your-own-javascript-runtime-pt3#creating-a-snapshot-in-buildrs
[op2]: https://github.com/denoland/deno_core/tree/main/ops/op2#readme
[TsModuleLoader]: https://deno.com/blog/roll-your-own-javascript-runtime-pt2#supporting-typescript
42 changes: 42 additions & 0 deletions core/examples/snapshot/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use deno_core::{
extension,
snapshot::{create_snapshot, CreateSnapshotOptions},
};
use std::path::PathBuf;
use std::{env, fs};

fn main() {
extension!(
runjs_extension,
// Must specify an entrypoint so that our module gets loaded while snapshotting:
esm_entry_point = "my:runtime",
esm = [
dir "src",
"my:runtime" = "runtime.js",
],
);

let options = CreateSnapshotOptions {
cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"),
startup_snapshot: None,
extensions: vec![runjs_extension::init_ops_and_esm()],
with_runtime_cb: None,
skip_op_registration: false,
extension_transpiler: None,
};
let warmup_script = None;

let snapshot =
create_snapshot(options, warmup_script).expect("Error creating snapshot");

// Save the snapshot for use by our source code:
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let file_path = out_dir.join("RUNJS_SNAPSHOT.bin");
fs::write(file_path, snapshot.output).expect("Failed to write snapshot");

// Let cargo know that builds depend on these files:
for path in snapshot.files_loaded_during_snapshot {
println!("cargo:rerun-if-changed={}", path.display());
}
}
6 changes: 6 additions & 0 deletions core/examples/snapshot/example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// Run this script with `cargo run`.

import { callRust } from "my:runtime";

callRust("Hello from example.js");
47 changes: 47 additions & 0 deletions core/examples/snapshot/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::{env::current_dir, rc::Rc};

use deno_core::{
error::AnyError, extension, op2, FsModuleLoader, JsRuntime,
PollEventLoopOptions, RuntimeOptions,
};

fn main() {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
if let Err(error) = runtime.block_on(run_js("./example.js")) {
eprintln!("error: {}", error);
}
}

#[op2(fast)]
fn op_call_rust(#[string] value: String) {
println!("Received this value from JS: {value}");
}

extension!(runjs_extension, ops = [op_call_rust,],);

async fn run_js(file_path: &str) -> Result<(), AnyError> {
let cwd = current_dir()?;
let main_module = deno_core::resolve_path(file_path, &cwd)?;

let mut js_runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(Rc::new(FsModuleLoader)),
startup_snapshot: Some(RUNTIME_SNAPSHOT),
extensions: vec![runjs_extension::init_ops()],
..Default::default()
});

let mod_id = js_runtime.load_main_es_module(&main_module).await?;
let result = js_runtime.mod_evaluate(mod_id);
js_runtime
.run_event_loop(PollEventLoopOptions::default())
.await?;
result.await
}

// Load the snapshot generated by build.rs:
static RUNTIME_SNAPSHOT: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/RUNJS_SNAPSHOT.bin"));
10 changes: 10 additions & 0 deletions core/examples/snapshot/src/runtime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
/**
* This module provides the JavaScript interface atop calls to the Rust ops.
*/

// Minimal example, just passes arguments through to Rust:
export function callRust(stringValue) {
const { op_call_rust } = Deno.core.ops;
op_call_rust(stringValue);
}
51 changes: 51 additions & 0 deletions core/runtime/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,22 +110,73 @@ impl SnapshotStoreDataStore {
}
}

/// Options for [`create_snapshot`].
///
/// See example use in [this blog post][1].
///
/// [1]: https://deno.com/blog/roll-your-own-javascript-runtime-pt3#diving-into-createsnapshotoptions
Comment thread
NfNitLoop marked this conversation as resolved.
Outdated
pub struct CreateSnapshotOptions {
/// The directory which Cargo will compile everything into.
///
/// This should always be the CARGO_MANIFEST_DIR environment variable.
pub cargo_manifest_dir: &'static str,

/// An optional starting snapshot atop which to build this snapshot.
///
/// Passed to: [`RuntimeOptions::startup_snapshot`]
pub startup_snapshot: Option<&'static [u8]>,

/// Passed to [`RuntimeOptions::skip_op_registration`] while initializing the snapshot runtime.
pub skip_op_registration: bool,

/// Extensions to include within the generated snapshot.
///
/// Passed to [`RuntimeOptions::extensions`]
pub extensions: Vec<Extension>,

/// An optional transpiler to modify the module source before inclusion in the snapshot.
///
/// For example, this might transpile from TypeScript to JavaScript.
///
/// Passed to: [`RuntimeOptions::extension_transpiler`]
pub extension_transpiler: Option<Rc<ExtensionTranspiler>>,

/// An optional callback to perform further modification of the runtime before
/// taking the snapshot.
pub with_runtime_cb: Option<Box<WithRuntimeCb>>,
}

/// See [`create_snapshot`] for usage overview.
pub struct CreateSnapshotOutput {
/// Any files marked as LoadedFromFsDuringSnapshot are collected here and should be
/// printed as 'cargo:rerun-if-changed' lines from your build script.
pub files_loaded_during_snapshot: Vec<PathBuf>,

/// The resulting snapshot file's bytes.
pub output: Box<[u8]>,
}

/// Create a snapshot of a JavaScript runtime, which may yield better startup
/// time.
///
/// There is a great [blog post] about how to use this function, but it's
/// getting a little out of date.
///
/// At a high level, the steps are:
///
/// * In your project's `build.rs` file:
/// * Call `create_snapshot()` from your `build.rs` file.
/// * Output the resulting snapshot to a path, preferably in [OUT_DIR].
/// * Make sure to print a `cargo:rerun-if-changed` line for each
/// [`CreateSnapshotOutput::files_loaded_during_snapshot`].
/// * In your project's source:
/// * Load the bytes of the generated snapshot file
/// ([`include_bytes`] is useful here)
/// * Pass those bytes to [`deno_core::JsRuntime::new`] via
/// [`RuntimeOptions::startup_snapshot`]
///
/// [blog post]: https://deno.com/blog/roll-your-own-javascript-runtime-pt3#creating-a-snapshot-in-buildrs
Comment thread
NfNitLoop marked this conversation as resolved.
Outdated
/// [OUT_DIR]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
#[must_use = "The files listed by create_snapshot should be printed as 'cargo:rerun-if-changed' lines"]
pub fn create_snapshot(
create_snapshot_options: CreateSnapshotOptions,
Expand Down