From 6704512210d14f69eb8e1fdf6d7bb4087d50b7ed Mon Sep 17 00:00:00 2001 From: Cody Casterline Date: Thu, 11 Jul 2024 13:33:51 -0700 Subject: [PATCH 1/6] Add docs for create_snapshot() and its options. --- core/runtime/snapshot.rs | 51 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/core/runtime/snapshot.rs b/core/runtime/snapshot.rs index 66aa8b61e..99b63b6e9 100644 --- a/core/runtime/snapshot.rs +++ b/core/runtime/snapshot.rs @@ -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 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, + + /// 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>, + + /// An optional callback to perform further modification of the runtime before + /// taking the snapshot. pub with_runtime_cb: Option>, } +/// 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, + + /// 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 +/// [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, From 57b69712c5db8ae0b786d0c9b44414dff06e108c Mon Sep 17 00:00:00 2001 From: Cody Casterline Date: Fri, 12 Jul 2024 18:38:00 -0700 Subject: [PATCH 2/6] Add an example for "Roll Your Own JS Runtime" The blog post series had become out-of-date. Committing example code will hopefully bit-rot less? --- Cargo.lock | 8 +++++ Cargo.toml | 1 + core/examples/snapshot/Cargo.toml | 18 +++++++++++ core/examples/snapshot/README.md | 27 ++++++++++++++++ core/examples/snapshot/build.rs | 41 ++++++++++++++++++++++++ core/examples/snapshot/example.js | 5 +++ core/examples/snapshot/src/main.rs | 46 +++++++++++++++++++++++++++ core/examples/snapshot/src/runtime.js | 9 ++++++ 8 files changed, 155 insertions(+) create mode 100644 core/examples/snapshot/Cargo.toml create mode 100644 core/examples/snapshot/README.md create mode 100644 core/examples/snapshot/build.rs create mode 100644 core/examples/snapshot/example.js create mode 100644 core/examples/snapshot/src/main.rs create mode 100644 core/examples/snapshot/src/runtime.js diff --git a/Cargo.lock b/Cargo.lock index 6ce17f631..1d72a761d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -247,6 +247,14 @@ dependencies = [ "generic-array", ] +[[package]] +name = "build-your-own-js-snapshot" +version = "0.1.0" +dependencies = [ + "deno_core", + "tokio", +] + [[package]] name = "bumpalo" version = "3.16.0" diff --git a/Cargo.toml b/Cargo.toml index 212def366..bdb1af322 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ resolver = "2" members = [ "core", + "core/examples/snapshot", "dcore", "ops", "ops/compile_test_runner", diff --git a/core/examples/snapshot/Cargo.toml b/core/examples/snapshot/Cargo.toml new file mode 100644 index 000000000..1d9235396 --- /dev/null +++ b/core/examples/snapshot/Cargo.toml @@ -0,0 +1,18 @@ +# 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 diff --git a/core/examples/snapshot/README.md b/core/examples/snapshot/README.md new file mode 100644 index 000000000..12614ea8f --- /dev/null +++ b/core/examples/snapshot/README.md @@ -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. + +## 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 diff --git a/core/examples/snapshot/build.rs b/core/examples/snapshot/build.rs new file mode 100644 index 000000000..21577393c --- /dev/null +++ b/core/examples/snapshot/build.rs @@ -0,0 +1,41 @@ +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()); + } +} diff --git a/core/examples/snapshot/example.js b/core/examples/snapshot/example.js new file mode 100644 index 000000000..e39a67834 --- /dev/null +++ b/core/examples/snapshot/example.js @@ -0,0 +1,5 @@ +// Run this script with `cargo run`. + +import { callRust } from "my:runtime"; + +callRust("Hello from example.js"); diff --git a/core/examples/snapshot/src/main.rs b/core/examples/snapshot/src/main.rs new file mode 100644 index 000000000..4e2ea87fd --- /dev/null +++ b/core/examples/snapshot/src/main.rs @@ -0,0 +1,46 @@ +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")); diff --git a/core/examples/snapshot/src/runtime.js b/core/examples/snapshot/src/runtime.js new file mode 100644 index 000000000..debe8ac26 --- /dev/null +++ b/core/examples/snapshot/src/runtime.js @@ -0,0 +1,9 @@ +/** + * 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); +} From b19e5ceec8c27bd1ae06f1778b515d1957d4ab0a Mon Sep 17 00:00:00 2001 From: Cody Casterline Date: Fri, 12 Jul 2024 19:18:18 -0700 Subject: [PATCH 3/6] Add copyright headers. --- core/examples/snapshot/Cargo.toml | 1 + core/examples/snapshot/build.rs | 1 + core/examples/snapshot/example.js | 1 + core/examples/snapshot/src/main.rs | 1 + core/examples/snapshot/src/runtime.js | 1 + 5 files changed, 5 insertions(+) diff --git a/core/examples/snapshot/Cargo.toml b/core/examples/snapshot/Cargo.toml index 1d9235396..03a76ce86 100644 --- a/core/examples/snapshot/Cargo.toml +++ b/core/examples/snapshot/Cargo.toml @@ -1,3 +1,4 @@ +# 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 diff --git a/core/examples/snapshot/build.rs b/core/examples/snapshot/build.rs index 21577393c..1258f2a69 100644 --- a/core/examples/snapshot/build.rs +++ b/core/examples/snapshot/build.rs @@ -1,3 +1,4 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use deno_core::{ extension, snapshot::{create_snapshot, CreateSnapshotOptions}, diff --git a/core/examples/snapshot/example.js b/core/examples/snapshot/example.js index e39a67834..800fd3eba 100644 --- a/core/examples/snapshot/example.js +++ b/core/examples/snapshot/example.js @@ -1,3 +1,4 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Run this script with `cargo run`. import { callRust } from "my:runtime"; diff --git a/core/examples/snapshot/src/main.rs b/core/examples/snapshot/src/main.rs index 4e2ea87fd..83f865d0c 100644 --- a/core/examples/snapshot/src/main.rs +++ b/core/examples/snapshot/src/main.rs @@ -1,3 +1,4 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use std::{env::current_dir, rc::Rc}; use deno_core::{ diff --git a/core/examples/snapshot/src/runtime.js b/core/examples/snapshot/src/runtime.js index debe8ac26..f5179e112 100644 --- a/core/examples/snapshot/src/runtime.js +++ b/core/examples/snapshot/src/runtime.js @@ -1,3 +1,4 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. /** * This module provides the JavaScript interface atop calls to the Rust ops. */ From 52d61e40f16987bc1fa7227771dca7ec4eba277b Mon Sep 17 00:00:00 2001 From: Cody Casterline Date: Tue, 23 Jul 2024 10:35:06 -0700 Subject: [PATCH 4/6] Don't link to outdated blog post in rust docs. --- core/runtime/snapshot.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/core/runtime/snapshot.rs b/core/runtime/snapshot.rs index 99b63b6e9..922168fdd 100644 --- a/core/runtime/snapshot.rs +++ b/core/runtime/snapshot.rs @@ -112,9 +112,9 @@ impl SnapshotStoreDataStore { /// Options for [`create_snapshot`]. /// -/// See example use in [this blog post][1]. +/// See: [example][1]. /// -/// [1]: https://deno.com/blog/roll-your-own-javascript-runtime-pt3#diving-into-createsnapshotoptions +/// [1]: https://github.com/denoland/deno_core/tree/main/core/examples/snapshot pub struct CreateSnapshotOptions { /// The directory which Cargo will compile everything into. /// @@ -159,9 +159,6 @@ pub struct CreateSnapshotOutput { /// 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: @@ -175,7 +172,9 @@ pub struct CreateSnapshotOutput { /// * 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 +/// For a concrete example, see [core/examples/snapshot/][example]. +/// +/// [example]: https://github.com/denoland/deno_core/tree/main/core/examples/snapshot /// [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( From 41a28775945e733962027d3010b29cd7a5d7b294 Mon Sep 17 00:00:00 2001 From: Cody Casterline Date: Tue, 23 Jul 2024 11:30:07 -0700 Subject: [PATCH 5/6] Check the output of the snapshot example from CI. --- core/examples/snapshot/tests/output.rs | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 core/examples/snapshot/tests/output.rs diff --git a/core/examples/snapshot/tests/output.rs b/core/examples/snapshot/tests/output.rs new file mode 100644 index 000000000..0a092d063 --- /dev/null +++ b/core/examples/snapshot/tests/output.rs @@ -0,0 +1,33 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use core::str; +use std::process::{Command, Output}; + +#[test] +fn check_output() -> Result<(), Box> { + let output = capture_output()?; + + let err = str::from_utf8(&output.stderr)?; + assert_eq!(err, ""); + assert!(output.status.success()); + + let out = str::from_utf8(&output.stdout)?; + assert_eq!(out, "Received this value from JS: Hello from example.js\n"); + + Ok(()) +} + +/// NOTE!: This is NOT the preferred pattern to follow for testing binary crates! +/// See: +/// +/// However, we want to keep this example simple, so we're not going to create separate main.rs & lib.rs, or +/// add injectable outputs. We'll just run the binary and then capture its output. +fn capture_output() -> Result { + Command::new("cargo") + .args([ + "run", + "--release", // CI runs in --release mode, so re-use its cache. + "--quiet", // only capture the command's output. + ]) + .output() +} From 19e672c565fb104d8efdf8c4f7bf7faf31f77daf Mon Sep 17 00:00:00 2001 From: snek Date: Thu, 5 Sep 2024 10:44:38 -0700 Subject: [PATCH 6/6] Update core/examples/snapshot/README.md Co-authored-by: Ian Bull --- core/examples/snapshot/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/examples/snapshot/README.md b/core/examples/snapshot/README.md index 12614ea8f..af7653259 100644 --- a/core/examples/snapshot/README.md +++ b/core/examples/snapshot/README.md @@ -8,6 +8,11 @@ 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. +## Running + +The example can be run by changing to the `core/examples/snapshot` directory and +running `cargo run`. + ## Differences Differences from those blog posts: