<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: tbxyz</title>
    <description>The latest articles on DEV Community by tbxyz (@tbxyz_0).</description>
    <link>https://dev.to/tbxyz_0</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3305653%2Fc5975ec1-adab-47b4-b518-433d123dfebd.png</url>
      <title>DEV Community: tbxyz</title>
      <link>https://dev.to/tbxyz_0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tbxyz_0"/>
    <language>en</language>
    <item>
      <title>rakers — a headless JS renderer in Rust</title>
      <dc:creator>tbxyz</dc:creator>
      <pubDate>Tue, 19 May 2026 20:17:57 +0000</pubDate>
      <link>https://dev.to/tbxyz_0/rakers-a-headless-js-renderer-in-rust-23m4</link>
      <guid>https://dev.to/tbxyz_0/rakers-a-headless-js-renderer-in-rust-23m4</guid>
      <description>&lt;p&gt;A lot of useful content on the web only exists after JavaScript runs. Server-side rendering has made a comeback, but plenty of sites still ship a near-empty HTML skeleton and populate it entirely client-side. If you want to extract that content — for archiving, testing, or processing — you need to run the JavaScript first.&lt;/p&gt;

&lt;p&gt;The standard answer is a headless browser: Playwright, Puppeteer, or a self-hosted Chrome. These work, but they are heavy. Chrome's footprint is around 300 MB, startup takes one to two seconds, and running it in CI requires care. For many use cases the full browser is overkill: you don't need CSS layout, GPU compositing, or WebGL. You just need the DOM to exist after the scripts have run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tbro/rakers" rel="noopener noreferrer"&gt;rakers&lt;/a&gt; is an attempt to find the floor on that problem. It is a single ~10 MB binary that parses HTML, runs the JavaScript, and returns the post-execution HTML.&lt;br&gt;
How it works&lt;/p&gt;

