👋
Welcome to TurboBoost Streams
TurboBoost Streams extends Turbo Streams to give you full control of the browser's Document Object Model (DOM).
turbo_stream.invoke "console.log", args: ["Hello World!"]
Thats right!
You can invoke
any DOM method on the client with Turbo Streams.
Table of Contents
- Why boosted Streams?
- Sponsors
- Community
- Dependencies
- Installation
- Setup
- Usage
- FAQ
- A Word of Warning
- Developing
- Deploying
- Releasing
- About TurboBoost
- License
Why boosted Streams?
Turbo Streams intentionally restrict official actions to CRUD related activity. These official actions work well for a considerable number of use cases. We recommend that you push Turbo Streams as far as possible before reaching for boosted streams.
If you find that CRUD isn't enough, boosted streams are there to handle pretty much everything else.
Sponsors
Proudly sponsored by
Community
Come join the party with over 2200+ like-minded friendly Rails/Hotwire enthusiasts on our Discord server.
Dependencies
- rails
>=6.1
- turbo-rails
>=1.1
- @hotwired/turbo
>=7.2.0
- @hotwired/turbo-rails
>=7.2.0
Installation
Be sure to install the same version for each libary.
bundle add "turbo_boost-streams --version VERSION"
yarn add "@turbo-boost/streams@VERSION --exact"
Setup
Import and intialize Turbo Boost Streams in your application.
# Gemfile
gem "turbo-rails", ">= 1.1", "< 2"
+gem "turbo_boost-streams", "~> VERSION"
# package.json
"dependencies": {
"@hotwired/turbo-rails": ">=7.2",
+ "@turbo-boost/streams": "^VERSION"
# app/javascript/application.js
import '@hotwired/turbo-rails'
+import '@turbo-boost/streams'
Usage
Manipulate the DOM from anywhere you use official Turbo Streams. The possibilities are endless. Learn more about the DOM at MDN.
turbo_stream.invoke "console.log", args: ["Hello World!"]
Method Chaining
You can use dot notation or selectors and even combine them!
turbo_stream
.invoke("document.body.insertAdjacentHTML", args: ["afterbegin", "<h1>Hello World!</h1>"]) # dot notation
.invoke("setAttribute", args: ["data-turbo-ready", true], selector: ".button") # selector
.invoke("classList.add", args: ["turbo-ready"], selector: "a") # dot notation + selector
Event Dispatch
It's possible to fire events on window
, document
, and element(s).
turbo_stream
.invoke(:dispatch_event, args: ["turbo-ready:demo"]) # fires on window
.invoke("document.dispatchEvent", args: ["turbo-ready:demo"]) # fires on document
.invoke(:dispatch_event, args: ["turbo-ready:demo"], selector: "#my-element") # fires on matching element(s)
.invoke(:dispatch_event, args: ["turbo-ready:demo", {bubbles: true, detail: {...}}]) # set event options
Syntax Styles
You can use snake_case
when invoking DOM functionality.
It will implicitly convert to camelCase
.
turbo_stream.invoke :event,
args: ["turbo-ready:demo", {detail: {converts_to_camel_case: true}}]
Need to opt-out? No problem... just disable it.
turbo_stream.invoke :contrived_demo, camelize: false
Extending Behavior
If you add new capabilities to the browser, you can control them from the server.
// JavaScript on the client
import morphdom from 'morphdom'
window.MyNamespace = {
morph: (from, to, options = {}) => {
morphdom(document.querySelector(from), to, options)
}
}
# Ruby on the server
turbo_stream.invoke "MyNamespace.morph",
args: [
"#demo",
"<div id='demo'><p>You've changed...</p></div>",
{children_only: true}
]
Implementation Details
There's basically one method to learn... invoke
# Ruby
turbo_stream
.invoke(method, args: [], selector: nil, camelize: true, id: nil)
# | | | | |
# | | | | |- Identifies this invocation (optional)
# | | | |
# | | | |- Should we camelize the JavaScript stuff? (optional)
# | | | (allows us to write snake_case in Ruby)
# | | |
# | | |- A CSS selector for the element(s) to target (optional)
# | |
# | |- The arguments to pass to the JavaScript method (optional)
# |
# |- The JavaScript method to invoke (can use dot notation)
📘 NOTE: The method will be invoked on all matching elements if aselector
is present.
The following Ruby code,
turbo_stream.invoke "console.log", args: ["Hello World!"], id: "123ABC"
emits this HTML markup.
<turbo-stream action="invoke" target="DOM">
<template>{"id":"123ABC","receiver":"console","method":"log","args":["Hello World!"]}</template>
</turbo-stream>
When this element enters the DOM,
Turbo Streams automatically executes invoke
on the client with the template's JSON payload and then removes the element from the DOM.
Broadcasting
You can also broadcast DOM invocations to subscribed users.
-
First, setup the stream subscription.
<!-- app/views/posts/show.html.erb --> <%= turbo_stream_from @post %> <!-- | |- *streamables - model(s), string(s), etc... -->
-
Then, broadcast to the subscription.
# app/models/post.rb class Post < ApplicationRecord after_save do # emit a message in the browser conosle for anyone subscribed to this post broadcast_invoke "console.log", args: ["Post was saved! #{to_gid.to_s}"] # broadcast with a background job broadcast_invoke_later "console.log", args: ["Post was saved! #{to_gid.to_s}"] end end
# app/controllers/posts_controller.rb class PostsController < ApplicationController def create @post = Post.find params[:id] if @post.update post_params # emit a message in the browser conosle for anyone subscribed to this post @post.broadcast_invoke "console.log", args: ["Post was saved! #{to_gid.to_s}"] # broadcast with a background job @post.broadcast_invoke_later "console.log", args: ["Post was saved! #{to_gid.to_s}"] # you can also broadcast directly from the channel Turbo::StreamsChannel.broadcast_invoke_to @post, "console.log", args: ["Post was saved! #{@post.to_gid.to_s}"] # broadcast with a background job Turbo::StreamsChannel.broadcast_invoke_later_to @post, "console.log", args: ["Post was saved! #{@post.to_gid.to_s}"] end end end
📘 NOTE: Method Chaining is not currently supported when broadcasting.
Background Job Queues
You may want to change the queue name for Turbo Stream background jobs in order to isolate, prioritize, and scale the workers independently.
# config/initializers/turbo_streams.rb
Turbo::Streams::BroadcastJob.queue_name = :turbo_streams
TurboBoost::Streams::BroadcastInvokeJob.queue_name = :turbo_streams
FAQ
-
Isn't this just RJS?
No. But, perhaps it could be considered RJS's "modern" spirtual successor.
🤷♂️ Though it embraces JavaScript instead of trying to avoid it. -
Does it use
eval
?No. The
invoke
stream can only execute existing functions on the client. It's not a carte blanche invitation to emit free-form JavaScript to be evaluated on the client.
A Word of Warning
TurboBoost Streams is a foundational tool designed to help you build modern, maintainable, and scalable reactive web apps with Hotwire. It allows you to break free from the strict CRUD/REST conventions that Rails and Hotwire wisely encourage. You should consider boosted streams a substrate for building additional libraries and abstractions.
Please don't use TurboBoost Streams to manually orchestrate micro DOM updates (from the server). Such techniques are what gave rise to Full Stack Frontend and sent the industry on a decade long journey of complexity and frustration.
Developing
This project supports a fully Dockerized development experience.
-
Simply run the following commands to get started.
git clone -o github https://github.com/hopsoft/turbo_boost-streams.git cd turbo_boost-streams
docker compose up -d # start the envionment (will take a few minutes on 1st run) docker exec -it turbo_boost-streams-web rake # run the test suite open http://localhost:3000 # open the `test/dummy` app in a browser
And, if you're using the containers gem (WIP).
containers up # start the envionment (will take a few minutes on 1st run) containers rake # run the test suite open http://localhost:3000 # open the `test/dummy` app in a browser
-
Edit files using your preferred tools on the host machine.
-
That's it!
Notable Files
Deploying
This project supports Dockerized deployment via the same configurtation used for development,
and... it actually runs the test/dummy
application in "production".
The test/dummy
app serves the following purposes.
- Test app for the Rails engine
- Documentation and marketing site with interactive demos
You can see it in action here. How's that for innovative simplicity?
Notable Files
How to Deploy
fly deploy
Releasing
- Run
yarn
andbundle
to pick up the latest - Bump version number at
lib/turbo_boost-streams/version.rb
. Pre-release versions use.preN
- Run
rake build
andyarn build
- Run
bin/standardize
- Commit and push changes to GitHub
- Run
rake release
- Run
yarn publish --no-git-tag-version --access public
- Yarn will prompt you for the new version. Pre-release versions use
-preN
- Commit and push changes to GitHub
- Create a new release on GitHub (here) and generate the changelog for the stable release for it
About TurboBoost
TurboBoost is a suite of libraries that enhance Rails, Hotwire, and Turbo... making them even more powerful and boosing your productivity. Be sure to check out all of the various the libraries.
License
These libraries are available as open source under the terms of the MIT License.