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.