Bundler on Wasm
Building Wasm modules is quite to creating Docker images: you start with the foundation, Ruby itself, then you add your dependencies via the bundle install
command.
In ruby.wasm, we have the rbwasm build
command that performs a similar task: generates a Wasm module with Ruby and all the specified dependencies from the Gemfile.
The command pulls the Ruby (MRI) source code, all the dependencies, and tries to compile and link them together into a single module. Why "tries"? Many dependencies rely on native (usually, C) extensions. Good news is that ruby.wasm can compile C extensions into WebAssembly. Not-so-good news is that not every extension is Wasm-compatible yet (e.g, nokogiri
).
Sometimes non-compilable extensions are optional transitive dependencies of the libraries you need (e.g., actioncable
depends on nio4r
, and rails
depends on actioncable
). To deal with this situation, wasmify-rails
provides a custom task, wasmify:build
, which wraps rbwasm build
and use the configuration file to exclude gems from the building process:
# config/wasmify.yml
exclude_gems:
- bigdecimal
- nio4r
- io-console
- psych
$ bin/rails wasmify:build
...
INFO: Packaging gem: tzinfo-data-1.2024.2
INFO: Packaging gem: wasmify-rails-0.1.2
INFO: Packaging setup.rb: bundle/setup.rb
INFO: Size: 78.24 MB
Thus, all the dependencies are precompiled ahead of the time.
Having such enhanced ruby.wasm
build, we now can inject the application source files into it and create the final module. For that, we have the wasmify:pack
command, which is wrapper over wasi-vfs.
Alternative: on-the-fly installing
It's possible to run Bundler within the core ruby.wasm environment without building a custom one. This approach is implemented in RunRuby.dev project and works pretty well. The only caveat is that you can not install any native extensions this way; they're just skipped.
Nevertheless, it turned out to be useful to testing and playground purposes. For example, here is the Action Controller bug reproduction template running in the browser: link.
Future: component model
With Component Model fully implemented, it would be possible to compile Ruby and dependencies indepedently and link them together without recompilation. That would drastically simplify the process and reduce the computational overhead. Just imaging a .wasm
module shipped from RubyGems and added to the application in runtime.