&lt;p&gt;The pipeline has three stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Parse — html5ever (Servo's HTML5 parser, published as a standalone crate) turns the input into a DOM tree.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Execute — &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags are collected in document order, inline and external alike. External scripts are fetched synchronously. All scripts are evaluated in a sandboxed JS context that exposes a browser-compatible global environment: document, window, console, XMLHttpRequest, localStorage, setTimeout, and more.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Serialize — The post-execution DOM is serialized back to HTML and written to stdout (or a file).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The JS engine is QuickJS via the rquickjs crate. QuickJS is ES2023, compact, and embeds cleanly into Rust without much ceremony. For environments without a C compiler there is an optional boa_engine backend, a pure-Rust JS engine with a smaller compatibility surface but zero native dependencies.&lt;br&gt;
The DOM stub&lt;/p&gt;

&lt;p&gt;The trickiest part is not the JS engine — it is the fake browser environment. Frameworks like React, Vue, and Svelte expect a fairly complete DOM API. Since rakers has no real layout engine, those APIs are implemented as a JavaScript stub in bootstrap.js that is injected before any page script runs.&lt;/p&gt;

&lt;p&gt;The stub covers the most common patterns: element creation and mutation, getElementById, querySelector, querySelectorAll, event listener registration, classList, localStorage, history.pushState, and XHR. XHR is backed by a synchronous Rust fetch (ureq) so frameworks that load templates at runtime via XHR — like RiotJS — actually retrieve them.&lt;/p&gt;

&lt;p&gt;Inevitably there are gaps. Anything that requires layout information (offsetWidth, getBoundingClientRect) returns zero. Native ES modules are skipped. The stub is good enough to run 21 of the 23 TodoMVC implementations, which provides a reasonable compatibility benchmark across React, Vue, Angular, Svelte, Preact, Mithril, Elm, Backbone, Ember, Knockout, and others.&lt;br&gt;
Using it&lt;/p&gt;

&lt;h2&gt;
  
  
  Render a URL
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;rakers https://example.com&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Render a local file
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;rakers page.html&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pipe HTML in
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;curl -s https://example.com | rakers&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Route traffic through Tor
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;rakers --proxy socks5://127.0.0.1:9050 https://example.com&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Filter to a CSS selector
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;rakers --selector "article.post" https://example.com&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Installation instructions in the &lt;a href="https://github.com/tbro/rakers#install" rel="noopener noreferrer"&gt;README&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is not
&lt;/h2&gt;

&lt;p&gt;rakers is not a browser. It has no CSS engine, no layout, no WebGL, no IndexedDB, no service workers. Anything that depends on those will not work. Sites that fingerprint the JS environment or require a real navigator.plugins array will also fail. For those cases a real headless browser is the right tool.&lt;/p&gt;

&lt;p&gt;The value proposition is narrow but real: for sites that render via a self-contained JS bundle with no exotic browser dependencies, rakers is faster, smaller, and simpler to deploy than anything that ships a copy of Chromium.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>javascript</category>
      <category>release</category>
    </item>
    <item>
      <title>A learning experience with NetworkManager and zbus</title>
      <dc:creator>tbxyz</dc:creator>
      <pubDate>Sun, 29 Jun 2025 14:16:31 +0000</pubDate>
      <link>https://dev.to/tbxyz_0/a-learning-experience-with-networkmanager-and-zbus-1emk</link>
      <guid>https://dev.to/tbxyz_0/a-learning-experience-with-networkmanager-and-zbus-1emk</guid>
      <description>&lt;p&gt;Traveling heavily recently, I've become fatigued from typing all the WiFi passphrases. My phone can happily connect via a QR code. Why does my laptop required so much typing, I asked. So I proposed a simple project.&lt;/p&gt;

&lt;p&gt;Researching how I might use my web-cam to connect to WiFi networks, I came across the &lt;a href="https://rbs.io/2024/07/network-manager-and-rusts-zbus/" rel="noopener noreferrer"&gt;Network Manager and Rust's Zbus article on rbs.io&lt;/a&gt;. I use &lt;code&gt;NewtworkManager&lt;/code&gt; and I enjoy working with &lt;code&gt;rust&lt;/code&gt;. I know next to nothing about DBus, but I thought some knowledge of hacking Linux networks couldn't do me any harm. Besides I had some free time.&lt;/p&gt;

&lt;p&gt;The above article set me on the right path. The &lt;code&gt;zbus&lt;/code&gt; documentation is still a little obscure in some salient details, however. Hopefully this post will help fill those in. The finished code is hosted in &lt;a href="https://github.com/tbro/scampi" rel="noopener noreferrer"&gt;the scampi repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I first needed to generate a &lt;code&gt;settings.rs&lt;/code&gt; with &lt;code&gt;zbus-xmlgen&lt;/code&gt; as described in the above article. It is straightforward to create a connection by building the nested structures of &lt;code&gt;HashMap&lt;/code&gt;s also described there. While that solution works, it is tedious, unsightly and doesn't make the most of rust's type system. &lt;a href="https://github.com/dbus2/zbus/issues/312" rel="noopener noreferrer"&gt;Finding the issue for using &lt;code&gt;DeserializeStruct&lt;/code&gt; with nested structs closed&lt;/a&gt;, I hoped there might now be a more ergonomic way forward.&lt;/p&gt;

&lt;p&gt;With the help of the &lt;a href="https://dbus2.github.io/zbus/faq.html" rel="noopener noreferrer"&gt;zbus book&lt;/a&gt; I managed to piece together a solution that feels a little more rust-like. Of course there are many pieces to the puzzle. You need serde and the zvariant::Type macro. But all that is gleaned fairly easily from the documentation, I think. The more salient discovery was that zbus allows declaring &lt;a href="https://www.alteeve.com/w/List_of_DBus_data_types" rel="noopener noreferrer"&gt;DBbus signatures&lt;/a&gt; on your types. This is done via a macro, like so:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#[zvariant(signature = "a{sa{sv}}")]`
struct MyType{..}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Declaring the correct signature(&lt;code&gt;a{sa{sv}}&lt;/code&gt;) on the struct passed to &lt;code&gt;NetworkManager&lt;/code&gt;'s &lt;code&gt;Settings.add_connection()&lt;/code&gt; allowed it to navigate &lt;code&gt;Dbus&lt;/code&gt;'s type system. Its children don't have any children of their own, so &lt;code&gt;SerializedDict&lt;/code&gt; and &lt;code&gt;DeserializeDict&lt;/code&gt; works fine on them.&lt;/p&gt;

&lt;p&gt;For a more complete example you can have a look at &lt;a href="https://github.com/tbro/scampi/blob/9642b1a0ee6175469803bf572e13c90915b7d9e9/src/network_manager/mod.rs#L63-L73" rel="noopener noreferrer"&gt;the code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I believe I could go further and replace some fields defined as &lt;code&gt;String&lt;/code&gt; with more appropriate types, but it's one more thing to learn and I think this is good enough for now.&lt;/p&gt;

&lt;p&gt;Originally published in &lt;a href="https://tbro.github.io/posts/2025-06-27-network-manager-and-zbus.html" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>rust</category>
      <category>cli</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
