wesc 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 65b91a95acd3078a53de01dd1a5f73f99d0057c2bebffbdcc1340ca0c4195252
4
+ data.tar.gz: 109ceeea27a5f686e59a9d121af8974bd9e296b0b8be0af2e9a058dc9eb69290
5
+ SHA512:
6
+ metadata.gz: d4e239e1c836bece4d89fb23cd76caa418865df89fff7bdc091aa65f0c2790b615f68de025d5aea05f5c0018f33cc04860c15fb33e7dd6e3b0d8c9bbdae70671
7
+ data.tar.gz: 662b9ee4e0ed332b9b638b39f2854eb2ad0c810764b44e13401e4da93d7485fa76bebc9f4867ddd0c063a234dc4422d0062e285b5090c04f2428b5a5ab5677f6
data/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # wesc (Ruby)
2
+
3
+ Ruby bindings for [`wesc`](https://github.com/luwes/wesc)'s streaming
4
+ HTML/web-component bundler. The Rust core runs in-process via a native extension
5
+ — no subprocess, no WASM — so you can build and server-render web components
6
+ straight from a Ruby backend (Rack, Sinatra, Rails, plain WEBrick, …).
7
+
8
+ The extension is built with [Magnus](https://github.com/matsadler/magnus) on top
9
+ of [rb-sys](https://github.com/oxidize-rb/rb-sys), the same way the Node, Python,
10
+ and PHP bindings use napi-rs / PyO3 / ext-php-rs. The native module
11
+ (`Wesc::Native`, [`ext/wesc/src/lib.rs`](./ext/wesc/src/lib.rs)) is wrapped by a thin pure-Ruby
12
+ layer ([`lib/wesc.rb`](./lib/wesc.rb)) that exposes the idiomatic
13
+ keyword-argument API.
14
+
15
+ ```sh
16
+ gem install wesc
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```ruby
22
+ require "wesc"
23
+
24
+ # One-shot: returns the full HTML output as a (binary) String.
25
+ html = Wesc.build(["./index.html"], minify: true)
26
+ puts "#{html.bytesize} bytes"
27
+
28
+ # Streaming: low memory, chunk by chunk. The block receives each chunk as a
29
+ # String, then `nil` once to signal end-of-stream. Raising from the block
30
+ # stops the build.
31
+ Wesc.build_stream(["./index.html"]) do |chunk|
32
+ io.write(chunk) unless chunk.nil?
33
+ end
34
+ ```
35
+
36
+ ## API
37
+
38
+ - `Wesc.build(entry_points, outcss: nil, outjs: nil, minify: false) -> String`
39
+ - `Wesc.build_stream(entry_points, outcss: nil, outjs: nil, minify: false) { |chunk| ... } -> nil`
40
+
41
+ | Argument | Type | Notes |
42
+ | -------------- | --------------------- | -------------------------------------------------- |
43
+ | `entry_points` | `Array<String>` | First entry is the host document. |
44
+ | `outcss` | `String, nil` | Path to write the bundled CSS file. `nil` skips. |
45
+ | `outjs` | `String, nil` | Path to write the bundled JS file. `nil` skips. |
46
+ | `minify` | `Boolean` | Minify generated assets. Defaults to `false`. |
47
+ | `&block` | `{ \|String, nil\| }` | `build_stream` only. Gets each chunk, then `nil`. |
48
+
49
+ `build` returns a binary (`ASCII-8BIT`) `String`. `build_stream` yields each
50
+ chunk as a binary `String`, then yields `nil` once to mark end-of-stream — the
51
+ same trailing-`nil`/`None` convention as the Python and PHP bindings.
52
+
53
+ > The bundler keeps a process-global file/template cache, so builds should not
54
+ > run concurrently within a single process — serialize them (the
55
+ > [`examples/ruby-server`](https://github.com/luwes/wesc/tree/main/examples/ruby-server)
56
+ > demo does exactly this with a `Mutex`).
57
+
58
+ ## Building from source
59
+
60
+ This is an rb-sys gem. From this directory:
61
+
62
+ ```sh
63
+ bundle install
64
+ bundle exec rake compile # build the native extension into lib/wesc/
65
+ bundle exec rake test # compile (if needed) + run the test suite
66
+ bundle exec rake build # package the gem into pkg/
67
+ ```
68
+
69
+ `rake compile` runs `cargo` under the hood, so the prerequisites are:
70
+
71
+ - The [Rust toolchain](https://rustup.rs) (the repo pins a version via
72
+ `rust-toolchain.toml`).
73
+ - Ruby 3.0+ with development headers.
74
+ - A C toolchain and `libclang` for bindgen (Xcode Command Line Tools on macOS;
75
+ `build-essential` + `libclang-dev` on Linux).
76
+
77
+ `rake compile` is the canonical build: rb-sys drives `cargo` and passes the
78
+ Ruby-specific linker flags (on macOS, `-Wl,-undefined,dynamic_lookup`) a loadable
79
+ extension needs, since the Ruby symbols are resolved by the host process at
80
+ load time.
81
+
82
+ The crate (at [`ext/wesc`](./ext/wesc)) is also a member of the repo's Cargo
83
+ workspace, so `cargo check -p wesc-rb` typechecks it against the active Ruby on
84
+ your `PATH`. It's excluded from the workspace's `default-members`, so a bare
85
+ `cargo build` at the root won't try to build it (it needs Ruby headers +
86
+ libclang, and a plain `cargo build -p wesc-rb` won't *link* standalone — that's
87
+ what `rake compile` is for).
88
+
89
+ > Tip: with [rbenv](https://github.com/rbenv/rbenv) the bundled
90
+ > [`.ruby-version`](./.ruby-version) selects a Ruby 3 toolchain automatically in
91
+ > this directory.
92
+
93
+ See the repo README's [Ruby section](../../README.md) for the broader project
94
+ and `crates/wesc-rb/ext/wesc/src/lib.rs` for the binding source.
@@ -0,0 +1,32 @@
1
+ [package]
2
+ name = "wesc-rb"
3
+ version = "0.6.2" # x-release-please-version
4
+ authors = ["Wesley Luyten <me@wesleyluyten.com>"]
5
+ edition = "2021"
6
+ license = "MIT"
7
+ description = "Ruby bindings for the wesc web component bundler"
8
+ repository = "https://github.com/luwes/wesc"
9
+ publish = false
10
+
11
+ [lib]
12
+ # The cdylib is loaded by Ruby as a native extension (`require "wesc/wesc_rb"`).
13
+ # The lib name drives the extension's `Init_wesc_rb` symbol and the built
14
+ # `libwesc_rb` artifact, so it must stay in sync with the
15
+ # `#[magnus::init(name = "wesc_rb")]` in src/lib.rs and the
16
+ # `create_rust_makefile("wesc/wesc_rb")` call in extconf.rb. It deliberately
17
+ # differs from the core crate's `wesc` lib name so rb-sys's `cargo metadata`
18
+ # lookup resolves this crate and not the bundler core.
19
+ name = "wesc_rb"
20
+ crate-type = ["cdylib"]
21
+
22
+ [dependencies]
23
+ # Depend on the published bundler core by version so the gem is self-contained
24
+ # when built outside the repo (cross-compiled release gems and `gem install`
25
+ # from source both fetch it from crates.io). Inside this monorepo the root
26
+ # Cargo.toml's `[patch.crates-io]` redirects it to the local crates/wesc, so
27
+ # `rake compile` builds against the in-tree core. This requirement must track
28
+ # the core crate's version (the marker keeps release-please bumping it in
29
+ # lockstep); otherwise the patched local 0.x core fails to satisfy it and the
30
+ # whole workspace stops resolving.
31
+ wesc = "0.6.2" # x-release-please-version
32
+ magnus = "0.7"
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Build the native extension with rb-sys (cargo under the hood).
4
+ #
5
+ # The crate's Cargo.toml lives alongside this file (ext/wesc/), so rb-sys builds
6
+ # the local `wesc_rb` cdylib and installs it as `wesc/wesc_rb.{so,bundle}` —
7
+ # which lib/wesc.rb loads with `require_relative "wesc/wesc_rb"`.
8
+ require "mkmf"
9
+ require "rb_sys/mkmf"
10
+
11
+ create_rust_makefile("wesc/wesc_rb")
@@ -0,0 +1,116 @@
1
+ #![deny(clippy::all)]
2
+
3
+ //! Ruby bindings for `wesc`.
4
+ //!
5
+ //! Exposes the streaming web-component bundler to Ruby via [Magnus](https://github.com/matsadler/magnus)
6
+ //! (on top of [rb-sys](https://github.com/oxidize-rb/rb-sys)) so it can run
7
+ //! in-process on a Ruby server — no subprocess, no WASM.
8
+ //!
9
+ //! These are the *native* entry points. They live under the `Wesc::Native`
10
+ //! module and take plain positional arguments; the thin pure-Ruby wrapper in
11
+ //! `lib/wesc.rb` re-exposes them as `Wesc.build` / `Wesc.build_stream` with
12
+ //! idiomatic keyword arguments (mirroring the Python package's native module +
13
+ //! wrapper split).
14
+ //!
15
+ //! Two entry points, matching how a server typically consumes a build:
16
+ //! - [`build`] — returns the full output as a (binary) `String`.
17
+ //! - [`build_stream`] — yields each chunk to the block (low memory). The block
18
+ //! runs in Ruby, then receives `nil` once to signal end-of-stream.
19
+ //!
20
+ //! Magnus guards the FFI boundary: a Rust panic is caught and re-raised as a
21
+ //! Ruby exception rather than unwinding into the VM (which would be undefined
22
+ //! behavior), and a Ruby exception raised by the block surfaces back to the
23
+ //! caller as a normal `Result::Err`.
24
+ //!
25
+ //! Note: the underlying bundler keeps a process-global file/template cache, so
26
+ //! builds should not run concurrently within a single process — serialize them
27
+ //! (the streaming server example does exactly this).
28
+
29
+ use magnus::{function, prelude::*, Error, RString, Ruby, Value};
30
+ use wesc::{build as wesc_build, BuildOptions};
31
+
32
+ /// Assemble [`BuildOptions`] from the native (positional) arguments.
33
+ fn collect_options(
34
+ entry_points: Vec<String>,
35
+ outcss: Option<String>,
36
+ outjs: Option<String>,
37
+ minify: bool,
38
+ ) -> BuildOptions {
39
+ BuildOptions {
40
+ entry_points,
41
+ outcss,
42
+ outjs,
43
+ minify,
44
+ }
45
+ }
46
+
47
+ /// Build the entry points and return the full HTML output as a binary `String`.
48
+ ///
49
+ /// Exposed to Ruby as `Wesc::Native.build`; see `Wesc.build` in `lib/wesc.rb`
50
+ /// for the public keyword-argument API.
51
+ fn build(
52
+ ruby: &Ruby,
53
+ entry_points: Vec<String>,
54
+ outcss: Option<String>,
55
+ outjs: Option<String>,
56
+ minify: bool,
57
+ ) -> RString {
58
+ let options = collect_options(entry_points, outcss, outjs, minify);
59
+
60
+ let mut output: Vec<u8> = Vec::new();
61
+ wesc_build(options, &mut |chunk: &[u8]| {
62
+ output.extend_from_slice(chunk);
63
+ });
64
+
65
+ ruby.str_from_slice(&output)
66
+ }
67
+
68
+ /// Stream the build to the block, chunk by chunk, for low-memory output.
69
+ ///
70
+ /// The block is called once per output chunk with the chunk as a binary
71
+ /// `String`, then once with `nil` to signal the end of the stream. If the block
72
+ /// raises, no further chunks are delivered and the exception propagates out.
73
+ ///
74
+ /// Exposed to Ruby as `Wesc::Native.build_stream`; see `Wesc.build_stream` in
75
+ /// `lib/wesc.rb` for the public keyword-argument API.
76
+ fn build_stream(
77
+ ruby: &Ruby,
78
+ entry_points: Vec<String>,
79
+ outcss: Option<String>,
80
+ outjs: Option<String>,
81
+ minify: bool,
82
+ ) -> Result<(), Error> {
83
+ let block = ruby.block_proc()?;
84
+ let options = collect_options(entry_points, outcss, outjs, minify);
85
+
86
+ // Remember the first block error so we can stop yielding and surface it.
87
+ let mut pending_error: Option<Error> = None;
88
+ wesc_build(options, &mut |chunk: &[u8]| {
89
+ if pending_error.is_some() {
90
+ return;
91
+ }
92
+ let bytes = ruby.str_from_slice(chunk);
93
+ if let Err(err) = block.call::<_, Value>((bytes,)) {
94
+ pending_error = Some(err);
95
+ }
96
+ });
97
+
98
+ if let Some(err) = pending_error {
99
+ return Err(err);
100
+ }
101
+
102
+ // Signal end-of-stream.
103
+ block.call::<_, Value>((ruby.qnil(),))?;
104
+ Ok(())
105
+ }
106
+
107
+ /// Initialize the native extension: define `Wesc::Native` and its module
108
+ /// functions. The pure-Ruby `Wesc` wrapper (lib/wesc.rb) builds on these.
109
+ #[magnus::init(name = "wesc_rb")]
110
+ fn init(ruby: &Ruby) -> Result<(), Error> {
111
+ let module = ruby.define_module("Wesc")?;
112
+ let native = module.define_module("Native")?;
113
+ native.define_singleton_method("build", function!(build, 4))?;
114
+ native.define_singleton_method("build_stream", function!(build_stream, 4))?;
115
+ Ok(())
116
+ }
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wesc
4
+ VERSION = "0.6.2"
5
+ end
data/lib/wesc.rb ADDED
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ # WeSC — We are the Superlative Components.
4
+ #
5
+ # A streaming HTML / web-component bundler. This gem exposes the Rust core (via
6
+ # a native extension) so it can run in-process on a Ruby server — no subprocess,
7
+ # no WASM.
8
+ #
9
+ # require "wesc"
10
+ #
11
+ # # One-shot: returns the full HTML output as a (binary) String.
12
+ # html = Wesc.build(["./index.html"], minify: true)
13
+ #
14
+ # # Streaming: low memory, chunk by chunk. The block gets each chunk as a
15
+ # # String, then `nil` once to signal end-of-stream.
16
+ # Wesc.build_stream(["./index.html"]) { |chunk| ... }
17
+ #
18
+ # See https://github.com/luwes/wesc for the full documentation.
19
+
20
+ require_relative "wesc/version"
21
+ # The native extension (defines Wesc::Native). Built from crates/wesc-rb via
22
+ # rb-sys; see the gem's Rakefile (`rake compile`).
23
+ require_relative "wesc/wesc_rb"
24
+
25
+ module Wesc
26
+ module_function
27
+
28
+ # Build the entry points and return the full HTML output as a (binary) String.
29
+ #
30
+ # html = Wesc.build(["./index.html"], minify: true)
31
+ #
32
+ # @param entry_points [Array<String>] entry point paths; the first is the host
33
+ # document.
34
+ # @param outcss [String, nil] optional path to write the bundled CSS file.
35
+ # @param outjs [String, nil] optional path to write the bundled JS file.
36
+ # @param minify [Boolean] minify generated JS/CSS assets. Defaults to false.
37
+ # @return [String] the rendered HTML (ASCII-8BIT / binary encoding).
38
+ def build(entry_points, outcss: nil, outjs: nil, minify: false)
39
+ Native.build(Array(entry_points), outcss, outjs, minify ? true : false)
40
+ end
41
+
42
+ # Stream the build to a block, chunk by chunk, for low-memory output.
43
+ #
44
+ # The block is called with each chunk as a String, then once with `nil` to
45
+ # signal the end of the stream. Raising from the block stops the build and the
46
+ # exception propagates out of this method.
47
+ #
48
+ # Wesc.build_stream(["./index.html"]) do |chunk|
49
+ # io.write(chunk) unless chunk.nil?
50
+ # end
51
+ #
52
+ # @param entry_points [Array<String>] entry point paths; the first is the host
53
+ # document.
54
+ # @param outcss [String, nil] optional path to write the bundled CSS file.
55
+ # @param outjs [String, nil] optional path to write the bundled JS file.
56
+ # @param minify [Boolean] minify generated JS/CSS assets. Defaults to false.
57
+ # @yieldparam chunk [String, nil] each output chunk, then `nil` at end-of-stream.
58
+ # @return [void]
59
+ def build_stream(entry_points, outcss: nil, outjs: nil, minify: false, &block)
60
+ raise ArgumentError, "Wesc.build_stream requires a block" unless block
61
+
62
+ Native.build_stream(Array(entry_points), outcss, outjs, minify ? true : false, &block)
63
+ end
64
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wesc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.2
5
+ platform: ruby
6
+ authors:
7
+ - Wesley Luyten
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-06-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rb_sys
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.9'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake-compiler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.0'
69
+ description: Ruby bindings for wesc's streaming HTML/web-component bundler. The Rust
70
+ core runs in-process via a native extension — no subprocess, no WASM.
71
+ email:
72
+ - me@wesleyluyten.com
73
+ executables: []
74
+ extensions:
75
+ - ext/wesc/extconf.rb
76
+ extra_rdoc_files: []
77
+ files:
78
+ - README.md
79
+ - ext/wesc/Cargo.toml
80
+ - ext/wesc/extconf.rb
81
+ - ext/wesc/src/lib.rs
82
+ - lib/wesc.rb
83
+ - lib/wesc/version.rb
84
+ homepage: https://github.com/luwes/wesc
85
+ licenses:
86
+ - MIT
87
+ metadata:
88
+ homepage_uri: https://github.com/luwes/wesc
89
+ source_code_uri: https://github.com/luwes/wesc
90
+ changelog_uri: https://github.com/luwes/wesc/blob/main/CHANGELOG.md
91
+ cargo_crate_name: wesc-rb
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '3.0'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: 3.3.11
106
+ requirements: []
107
+ rubygems_version: 3.5.22
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: We are the Superlative Components! — a streaming HTML/web-component bundler.
111
+ test_files: []