Announcing WinterJS

The most performant JavaScript Service Workers server thanks to Rust and SpiderMonkey

syrusakbary avatar
Syrus Akbary

Founder & CEO

wasmer edge

October 27, 2023

arrowBack to articles
Post cover image

Follow up the WinterJS series reading about the recent production-ready WinterJS 1.0 release here.

Today we are incredibly excited to announce WinterJS (wasmer/winterjs package).

WinterJS is a JavaScript Service Workers server written in Rust, that uses the SpiderMonkey engine to execute JavaScript (the same engine that Firefox uses). We chose to follow the WinterCG specification to aim for maximum compatibility with other services such as Cloudflare Workers, Deno Deploy and Vercel (hence the name WinterJS).

WinterJS is not only blazing fast™️ but can also be compiled to WebAssembly thanks to WASIX and thus also run fully with Wasmer.

Let's see how it works. We'll start by creating a simple serviceworker.js file that just returns a simple "hello world";

addEventListener('fetch', (req) => {
  req.respondWith(new Response('hello'));

Running it with WinterJS is as simple as this:

$ wasmer run wasmer/winterjs --net --mapdir /app:. /app/serviceworker.js

WinterJS can also be run natively with Rust (cargo install --git && winterjs serviceworker.js). You can find the source code of WinterJS in the GitHub repo:

Thanks to the WASIX capabilities of WinterJS, the JavaScript service worker can also be deployed to Wasmer Edge. Check the working demo here:

And now that you have seen a sneak peak on how to use WinterJS, lets do a deep dive on our journey building it.

Choosing the JS engine

Before starting on the quest of creating a JavaScript Service Workers server, we analyzed the Javacript runtimes that we could use.

Here are the main requirements we have for such JavaScript runtime:

  • Speed: It should be fast to run
  • Wasm-compatible: It should be able to run without restrictions in a Wasm environment (such as Wasmer)
  • Development time: We should be able to iterate fast on it

And here are the JS runtimes that we analyzed:

  • QuickJS. Challenges:
    • We will need to implement all the JS apis diff (, addEventListener, …)
    • We need to implement the serviceWorker API entirely in C
  • Static Hermes. Challenges:
    • Node.js polyfills not available in static mode
    • Not a lot of functions (such as http calls) are available in the polyfill
    • Had to make it compile to Wasm with WASIX
  • Bun (JavascriptCore). Challenges:
    • Zig not fully supporting WASIX
    • Compiling JavascriptCore to WASIX is possible (was done before), but not trivial
  • MozJS (SpiderMonkey). Challenges:
    • We will need to implement all the JS apis diff (, addEventListener, …)
    • We need to implement the serviceWorker API (in Rust)
    • We will need to plug the service worker with a WASIX http server
  • Node.js (v8). Challenges:
    • Compile v8 in jitless mode to Wasm is an unknown-unknown

Using SpiderMonkey with mozjs

After a few runtime trials we set on SpiderMonkey as the most reasonable approach that fitted our tight timeline.

So we begin porting. We started with a fork of mozjs that supported a new compilation tier called PBI (Portable Baseline Interpreter).

After some work on the mozjs build system to target WASIX, we were able to bypass most of the issues, except one: the bindings generation.

The bindings that allow using the SpiderMonkey C++ API from Rust were automatically generated using c-bindgen. Plugging those bindings onto WASIX was a challenge so we simply decided to target a 32 bit system and modify them by hand (a 32,000 file!) to target WASIX.

And voilá… everything worked!

However, after adding a few missing resources to JS, we realized that perhaps mozjs didn’t have the easiest API to use:

unsafe extern "C" fn base64_encode(cx: *mut JSContext, argc: u32, vp: *mut Value) -> bool {
    let args = CallArgs::from_vp(vp, argc);

    if args.argc_ < 1 {
        return false;

    let source = js_try!(cx, raw_handle_to_string(cx, args.get(0)));
    let result = ::base64::engine::general_purpose::STANDARD.encode(bytes);

    rooted!(in(cx) let mut rval = UndefinedValue());
    result.to_jsval(cx, rval.handle_mut());



Using SpiderMokey with spiderfire

Thankfully, the spiderfire project had been working on improving the API surface for using SpiderMonkey from Rust.

So the example laid out before now looks way simpler and easier to read/maintain:

fn btoa<'cx>(val: String) -> String {
    let bytes = val.as_bytes();

Deploying to Wasmer Edge

Compiling WinterJS to WASIX was challenging, but completely worth it. Thanks to its WASIX capabilities we can now run any Javascript Service Workers workloads in Wasmer Edge.

We have put together an in depth tutorial on how to use Javascript Service Workers in Wasmer Edge... please check it out!

We believe WinterJS will enable many new use cases. For example, running Service Workers natively in your IoT device (where Node is too heavy to run), or even in your browser.

At Wasmer we are incredibly excited to see how you will use WinterJS.

WinterJS on GitHub:

About the Author

Syrus Akbary is an enterpreneur and programmer. Specifically known for his contributions to the field of WebAssembly. He is the Founder and CEO of Wasmer, an innovative company that focuses on creating developer tools and infrastructure for running Wasm

Syrus Akbary avatar
Syrus Akbary
Syrus Akbary

Founder & CEO

Read more

wasmer edgejavascriptWinterJS

How fast is WinterJS vs alternatives? Blazing-fast

Syrus AkbaryNovember 1, 2023

engineeringwasmer edgeedge computing

The Cloud is dead, long live the Cloud! Announcing Wasmer Edge

Syrus AkbaryJune 15, 2023

wasmerwasmer edgerustprojectsedgeweb scraper

Build a Web Scraper in Rust and Deploy to Wasmer Edge

RudraAugust 14, 2023

wasmer runtimejavascriptwasmer-jsbrowser

Introducing the new Wasmer JS SDK

Michael BryanDecember 13, 2023