Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update docs for plugins #832

Merged
merged 4 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Binary file added crates/runner/test_plugin.wasm
Binary file not shown.
58 changes: 41 additions & 17 deletions docs/docs-contributing-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ This document is intended to provide an overview of the architecture of `javy`.
flowchart TD
javy-cli --> wasm
subgraph wasm[plugin.wasm]
javy-plugin --> javy
javy-plugin --> javy-plugin-api
javy-plugin-api --> javy
javy --> rquickjs
end
```
Expand All @@ -30,7 +31,7 @@ QuickJS that would be helpful, this is the place to add it.
#### Example

This is a contrived example of how to make a change. If I want to add
a configuuration to set a global variable called `javy_rocks` to `true`, I would
a configuration to set a global variable called `javy_rocks` to `true`, I would
do the following:

In `crates/javy/src/config.rs`:
Expand All @@ -39,41 +40,56 @@ In `crates/javy/src/config.rs`:
/// A configuration for [`Runtime`](crate::Runtime).
#[derive(Debug)]
pub struct Config {
+ pub(crate) set_javy_rocks: bool,
+ pub(crate) javy_rocks: bool,
}

impl Default for Config {
/// Creates a [`Config`] with default values.
fn default() -> Self {
Self {
+ set_javy_rocks: false,
+ javy_rocks: false,
}
}
}

impl Config {
+ /// Sets `globalThis.javy_rocks` to `true`.
+ pub fn set_javy_rocks(&mut self) -> &mut Self {
+ self.set_javy_rocks = true;
+ pub fn javy_rocks(&mut self) -> &mut Self {
+ self.javy_rocks = true;
+ self
+ }
}
```

In `crates/javy/src/apis/javy_rocks.rs`:

```rust
pub struct JavyRocks;

impl Instrinsic for JavyRocks {
unsafe fn add_intrinsic(ctx: NonNull<qjs::JSContext>) {
register(Ctx::from_raw(ctx)).expect("registering Javy Rocks to succeed")
}
}

fn register<'js>(this: Ctx<'js>) -> Result<()> {
let globals = this.globals();
globals.set("javy_rocks", true);
}
```

In `crates/javy/src/runtime.rs`:

```diff
+ context.with(|cx| {
+ if cfg.set_javy_rocks {
+ let globals = cx.globals();
+ globals.set("javy_rocks", true);
+ if cfg.javy_rocks {
+ unsafe {
+ JavyRocks::add_intrinsic(ctx.as_raw())
+ }
+ });
+
+ }
```


Read the `config` and call the appropriate methods on `context` to apply the
Define a new property on the `Config`, in `runtime`'s `build_from_config` add
the intrinsic if the config property is enabled, and use `context` to define the
configuration.

#### When to add a `cargo` feature
Expand Down Expand Up @@ -110,9 +126,10 @@ You should gate your feature with a cargo feature if your feature/change:
### `javy-plugin`

Gets compiled to `plugin.wasm` for use by the CLI and in environments for
running dynamically linked modules. This isn't intended to be used as a code
library by third parties. Contains logic for driving the `javy` crate for Wasm
modules generated by `javy-cli`.
running dynamically linked modules. This is the default plugin for Javy.
This isn't intended to be used as a code library by third parties. Defines a
an `initialize_runtime` function that uses a configuration structure to
allow the CLI to set various JS runtime configuration options.

#### When to add a `cargo` feature

Expand All @@ -122,6 +139,13 @@ 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.

### `javy-plugin-api`

Used by Javy plugins to provide common implementations for exports and custom
sections the plugin is expected to expose. This drives the APIs exposed by the
`javy` crate. For example, this crate adds a Wasm function export for `invoke`
to the plugin which is used for actually running the JavaScript.

## `npm` packages

### `javy`
Expand Down
30 changes: 18 additions & 12 deletions docs/docs-using-dynamic-linking.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,37 @@ linked modules do not and will not execute in WebAssembly runtimes that do not
meet these requirements.

To successfully instantiate and run a dynamically linked Javy module, the
execution environment must provide a `javy_quickjs_provider_v<version>` namespace for
importing that links to the exports provided by the `plugin.wasm`
module. Dynamically linked modules **cannot** be instantiated in environments
that do not provide this import.
execution environment must provide a collection of imports that match the
exports from a Javy plugin Wasm module (for example,
`canonical_abi_realloc` and `invoke`). The namespace for these imports must
match the import namespace specified by the Javy plugin Wasm module used to
generate the dynamically linked Wasm module (this is, if the plugin's import
namespace was `my_plugin_v1`, then the imports must be made available under the
module name `my_plugin_v1`). This value is available from the `import_namespace`
custom section in the Javy plugin module. You can also statically inspect the
imports of the dynamically linked Wasm module to determine the import
namespace. Dynamically linked modules **cannot** be instantiated in
environments that do not provide the required imports.

Dynamically linked Javy modules are tied to QuickJS since they use QuickJS's
bytecode representation.

#### Obtaining the default plugin module

