diff --git a/.gitattributes b/.gitattributes index 3712583d..4a26aae7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,3 @@ -crates/quickjs-wasm-sys/quickjs/* linguist-vendored - # Some people try to run shell scripts in WSL after cloning the repo in Windows. # If they clone the repo in Windows and the default `core.autocrlf` settings # are used, the shell script line endings are converted to Windows line diff --git a/.gitignore b/.gitignore index 3a168164..1b0c523b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,5 +22,3 @@ tests/target/*.wasm .idea .vscode - -crates/quickjs-wasm-sys/wasi-sdk diff --git a/Makefile b/Makefile index 85529297..dbea23f8 100644 --- a/Makefile +++ b/Makefile @@ -69,6 +69,3 @@ clean: clean-wasi-sdk clean-cargo clean-cargo: cargo clean - -clean-wasi-sdk: - rm -r crates/quickjs-wasm-sys/wasi-sdk 2> /dev/null || true diff --git a/README.md b/README.md index d1ec8649..0183ab93 100644 --- a/README.md +++ b/README.md @@ -277,10 +277,6 @@ $ wasmtime run --preload javy_quickjs_provider_v2=provider.wasm my_code.wasm hello world! ``` -## Using quickjs-wasm-rs to build your own toolchain - -The `quickjs-wasm-rs` crate that is part of this project can be used as part of a Rust crate targeting Wasm to customize how that Rust crate interacts with QuickJS. This may be useful when trying to use JavaScript inside a Wasm module and Javy does not fit your needs as `quickjs-wasm-rs` contains serializers that make it easier to send structured data (for example, strings or objects) between host code and Wasm code. - ## Releasing 1. Update the root `Cargo.toml` with the new version diff --git a/docs/contributing-architecture.md b/docs/contributing-architecture.md index f66491db..51a8d392 100644 --- a/docs/contributing-architecture.md +++ b/docs/contributing-architecture.md @@ -9,14 +9,11 @@ flowchart TD javy-cli --> wasm subgraph wasm[javy_core.wasm / javy_quickjs_provider.wasm] javy-core --> javy - javy-core --> javy-apis - javy-apis --> javy - javy --> quickjs-wasm-rs - quickjs-wasm-rs --> quickjs-wasm-sys + javy --> rquickjs end ``` -We anticipate most changes will be to the `javy`, `javy-apis`, and `quickjs-wasm-rs` crates. +We anticipate most changes will be to the `javy-cli` and `javy` crates. ### `javy` @@ -83,203 +80,9 @@ Read the `config` and call the appropriate methods on `context` to apply the con You should consider gating your feature by a Cargo feature when: - Your feature would materially increase the size of the produced Wasm module. -- Your feature requires enabling additional features in the `quickjs-wasm-rs` crate. These are guidelines and we're willing to discuss if a feature needs to be gated by a Cargo feature on a case-by-case basis. -### `javy-apis` - -Common JS APIs for use with a `javy::Runtime`. For example, `console`, `TextEncoder`, `TextDecoder`. If there is a standard JS API that seems like it would be useful to multiple users of Javy, it should be implemented in this crate. If this is an API specific to your use case, you should define it in a crate of your own and register the implementation using a similar approach to how the APIs in this crate define their implementations. - -#### Adding an API implementation - -1. Add a feature to the crate's `Cargo.toml` for your module. -2. Create a directory under `src` with a `mod.rs` file. -3. If your API implementation requires configuration, create a configuration struct for the configuration properties required in your module. -4. If necessary, add any JS source files inside the module you're adding. If you can implement your API without JS, you don't need to add any. -5. In `mod.rs`, implement the `crate::JSApiSet` trait. If your API requires configuration, add the configuration struct defined earlier to the `crate::ApiConfig` struct under a `#[cfg(feature = "your feature name")]` attribute. -6. Add the `mod` to the crate's `lib.rs` under a `#[cfg(feature = "your feature name")]` attribute. -7. Add a call to your struct's `register` method under a `#[cfg(feature = "your feature name")]` in `lib.rs`'s `add_to_runtime` function. - -##### Example - -Here's a contrived example of adding an API to print an environment variable with a prefix that's configured when creating a Javy runtime. Normally this wouldn't go in this crate, but instead in your own crate since it's not a generally useful API. - -Create the directory, `crates/apis/src/env_var_printer`. - -In `crates/apis/Cargo.toml`: - -```diff - [features] - console = [] -+ env_var_printer = [] - random = ["dep:fastrand"] - stream_io = [] - text_encoding = [] -``` - -In `crates/apis/src/env_var_printer/config.rs`: - -```rust -use crate::APIConfig; - -// Use crate visibility to avoid exposing the property outside the crate -#[derive(Debug)] -pub(crate) struct EnvVarConfig { - pub(super) prefix: String, -} - -// Always have a default value for every config. -impl Default for EnvVarConfig { - fn default() -> Self { - Self { - prefix: "Default prefix: ".to_string(), - } - } -} - -// Define one or more methods on `APIConfig`, not `EnvVarConfig`, to set properties. -impl APIConfig { - /// Sets the prefix for `Javy.Env.print`. - pub fn prefix(&mut self, prefix: String) -> &mut Self { - self.env_var.prefix = prefix; - self - } -} -``` - -In `crates/apis/src/env_var_printer/env-var-printer.js`: - -```js -// Wrap everything in an anonymous function to avoid leaking local variables into the global scope. -(function () { - // Get a reference to the function before we delete it from `globalThis`. - const __javy_env_printEnvVar = globalThis.__javy_env_printVal; - globalThis.Javy.Env = { - print(name) { - __javy_env_printEnvVar(name); - }, - }; - // Delete the function from `globalThis` so it doesn't leak. - Reflect.deleteProperty(globalThis, "__javy_env_printVal"); -})(); -``` - -For something this simple, you don't need a JS file, I'm including it to demonstrate how things would be wired up. - -In `crates/apis/src/env_var_printer/mod.rs`: - -```rust -use std::env; - -use anyhow::{bail, Result}; -use javy::{quickjs::JSValue, Runtime}; - -use crate::{APIConfig, JSApiSet}; -pub(super) use config::EnvVarConfig; - -mod config; - -pub(super) struct EnvVarPrinter; - -impl JSApiSet for EnvVarPrinter { - fn register(&self, runtime: &Runtime, config: &APIConfig) -> Result<()> { - let context = runtime.context(); - - let global = context.global_object()?; - - let mut javy_object = global.get_property("Javy")?; - - // If you're defining something on the `Javy` object, ensure it exists. - if javy_object.is_undefined() { - javy_object = context.object_value()?; - global.set_property("Javy", javy_object)?; - } - - // `wrap_callback`` has a static lifetime so you can't use references to the config in its body. - let prefix = config.env_var.prefix.clone(); - global.set_property( - "__javy_env_printVal", - context.wrap_callback(move |_ctx, _this, args| { - let [name] = args else { - bail!("Incorrect number of arguments"); - }; - // Convert JSValueRefs to Rust types. - let name: String = name.try_into()?; - println!("{}{}", prefix, env::var(name)?); - Ok(JSValue::Undefined) - })?, - )?; - - context.eval_global("env-var-printer.js", include_str!("env-var-printer.js"))?; - - Ok(()) - } -} - -// Automated tests are highly recommended -#[cfg(test)] -mod tests { - use std::env; - - use crate::{APIConfig, JSApiSet}; - use anyhow::Result; - use javy::Runtime; - - use super::EnvVarPrinter; - - #[test] - fn test_print_env_var() -> Result<()> { - let runtime = Runtime::default(); - let context = runtime.context(); - EnvVarPrinter.register(&runtime, &APIConfig::default())?; - env::set_var("HELLO", "there"); - let _ = context.eval_global("main", "Javy.Env.print('HELLO');")?; - env::remove_var("HELLO"); - Ok(()) - } -} -``` - -In `crates/apis/src/api_config.rs`: - -```diff - #[derive(Debug, Default)] - pub struct APIConfig { - #[cfg(feature = "console")] - pub(crate) console: crate::console::ConsoleConfig, -+ #[cfg(feature = "env_var_printer")] -+ pub(crate) env_var: crate::env_var_printer::EnvVarConfig, - } -``` - -In `crates/apis/src/lib.rs`: - -```diff - #[cfg(feature = "console")] - mod console; -+ #[cfg(feature = "env_var_printer")] -+ mod env_var_printer; - #[cfg(feature = "random")] - mod random; -``` - -and - -```diff - pub fn add_to_runtime(runtime: &Runtime, config: APIConfig) -> Result<()> { - #[cfg(feature = "console")] - console::Console::new().register(runtime, &config)?; -+ #[cfg(feature = "env_var_printer")] -+ env_var_printer::EnvVarPrinter.register(runtime, &config)?; - #[cfg(feature = "random")] - random::Random.register(runtime, &config)?; -``` - -#### When to add a cargo feature - -All new APIs should be gated by a cargo feature so users of the crate can opt into including them in their runtime. - ### `javy-cli` The CLI for compiling JS to Wasm. This isn't intended to be a CLI that accommodates all uses for all users but rather to provide a useful base of functionality. This is kind of similar to how Wasmtime ships with a crate and a CLI and doing non-generic things with Wasmtime requires writing your own CLI around the Wasmtime crate. @@ -301,29 +104,11 @@ You should gate your feature with a cargo feature if: - You want to support building a Wasm module with an experimental configuration of the runtime. We do this for the event loop because the current implementation has not been thoroughly vetted. We also need a build of Javy with event loop support to run a number of web platform tests for text encoding. -### `quickjs-wasm-rs` - -Provides an ergonomic API around the `quickjs-wasm-sys` crate as well as a `serde` implementations for `JSValueRef`. - -#### When to add a cargo feature - -You should gate your feature with a cargo feature if: - -- Including your feature will materially increase the size of the produced Wasm module. - -### `quickjs-wasm-sys` - -A Rust wrapper around the QuickJS C library. - -#### When to add a cargo feature - -We do not anticipate changes to this library requiring a new cargo feature. Please reach out on Zulip or in GitHub if there is a reason to add a new cargo feature. - ## NPM packages ### `javy` -A JS library providing ergonomic helpers around the lower level APIs for I/O exposed by `javy-apis`. +A JS library providing ergonomic helpers around the lower level APIs for I/O exposed by the `javy` crate. ### `javy-cli` diff --git a/docs/contributing.md b/docs/contributing.md index 5e92dbaa..7df4da37 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -6,13 +6,11 @@ See our [architecture document](contributing-architecture.md) for more informati ## Adding additional JS APIs -We will only add JS APIs or accept contributions that add JS APIs that are potentially useful across multiple environments and do not invoke non-[WASI](https://wasi.dev/) hostcalls. If you wish to add or use JS APIs that do not meet these criteria, please use the `quickjs-wasm-rs` crate directly. We may revisit how we support importing and exporting custom functionality from Javy once [the Component Model](https://github.com/WebAssembly/component-model) has stabilized. +We will only add JS APIs or accept contributions that add JS APIs that are potentially useful across multiple environments and do not invoke non-[WASI](https://wasi.dev/) hostcalls. If you wish to add or use JS APIs that do not meet these criteria, please use the `rquickjs` crate directly. We may revisit how we support importing and exporting custom functionality from Javy once [the Component Model](https://github.com/WebAssembly/component-model) has stabilized. ## Versioning for library crates -The library crates, `javy`, `javy-apis`, `quickjs-wasm-rs`, and `quickjs-wasm-sys`, use the versioning system described in [Rust for Rustaceans](https://rust-for-rustaceans.com/) in the _Unreleased Versions_ section in the _Project Structure_ chapter. The underlying motivation is that the version in the crate's `Cargo.toml` is important between releases to ensure Cargo does not reuse a stale version if a project relies on a version of the crate that has not yet been published to crates.io and the version required by that project is updated to a version with new additive or breaking changes. - -The versions for `javy` and `javy-apis` must always be the same. So a version change in the one crate should result in a version change in the other crate. +The library crate, `javy`, uses the versioning system described in [Rust for Rustaceans](https://rust-for-rustaceans.com/) in the _Unreleased Versions_ section in the _Project Structure_ chapter. The underlying motivation is that the version in the crate's `Cargo.toml` is important between releases to ensure Cargo does not reuse a stale version if a project relies on a version of the crate that has not yet been published to crates.io and the version required by that project is updated to a version with new additive or breaking changes. ### The system @@ -20,7 +18,7 @@ After publishing a release, immediately update the version number to the next pa When releasing, remove the suffix and then publish. -For example, let's say the last published version of `quickjs-wasm-rs` is `2.0.0`, so the current version in the Cargo.toml file is `2.0.0-alpha.1`. If you add a new public function, you would change the version to `2.1.0-alpha.1`. This is because adding a new public function is considered an additive change. After merging those changes, if you add a new public function, you would change the version to `2.1.0-alpha.2`. This is because adding another new function is an additional additive change. Now if you were to make a function that was public, private, you would change the version to `3.0.0-alpha.1`. This is because removing a public function is considered a breaking change. After merging that change, if you were to then add a new public function, then you would increment the version to `3.0.0-alpha.2` because this is making an additional additive change. It's not necessary to increment the minor version in this case because version `3.0.0` has not been published yet so version `3.0.0` can contain a mixture of additive and breaking changes from the last `2.x.x` version published. +For example, let's say the last published version of `javy` is `2.0.0`, so the current version in the Cargo.toml file is `2.0.0-alpha.1`. If you add a new public function, you would change the version to `2.1.0-alpha.1`. This is because adding a new public function is considered an additive change. After merging those changes, if you add a new public function, you would change the version to `2.1.0-alpha.2`. This is because adding another new function is an additional additive change. Now if you were to make a function that was public, private, you would change the version to `3.0.0-alpha.1`. This is because removing a public function is considered a breaking change. After merging that change, if you were to then add a new public function, then you would increment the version to `3.0.0-alpha.2` because this is making an additional additive change. It's not necessary to increment the minor version in this case because version `3.0.0` has not been published yet so version `3.0.0` can contain a mixture of additive and breaking changes from the last `2.x.x` version published. ## cargo vet diff --git a/docs/extending.md b/docs/extending.md index 15f9ea18..dd23e048 100644 --- a/docs/extending.md +++ b/docs/extending.md @@ -1,6 +1,6 @@ # Extending Javy -If you want to use Javy for your own project, you may find that the existing code is not sufficient since you may want to offer custom APIs or use different branding for the CLI. The approach we'd recommend taking is to create your own version of the `javy-cli` and `javy-core` crates (you could fork these if you would like) and depend on the upstream version of the `javy` and `javy-apis` crates. You can add your own implementations of custom JS APIs in your fork of the `javy-core` crate or in a different crate that you depend on in your `javy-core` fork. If you find that something is missing in the `javy` crate that you require to implement something in your fork, we would appreciate it if you would open a GitHub issue and consider making the change upstream instead of in your fork so all users of the `javy` crate can benefit. +If you want to use Javy for your own project, you may find that the existing code is not sufficient since you may want to offer custom APIs or use different branding for the CLI. The approach we'd recommend taking is to create your own version of the `javy-cli` and `javy-core` crates (you could fork these if you would like) and depend on the upstream version of the `javy` crate. You can add your own implementations of custom JS APIs in your fork of the `javy-core` crate or in a different crate that you depend on in your `javy-core` fork. If you find that something is missing in the `javy` crate that you require to implement something in your fork, we would appreciate it if you would open a GitHub issue and consider making the change upstream instead of in your fork so all users of the `javy` crate can benefit. See our documentation on [using complex data types in Wasm functions](complex-data-types-in-wasm-functions.md) for how to support Wasm functions that need to use byte arrays, strings, or structured data. @@ -11,54 +11,6 @@ flowchart TD your-cli --> wasm subgraph wasm[your_core.wasm] your-core --> javy[upstream javy] - your-core --> javy-apis[upstream javy-apis] - javy-apis --> javy - javy --> quickjs-wasm-rs - quickjs-wasm-rs --> quickjs-wasm-sys + javy --> rquickjs end ``` - -## An example of changes you could make - -Let's say you create a crate called `my-new-apis` with the following code in its `lib.rs` to define your new JS APIs: - -```rust -use anyhow::Result; -use javy::Runtime; - -pub fn register(runtime: &Runtime) -> Result<()> { - let ctx = runtime.context(); - - let global_object = ctx.global_object()?; - - // Registers a `SomeApi.foo` function. - let some_api_object = ctx.object_value()?; - some_api_object.set_property("foo", ctx.wrap_callback(|_ctx, _this, _args| todo!())?)?; - global_object.set_property("SomeApi", some_api_object)?; - - // Registers a `SomeOtherApi.bar` function. - let some_other_api_object = ctx.object_value()?; - some_other_api_object.set_property("bar", ctx.wrap_callback(|_ctx, _this, _args| todo!())?)?; - global_object.set_property("SomeOtherApi", some_other_api_object)?; - - Ok(()) -} - -``` - -We'd suggest registering your additional API code next to where you register the Javy APIs. For example, in Javy's core crate, it's in `crates/core/runtimes.rs`: - -```diff - pub(crate) fn new_runtime() -> Result { - let mut api_config = APIConfig::default(); - api_config.log_stream(LogStream::StdErr); -- Runtime::new_with_apis(Config::default(), api_config) -+ // Creates the Javy runtime and registers the Javy APIs. -+ let runtime = Runtime::new_with_apis(Config::default(), api_config)?; -+ // Registers your custom APIs. -+ my_new_apis::register(&runtime)?; -+ Ok(runtime) - } -``` - -You are free to call `Runtime::new_with_apis` or another function to create your Javy `Runtime` and define and register your additional APIs wherever you would like in your own crate. This is just an example of one way you could do it. diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 724132f0..d07f807b 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -6,21 +6,6 @@ who = "Jeff Charles " criteria = "safe-to-deploy" version = "1.0.0" -[[audits.javy-apis]] -who = "Jeff Charles " -criteria = "safe-to-deploy" -version = "1.0.0" - -[[audits.quickjs-wasm-rs]] -who = "Jeff Charles " -criteria = "safe-to-deploy" -version = "1.0.0" - -[[audits.quickjs-wasm-sys]] -who = "Jeff Charles " -criteria = "safe-to-deploy" -version = "1.0.0" - [[trusted.aho-corasick]] criteria = "safe-to-deploy" user-id = 189 # Andrew Gallant (BurntSushi)