wesc (Ruby)
Ruby bindings for wesc's streaming
HTML/web-component bundler. The Rust core runs in-process via a native extension
— no subprocess, no WASM — so you can build and server-render web components
straight from a Ruby backend (Rack, Sinatra, Rails, plain WEBrick, …).
The extension is built with Magnus on top
of rb-sys, the same way the Node, Python,
and PHP bindings use napi-rs / PyO3 / ext-php-rs. The native module
(Wesc::Native, ext/wesc/src/lib.rs) is wrapped by a thin pure-Ruby
layer (lib/wesc.rb) that exposes the idiomatic
keyword-argument API.
gem install wesc
Usage
require "wesc"
# One-shot: returns a Wesc::Result with the HTML plus the bundled CSS/JS.
# Pass outcss:/outjs: to also get the bundles (an empty string keeps them in
# memory only; a path additionally writes the file).
result = Wesc.build(["./index.html"], outcss: "", outjs: "", minify: true)
puts "#{result.html.bytesize} bytes of HTML"
puts result.css # the bundled CSS (nil when outcss: was not requested)
puts result.js # the bundled JS (nil when outjs: was not requested)
# Streaming: low memory, chunk by chunk. The block receives each chunk as a
# String, then `nil` once to signal end-of-stream. Raising from the block
# stops the build.
Wesc.build_stream(["./index.html"]) do |chunk|
io.write(chunk) unless chunk.nil?
end
API
Wesc.build(input, outcss: nil, outjs: nil, minify: false) -> Wesc::ResultWesc.build_stream(input, outcss: nil, outjs: nil, minify: false) { |chunk| ... } -> nil
| Argument | Type | Notes |
|---|---|---|
input |
Array<String> |
First entry is the host document. |
outcss |
String, nil |
Request the bundled CSS. Path writes the file; "" is in-memory only; nil skips. |
outjs |
String, nil |
Request the bundled JS. Path writes the file; "" is in-memory only; nil skips. |
minify |
Boolean |
Minify generated assets. Defaults to false. |
&block |
`{ \ | String, nil\ |
build returns a Wesc::Result (Struct.new(:html, :css, :js)): html is a
binary (ASCII-8BIT) String, and css/js are binary Strings when
requested via outcss/outjs or nil otherwise. outcss/outjs still write
the bundle to disk when given a non-empty path; an empty string returns it in
memory only. build_stream yields each HTML chunk as a binary String, then
yields nil once to mark end-of-stream — the same trailing-nil/None
convention as the Python and PHP bindings.
The bundler keeps a process-global file/template cache, so builds should not run concurrently within a single process — serialize them (the
examples/ruby-serverdemo does exactly this with aMutex).
Building from source
This is an rb-sys gem. From this directory:
bundle install
bundle exec rake compile # build the native extension into lib/wesc/
bundle exec rake test # compile (if needed) + run the test suite
bundle exec rake build # package the gem into pkg/
rake compile runs cargo under the hood, so the prerequisites are:
- The Rust toolchain (the repo pins a version via
rust-toolchain.toml). - Ruby 3.0+ with development headers.
- A C toolchain and
libclangfor bindgen (Xcode Command Line Tools on macOS;build-essential+libclang-devon Linux).
rake compile is the canonical build: rb-sys drives cargo and passes the
Ruby-specific linker flags (on macOS, -Wl,-undefined,dynamic_lookup) a loadable
extension needs, since the Ruby symbols are resolved by the host process at
load time.
The crate (at ext/wesc) is also a member of the repo's Cargo
workspace, so cargo check -p wesc-rb typechecks it against the active Ruby on
your PATH. It's excluded from the workspace's default-members, so a bare
cargo build at the root won't try to build it (it needs Ruby headers +
libclang, and a plain cargo build -p wesc-rb won't link standalone — that's
what rake compile is for).
Tip: with rbenv the bundled
.ruby-versionselects a Ruby 3 toolchain automatically in this directory.
See the repo README's Ruby section for the broader project
and crates/wesc-rb/ext/wesc/src/lib.rs for the binding source.