Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

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

Awesome Lists containing this project

README

        







Welcome to TurboBoost Streams 👋




Lines of Code





GEM Version


GEM Downloads


Ruby Style


NPM Version


NPM Downloads


NPM Bundle Size


JavaScript Style


Tests


GitHub Discussions


Discord Community


Sponsors




Ruby.Social Follow


Twitter Follow

**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 demos

You 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).