Ride The Streetcar 🚋
All aboard the server 🔀 client train! #
Drive surgical state changes to your frontend DOM and JavaScript objects…from any backend (Ruby & JavaScript supported out of the gate). Fully compatible with form fetch techniques as well as htmx via a custom extension.
Only 2kB minzipped with zero dependencies!
Elevator Pitch 🛗 #
You are but a humble web server. You have a received a request from a client which you know is a web browser. You would like to instruct the web browser what to do, for it lacks a complex preloaded JavaScript application and relies on you for helpful logic (aww 🥰). How do you do that? Isn’t it strange that there is no native web API for pushing DOM operations from server to client?
Well, that’s where Ride The Streetcar comes in! 🔔 If you’re a Ruby server, you might do this:
streetcar = RideTheStreetcar.dsl
response = streetcar.ride do
query_selector("#progress-message").inner_html =
"<em>Yay!</em> Your process is complete. <button>Done</button>"
end
Equivalent if you’re a JavaScript server:
const streetcar = RideTheStreetcar.dsl()
const response = streetcar.ride(sc => {
sc.querySelector("#progress-message").innerHTML =
"<em>Yay!</em> Your process is complete. <button>Done</button>"
})
And on the client, process with fetch in a two-liner:
const html = await fetch("/path/of/backend/fragment").then(r => r.text())
Streetcar.run(html)
What is this “HTML fragment”, you may ask? It’s a payload of serialized statements in the form of custom elements…a “streetcar line” if you will (line breaks/indentation added for visual clarity):
<sc-line>
<sc-query-selector selectors="#progress-message">
<sc-inner-html>
<template>
<em>Yay!</em> Your process is complete. <button>Done</button>
</template>
</sc-inner-html>
</sc-query-selector>
</sc-line>
This will select the element in the DOM with id="progress-message" and set its inner HTML to the Yay! markup provided.
These custom elements aren’t “web components” in the traditional sense. They are non-visual and self-executing in order to translate the serialized logic into JavaScript statements when processed by the client.
You may be familiar with “pull” based hypermedia tools such htmx (fragment-handling logic is defined client-side) or “push” based tools like Hotwired Turbo (fragment-handling logic is defined server-side). Streetcar is a push-based tool but happy to coordinate with existing pull-based tools—in fact, you can run Streetcar as an htmx extension!
If you’re wondering what if I don’t want to push mere chucks of HTML from the server to the client? My app is more sophisticated than that! I’m building full web component trees and using optimistic UI techniques, etc. that’s fine too! Ride The Streetcar can push method calls on specific elements or globally-accessible JavaScript objects, set properties, dispatch events, or even perform completely custom operations you define ahead of time!
Ride The Streetcar doesn’t (currently) deal directly with async delivery mechanisms like SSE (Server-Sent Events) or Websockets. But you can certainly use those techniques and bring Streetcar along for the ride! (pun intended!)
Continue reading the documentation for installation & usage:
A Brief History 🕰️ #
I (Jared White) was the lead maintainer of the Xajax project (originally created by J. Max Wilson) in 2006. It provided a JavaScript client and a PHP server which let frontend DOM operations and functions be run via backend commands. Much later in 2021-2022, I was a contributor to CableReady and its new CableCar feature which let frontend DOM operations and functions be run via backend commands. After that, I switched to using Turbo Stream Actions which let fronten—I think you see where this is going. In 2024, I wrote about a pattern called Action Web Components which let fron—you know the drill!
At this point, I decided this pattern is simply too good to leave to happenstance. Ride The Streetcar (Streetcar for short) is my attempt to create the future-proof and framework-agnostic “Xajax” I’d always dreamed of. 🛤️
“Clang, clang, clang” went the trolley
“Ding, ding, ding” went the bell
“Zing, zing, zing” went my heartstrings
From the moment I saw him, I fell
Community 👋😃 #
Join the Human Web Collective Discord where you can meet friendly fellow developers who love investigating and using “vanilla-adjacent” frontend APIs and ask questions about this project and beyond.
Interested in contributing to Ride The Streetcar? Bug reports, feature requests, and PRs most welcome. (Full human authorship only, please!) There are three repos to choose from depending on the issue:
- streetcar-elements (Frontend Library)
- ride_the_streetcar (Ruby Backend)
- ride-the-streetcar-js (JavaScript/Node Backend)