Migrating the Editor to a Wasmer Package

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
  • 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.

    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 "" as user "michael-f-bryan"

    Otherwise, you will need to log in.

    $ wasmer login
    Opening auth link in your default browser:
    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;
         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
    The Markdown Editor

    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.


    Source Code