Improving wasm imports with wrangler and Cloudflare Workers
WebAssembly (wasm) has been supported by Cloudflare Workers since 2018. Enabling WebAssembly support has extended the functionality of Workers so much in the last 5 years, enabling use-cases that simply weren't feasible in JavaScript beforehand.
However, the workflow and DX for importing wasm into your Worker via wrangler has been less than ideal, especially when working with third-party wasm libraries.
TL;DR
You can now import wasm files in wrangler >= 3.15.0 like this:
import wasm from '@resvg/resvg-wasm/index_bg.wasm'
instead of like this:
import wasm from '../../node_modules/@resvg/resvg-wasm/index_bg.wasm'
Prior art
The most common solutions in the past for importing wasm from a third party library would be to either:
- copy the bundled
.wasm
file out of thenode_modules
directory and into your ownsrc
(etc.) dir for easy import - or, import using a relative path
The first option felt very hacky to me, so I opted for the second. Some folks were even making this a part of their build step, and while this felt a little better, it still felt unnecessary to me.
Initial code
My initial code ended up looking something like this:
import wasm from '../../node_modules/@resvg/resvg-wasm/index_bg.wasm'
It worked well, and was better than copying the wasm
file into my own src
directory, but it still felt a little unergonomic and flimsy. Looking into the package some more, I saw it had entrypoints setup via exports
in their package.json
, so I hoped I would be able to make use of this and simply import from @resvg/resvg-wasm/index_bg.wasm
, however this did not work and resulted in an error like this from wrangler:
✘ [ERROR] ENOENT: no such file or directory, open 'E:\<path>\src\@resvg\resvg-wasm' [plugin wrangler-module-collector]
For some reason, it was being treated as a relative path.
Investigation
plugin wrangler-module-collector
gave me some insight that this was a custom esbuild plugin wrangler was using, so I started investigating there. I quickly found the custom esbuild plugin wrangler had implemented in their source:
This plugin was handling all imports to **/*.wasm
, so that they'd be bundled into the Worker as needed, but was treating every path as a relative one.
I figured it should be pretty easy to add npm
resolution support to this so I could import @resvg/resvg-wasm/index_bg.wasm
as originally desired, so I dove in and started hacking away at Wrangler.
Implementation
Wrangler is part of Cloudflare's workers-sdk
repository, powered by Turbo and pnpm, so it was relatively straight forward to jump in and start working. They have a detailed CONTRIBUTING guide too, which I was already somewhat familiar with.
I was looking for an easy way to use the entrypoint exposed via exports
in the package, without having to re-implement all of that logic myself - there's so much build tooling in the JavaScript ecosystem, so someone had to have built something for this already. I quickly ran across resolve.exports
, a package used by so much of JS ecosystem including Vite, Jest, and much more, so I was confident it would work well here.
Submitting my changes
You can find my full Pull Request below:
I was very happy with how it turned out, and after a little back and forth with the awesome wrangler team, it was well tested, and ready for merge.
Final code
As of wrangler 3.15.0, you can now simply import wasm files from packages, assuming they setup an entrypoint via exports
in their package.json
like this:
import wasm from '@resvg/resvg-wasm/index_bg.wasm'
A small win, but much nicer ergonomics and developer experience when working with wasm in Cloudflare Workers! 🎉