Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/hopsoft/turbo_boost-streams
Take full control of the DOM with Turbo Streams
https://github.com/hopsoft/turbo_boost-streams
hotwire rails reactive ruby ruby-on-rails turbo turbo-streams
Last synced: 1 day ago
JSON representation
Take full control of the DOM with Turbo Streams
- Host: GitHub
- URL: https://github.com/hopsoft/turbo_boost-streams
- Owner: hopsoft
- License: mit
- Created: 2022-08-25T21:53:55.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2024-03-01T05:28:09.000Z (11 months ago)
- Last Synced: 2025-01-13T11:06:16.146Z (8 days ago)
- Topics: hotwire, rails, reactive, ruby, ruby-on-rails, turbo, turbo-streams
- Language: HTML
- Homepage: https://hopsoft.io/@turbo-boost/streams
- Size: 2.73 MB
- Stars: 273
- Watchers: 8
- Forks: 13
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: MIT-LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
- awesome-hotwire - turbo_boost-streams - Stream enhancements for Turbo. (**Awesome Hotwire** [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome) / Turbo)
- awesome-hotwire - turbo_boost-streams - Stream enhancements for Turbo. (**Awesome Hotwire** [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome) / Turbo)
README
Welcome to TurboBoost Streams 👋
**TurboBoost Streams extends [Turbo Streams](https://turbo.hotwired.dev/reference/streams) to give you full control of the
browser's [Document Object Model (DOM).](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)**```ruby
turbo_stream.invoke "console.log", args: ["Hello World!"]
```**That's right!**
You can `invoke` any DOM method on the client with Turbo Streams.## Table of Contents
- [Why boosted Streams?](#why-boosted-streams)
- [Sponsors](#sponsors)
- [Community](#community)
- [Dependencies](#dependencies)
- [Installation](#installation)
- [Setup](#setup)
- [Usage](#usage)
- [Method Chaining](#method-chaining)
- [Event Dispatch](#event-dispatch)
- [Morph](#morph)
- [Morph Method](#morph-method)
- [Syntax Styles](#syntax-styles)
- [Extending Behavior](#extending-behavior)
- [Implementation Details](#implementation-details)
- [Broadcasting](#broadcasting)
- [Background Job Queues](#background-job-queues)
- [FAQ](#faq)
- [A Word of Warning](#a-word-of-warning)
- [Developing](#developing)
- [Notable Files](#notable-files)
- [Deploying](#deploying)
- [Notable Files](#notable-files-1)
- [How to Deploy](#how-to-deploy)
- [Releasing](#releasing)
- [About TurboBoost](#about-turboboost)
- [License](#license)## Why boosted Streams?
Turbo Streams [intentionally restrict](https://turbo.hotwired.dev/handbook/streams#but-what-about-running-javascript%3F)
official actions to CRUD-related activity.
These [official actions](https://turbo.hotwired.dev/reference/streams#the-seven-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](https://discord.gg/stimulus-reflex).
## Dependencies
- [rails](https://rubygems.org/gems/rails) `>= 6.1`
- [turbo-rails](https://rubygems.org/gems/turbo-rails) `>= 1.1`
- [@hotwired/turbo-rails](https://www.npmjs.com/package/@hotwired/turbo-rails) `>= 7.2`## Installation
Be sure to install the same version for each library.
```sh
bundle add "turbo_boost-streams --version VERSION"
npm install "@turbo-boost/streams@VERSION"
```## Setup
Import and initialize Turbo Boost Streams in your application.
```diff
# Gemfile
gem "turbo-rails", ">= 1.1", "< 2"
+gem "turbo_boost-streams", "~> VERSION"
``````diff
# package.json
"dependencies": {
"@hotwired/turbo-rails": ">=7.2",
+ "@turbo-boost/streams": "^VERSION"
``````diff
# app/javascript/application.js
import '@hotwired/turbo-rails'
+import '@turbo-boost/streams'
```## Usage
Manipulate the DOM from anywhere you use official [Turbo Streams](https://turbo.hotwired.dev/handbook/streams#integration-with-server-side-frameworks).
The possibilities are endless.
[Learn more about the DOM at MDN.](https://developer.mozilla.org/en-US/docs/Web/API.)```ruby
turbo_stream.invoke "console.log", args: ["Hello World!"]
```### Method Chaining
You can use [dot notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#dot_notation)
or [selectors](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) and even combine them!```ruby
turbo_stream
.invoke("document.body.insertAdjacentHTML", args: ["afterbegin", "Hello World!
"]) # 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).
```ruby
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
```### Morph
You can morph elements with the `morph` method.
```ruby
turbo_stream.invoke(:morph, args: [render("path/to/partial")], selector: "#my-element")
```> [!NOTE]
> TurboBoost Streams uses [Idiomorph](https://github.com/bigskysoftware/idiomorph) for morphing.The following options are used to morph elements.
```js
{
morphStyle: 'outerHTML',
ignoreActiveValue: true,
head: { style: 'merge' },
callbacks: { beforeNodeMorphed: (oldNode, _) => ... }
}
```> [!TIP]
> The callbacks honor the `data-turbo-permanent` attribute and is aware of the [Trix](https://trix-editor.org/) editor.### Morph Method
The morph method is also exported to the `TurboBoost.Streams` global and is available for client side morphing.
```js
TurboBoost.Streams.morph.method // → function(targetNode, htmlString, options = {})
```You can also override the `morph` method if desired.
```js
TurboBoost.Streams.morph.method = (targetNode, htmlString, options = {}) => {
// your custom implementation
}
```It also support adding a delay before morphing is performed.
```js
TurboBoost.Streams.morph.delay = 50 // → 50ms
```> [!TIP]
> Complex test suites may require a delay to ensure the DOM is ready before morphing.### Syntax Styles
You can use [`snake_case`](https://en.wikipedia.org/wiki/Snake_case) when invoking DOM functionality.
It will implicitly convert to [`camelCase`](https://en.wikipedia.org/wiki/Camel_case).```ruby
turbo_stream.invoke :event,
args: ["turbo-ready:demo", {detail: {converts_to_camel_case: true}}]
```Need to opt-out? No problem... just disable it.
```ruby
turbo_stream.invoke :contrived_demo, camelize: false
```### Extending Behavior
If you add new capabilities to the browser, you can control them from the server.
```js
// JavaScript on the client
import morphdom from 'morphdom'window.MyNamespace = { coolStuff: (arg) => { ... } }
``````ruby
# Ruby on the server
turbo_stream.invoke "MyNamespace.coolStuff", args: ["Hello World!"]
```### Implementation Details
There's basically one method to learn... `invoke`
```ruby
# 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 a `selector` is present.
The following Ruby code,
```ruby
turbo_stream.invoke "console.log", args: ["Hello World!"], id: "123ABC"
```emits this HTML markup.
```html
{"id":"123ABC","receiver":"console","method":"log","args":["Hello World!"]}
```
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.
1. First, setup the stream subscription.
```erb
<%= turbo_stream_from @post %>
```2. Then, broadcast to the subscription.
```ruby
# app/models/post.rb
class Post < ApplicationRecord
after_save do
# emit a message in the browser console for anyone subscribed to this post
broadcast_invoke "console.log", args: ["Post was saved! #{to_gid}"]# broadcast with a background job
broadcast_invoke_later "console.log", args: ["Post was saved! #{to_gid}"]
end
end
``````ruby
# 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 console for anyone subscribed to this post
@post.broadcast_invoke "console.log", args: ["Post was saved! #{@post.to_gid}"]# broadcast with a background job
@post.broadcast_invoke_later "console.log", args: ["Post was saved! #{@postto_gid}"]# you can also broadcast directly from the channel
Turbo::StreamsChannel.broadcast_invoke_to @post, "console.log",
args: ["Post was saved! #{@post.to_gid}"]# broadcast with a background job
Turbo::StreamsChannel.broadcast_invoke_later_to @post, "console.log",
args: ["Post was saved! #{@post.to_gid}"]
end
end
end
```> 📘 **NOTE:** [Method Chaining](#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.
```ruby
# 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" spiritual 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.
1. Simply run the following commands to get started.
```sh
git clone -o github https://github.com/hopsoft/turbo_boost-streams.git
cd turbo_boost-streams
``````sh
docker compose up -d # start the environment (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)](https://github.com/hopsoft/containers).
```sh
containers up # start the environment (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
```1. Edit files using your preferred tools on the host machine.
1. That's it!
#### Notable Files
- [Dockerfile](https://github.com/hopsoft/turbo_boost-streams/blob/main/Dockerfile)
- [docker-compose.yml](https://github.com/hopsoft/turbo_boost-streams/blob/main/docker-compose.yml)
- [bin/docker/run/local](https://github.com/hopsoft/turbo_boost-streams/blob/main/bin/docker/run/local)## Deploying
This project supports Dockerized deployment via the same configuration used for development,
and... it actually runs the [`test/dummy`](https://github.com/hopsoft/turbo_boost-streams/tree/main/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 demosYou can [__see it in action__ here.](https://hopsoft.io/@turbo-boost/streams)
_How's that for innovative simplicity?_#### Notable Files
- [Dockerfile](https://github.com/hopsoft/turbo_boost-streams/blob/main/Dockerfile)
- [fly.toml](https://github.com/hopsoft/turbo_boost-streams/blob/main/fly.toml)
- [bin/docker/run/remote](https://github.com/hopsoft/turbo_boost-streams/blob/main/bin/docker/run/remote)#### How to Deploy
```sh
fly deploy
```## Releasing
> [!TIP]
> Run these commands on the host machine _(i.e. not inside the dev container)_1. Run `npm update` and `bundle update` to pick up the latest dependencies
1. Update the version number consistently in the following files:
* `lib/turbo_boost/streams/version.rb` - pre-release versions should use `.preN`
* `app/javascript/version.js` - pre-release versions use `-preN`
* `package.json` - pre-release versions use `-preN`
1. Run `bin/standardize`
1. Run `rake build`
1. Run `npm run build`
1. Commit and push any changes to GitHub
1. Run `rake release`
1. Run `npm publish --access public`
1. Create a new release on GitHub ([here](https://github.com/hopsoft/turbo_boost-streams/releases)) 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 boosting your productivity.
Be sure to check out all of the various libraries.- [Streams](https://github.com/hopsoft/turbo_boost-streams)
- [Commands](https://github.com/hopsoft/turbo_boost-commands)
- [Elements](https://github.com/hopsoft/turbo_boost-elements)
- [Devtools](https://github.com/hopsoft/turbo_boost-devtools)
- Coming soon...## License
These libraries are available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).