Migrating the Editor to a Wasmer Package
In the previous tutorial, we created a basic Markdown editor by wiring a
<textarea>
up to a Markdown renderer compiled to WASI. Now, we're going to
take things to the next level by converting the renderer to a package and
running it from the Wasmer registry.
Install the Wasmer Toolchain
Install Wasmer
Click here for instructions on how to install Wasmer if you haven't done it already!
Create a Wasmer Package
We can use the wasmer
CLI to create a new package. From the
markdown-renderer/
crate created in the previous tutorial, run the following:
$ wasmer init
NOTE: Initializing wasmer.toml file with metadata from Cargo.toml
-> ./Cargo.toml
This should have created a wasmer.toml
file.
- Cargo.toml
- Cargo.lock
- wasmer.toml
- main.rs
Update Package Metadata
Make sure you update the [package]
table in your wasmer.toml
to refer to
your user. I'll be publishing to the wasmer-examples
namespace, but you'll
probably want to use your own username here.
[package]
name = 'wasmer-examples/markdown-renderer'
version = '0.1.0'
description = 'Render markdown input to HTML'
entrypoint = 'markdown-renderer'
...
That entrypoint
field is important! It tells the Wasmer SDK that the
markdown-renderer
command should be executed by default.
Compile to WebAssembly
Don't forget to compile the markdown-renderer.wasm
, if you haven't already.
$ cargo build --target=wasm32-wasi --release
Finished release [optimized] target(s) in 0.10s
Publishing to the Wasmer Registry
Now our wasmer.toml
is up to date, we can go ahead and publish to the Wasmer
registry.
$ wasmer publish
[1/2] ⬆️ Uploading...
[2/2] 📦 Publishing...
Successfully published package `wasmer-examples/markdown-renderer@0.1.0`
Publishing a package requires the wasmer
CLI to be authenticated with the
Wasmer registry.
You can check your authentication status using the following command:
$ wasmer whoami
logged into registry "https://registry.wasmer.io/graphql" as user "michael-f-bryan"
Otherwise, you will need to log in.
$ wasmer login
Opening auth link in your default browser: https://wasmer.io/auth/cli?nonce_id=...&secret=...
Waiting for session... Done!
✅ Login for Wasmer user "michael-f-bryan" saved
Switch to the wasmer-examples/markdown-renderer
Package
Now, let's update the original code to use Wasmer packages instead of running
*.wasm
binaries directly.
The code is almost identical, so I'll just explain the parts that have changed.
First, we import the Wasmer
and Command
classes and remove the
markdownRendererUrl
import.
-import { init, runWasix } from "@wasmer/sdk";
-import markdownRendererUrl from "./markdown-renderer/target/wasm32-wasi/release/markdown-renderer.wasm?url";
+import { init, Wasmer, Command } from "@wasmer/sdk";
Next, instead of passing a fetch()
response to
WebAssembly.compileStreaming()
, we use Wasmer.fromRegistry("...")
to load a
package from the registry.
async function initialize() {
await init();
- return WebAssembly.compileStreaming(fetch(markdownRendererUrl));
+ return await Wasmer.fromRegistry("wasmer-examples/markdown-renderer");
}
Next, we update the renderMarkdown()
method to accept a Command
rather than
a WebAssembly.Module
. A Command
is an executable element inside a Wasmer
package and will use the WASIX runner under the hood.
-async function renderMarkdown(module: WebAssembly.Module, markdown: string) {
- const instance = await runWasix(module, {});
+async function renderMarkdown(cmd: Command, markdown: string) {
+ const instance = await cmd.run();
const stdin = instance.stdin.getWriter();
const encoder = new TextEncoder();
-
await stdin.write(encoder.encode(markdown));
await stdin.close();
@@ -29,12 +26,12 @@
}
Finally, we update main()
to rename module
to pkg
and pass
pkg.entrypoint
to renderMarkdown()
. We know the
wasmer-examples/markdown-renderer
package will always have an entrypoint (we
wrote it!) so we can skip the if (!pkg.entrypoint)
check with !
.
async function main() {
- const module = await initialize();
+ const pkg = await initialize();
const output = document.getElementById("html-output") as HTMLIFrameElement;
const markdownInput = document.getElementById("markdown-input") as HTMLTextAreaElement;
const debouncedRender = debounce(async () => {
- const renderedHtml = await renderMarkdown(module, markdownInput.value);
+ const renderedHtml = await renderMarkdown(pkg.entrypoint!, markdownInput.value);
if (renderedHtml) {
output.srcdoc = renderedHtml;
}
Run It!
And finally, we can use the vite
dev server to try it out. The results should
be identical to before because we haven't changed any of the underlying
functionality.
$ npm run dev
VITE v5.0.4 ready in 107 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help
If things don't seem to work, check the Dev Tools console. An error like this
indicates we've run into SharedArrayBuffer
and Cross-Origin Isolation
issues!
Uncaught (in promise) Error: Unable to find "wasmer-examples/markdown-renderer" in the registry
at A.wbg.__wbg_new_ab87fd305ed9004b (Library.mjs:11:46497)
at 012cca42:0x2b7669
at 012cca42:0x377e8b
at 012cca42:0x142c64
at 012cca42:0x312190
at 012cca42:0x3b79d0
at 012cca42:0x3b0dae
at cA (Library.mjs:11:25455)
at C (Library.mjs:11:25290)
The fix is to make the vite
dev server set the Cross-Origin-Opener-Policy
and Cross-Origin-Embedder-Policy
headers. See Configure the Dev Server
for more.