#### Obtaining the plugin module
The `plugin.wasm` module is available as an asset on the Javy release you are
using.

The `plugin.wasm` module is available as an asset on the Javy
release you are using.
It can also be obtained by running `javy emit-plugin -o <path>` to write the
module into `<path>`.

It can also be obtained by running `javy emit-provider -o
<path>` to write the module into `<path>`.

#### Creating and running a dynamically linked module througy the CLI
#### Creating and running a dynamically linked module through the CLI

Run:

```
$ echo 'console.log("hello world!");' > my_code.js
$ javy build -C dynamic -o my_code.wasm my_code.js
$ javy emit-provider -o plugin.wasm
$ javy build -C dynamic -C plugin=plugin.wasm -o my_code.wasm my_code.js
$ wasmtime run --preload javy_quickjs_provider_v3=plugin.wasm my_code.wasm
hello world!
```
104 changes: 85 additions & 19 deletions docs/docs-using-extending.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,94 @@
# Extending

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-plugin` 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-plugin` crate
or in a different crate that you depend on in your `javy-plugin` 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.
code is not sufficient since you may want to offer custom JavaScript APIs. The
approach we recommend taking is to create your own Javy plugin Wasm module.
This plugin module can then be specified when using the Javy CLI when building
Javy Wasm modules as a `-C plugin` flag when using `javy build`.

To create your own Javy plugin Wasm module, create a new Rust project that
will compile to a library (that is, `cargo init --lib`).

Your `Cargo.toml` should look like:

```toml
[package]
name = "my-plugin-name"
version = "0.1.0"

[lib]
name = "my_plugin_name"
crate-type = ["cdylib"]

[dependencies]
javy-plugin-api = "1.0.0"
```

And `src/lib.rs` should look like:

```rust
use javy_plugin_api::import_namespace;
use javy_plugin_api::javy::quickjs::prelude::Func;
use javy_plugin_api::javy::Config;

import_namespace!("my_plugin_name");

#[export_name = "initialize_runtime"]
pub extern "C" fn initialize_runtime() {
let mut config = Config::default();
config
.text_encoding(true)
.javy_stream_io(true);

javy_plugin_api::initialize_runtime(config, |runtime| {
runtime
.context()
.with(|ctx| {
ctx.globals()
.set("isThisAPlugin", Func::from(|| "yes it is"))
})
.unwrap();
runtime
})
.unwrap();
}
```

You can then run `cargo build --target=wasm32-wasip1 --release` to create a
Wasm module. Then you need to run

```
javy init-plugin <path_to_plugin> -o <path_to_initialized_module>`
Copy link
Member

Choose a reason for hiding this comment

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

🎉

```

which will validate and initialize the Javy runtime. This `javy init-plugin`
step is required for the plugin to be useable by the Javy CLI.

See our documentation on [using complex data types in Wasm
functions](./contributing-complex-data-types.md) for how to support Wasm
functions that need to use byte arrays, strings, or structured data.

For a visual representation of how we expect forks to consume our crates:
## The full plugin API

```mermaid
flowchart TD
your-cli --> wasm
subgraph wasm[your_plugin.wasm]
your-plugin --> javy[upstream javy]
javy --> rquickjs
end
```
The Wasm API the Javy CLI expects Javy plugins to expose is the following:
- Exported `initialize_runtime() -> ()` function. This will initialize a mutable
global containing the Javy runtime for use by `compile_src` and `invoke`.
- Exported
`canonical_abi_realloc(orig_ptr: i32, orig_len: i32, new_ptr: i32, new_len: i32) -> ptr: i32`
function. This is used to allocate memory in the plugin module.
- Exported `compile_src(src_ptr: i32, src_len: i32) -> bytecode_wide_ptr: i32`
function. This is used to compile JavaScript source code to QuickJS bytecode.
The return pointer points to a tuple of
`(bytecode_ptr: i32, bytecode_len: i32)` in the plugin instance's linear
memory.
- Exported
`invoke(bytecode_ptr: i32, bytecode_len: i32, fn_name_ptr: i32, fn_name_len: i32) -> ()`
function. This is used to evaluate the JavaScript code and optionally to
call an exported JS function if `fn_name_ptr` is not `0`.
- Custom section named `import_namespace` containing a UTF-8 encoded string.
This is used to determine the namespace that will be used for the Wasm
imports in dynamically linked modules built with this plugin.

The `javy-plugin-api` crate will export implementations of all required exported
functions except `initialize_runtime`. `import_namespace!` will define the
`import_namespace` custom section.
Copy link
Member

Choose a reason for hiding this comment

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

Minor nit: what do you think of putting this information inside a table to improve readability? It'll for sure be less readable in markdown format, but potentially more readable when rendered.

Copy link
Collaborator Author

@jeffcharles jeffcharles Nov 19, 2024

Choose a reason for hiding this comment

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

I don't think a table would help make it more readable. The explanation text would just be super squished. Maybe headers instead? That's how most documentation sites would render methods/functions.

Loading