Provides event handling and an HTMLElement mixin for Declarative Shadow DOM in Hotwire Turbo.

Overview

Turbo Shadow

Provides event handling and an HTMLElement mixin for Declarative Shadow DOM support in Hotwire Turbo.

Requires Turbo 7.2 or higher.

Quick Install

Add the NPM library to your project:

npm i turbo-shadow
# or
yarn add turbo-shadow

Add this to your JavaScript entrypoint (likely index.js) right after you import Turbo:

import * as TurboShadow from "turbo-shadow"

And add this to your HTML head (unfortunately Turbo's client-side caching will strip out all shadow roots).

<meta name="turbo-cache-control" content="no-cache" />

Now when you write a web component by subclassing HTMLElement (or some subclass of that), you can use the ShadowRootable mixin along with the shadowRootAttached promise to ensure by the time you run code within your connectedCallback, the shadow root with server-sent declarative markup has already been attached.

<!-- HTML sent from the server and intercepted by Turbo -->
<test-dsd>
  <template shadowroot="open">
    <p>Your message is:</p>
    <slot></slot>
  </template>

  <p>Greetings from Turbo</p>
</test-dsd>
// Client-side JavaScript
import { ShadowRootable } from "turbo-shadow"

class TestDSDElement extends ShadowRootable(HTMLElement) {
  async connectedCallback() {
    // Wait for the promise that the shadow root has been attached
    await this.shadowRootAttached

    // Shadow DOM markup is now loaded and working for the component
    console.log("The shadow root has been attached", this.shadowRoot.innerHTML)
  }
}
customElements.define("test-dsd", TestDSDElement)

Something to note: the client-side JavaScript component definition is actually optional. With Declarative Shadow DOM, you can write HTML-only shadow roots and that's perfectly fine. In fact, you don't even need to use custom elements! You can still get all the benefits of encapsulated styles and DOM that's hidden away from the parent document using most built-in HTML elements. This totally works:

<div style="padding: 10px; background: lightcyan">
  <template shadowroot="open">
    <p>Just some regular ol' markup.</p>
    <style>
      /* Styles are fully encapsulated because they only apply to the shadow root! */
      p {
        color: lightsalmon;
      }
    </style>
  </template>
</div>

Keep reading for further details…

Rationale for Turbo Shadow

Hotwire Turbo is an excellent JavaScript library that can take your MPA (Multi-Page Application) and make it feel more like an SPA (Single-Page Application): with fast page changes which you can augment with slick CSS transitions, frame-like support for loading and updating specific regions of a page in real-time, and a feature called Streams which can surgically alter the DOM from server-driven events.

Declarative Shadow DOM (DSD) is an emerging spec for Web Components which lets you define the "shadow DOM" template for a component using server-rendered HTML. So instead of writing out this:

<h1>Hello World from a Web Server</h1>

<howdy-folks>
  <!-- Who knows what will get rendered here? Only the client knows! :( -->
</howdy-folks>

You can write out this (or generate it automatically from a template engine of some kind):

<h1>Hello World from a Web Server</h1>

<howdy-folks>
  <template shadowroot="open">
    <!-- Now we get to provide the shadow DOM! -->
    <p>Isn't this amazing?!</p>

    <style>
      /* Styles are fully encapsulated because they only apply to the shadow root! */
      p { 
        color: green;
      }
    </style>
  </template>
</howdy-folks>

You would think that Declarative Shadow DOM and Turbo would be a match made in heaven! Both resolve around the centrality of HTML. But…you would be wrong. 😭

First of all, DSD is only natively supported in Chromium browsers. You would need to use a polyfill for Firefox and Safari. However, there are no polyfills out there (that I'm aware of) which support Turbo's event system (for Drive, Frames, and Streams). And even if there were, they don't provide extra support for the custom element to get notified when a shadow root has actually been attached. Expecting it to be in place already when connectedCallback gets triggered is a no-go, because Turbo has already attached new elements to the document prior to the triggering of Turbo events. Something would need to intercept the Turbo events, run a polyfill, and then notify the elements that the shadow roots are now attached.

In addition, Turbo currently isn't directly compatible with the native DSD support in Chromium, because standard HTML parsing methods in JavaScript don't support DSD for security reasons. For example, if you were to run this:

(new DOMParser()).parseFromString(htmlContainingDSD, "text/html")

Any shadow root templates in the htmlContainingDSD would be ignored…aka they'd just remain inert templates in the output node tree. To get real attached shadow DOM roots, you'd have to supply an extra argument:

(new DOMParser()).parseFromString(htmlContainingDSD, "text/html", { includeShadowRoots: true })

This is all described in the DSD spec explainer.

Will Turbo itself get updated in the future to support this? Possibly, but unlikely until the DSD spec is itself supported by all major browsers. Until that time, you will need a Turbo-specific polyfill to handle full DSD support.

Introducing: Turbo Shadow. 😎

If you find any bugs or edge cases that need solving, please file an issue! Otherwise, the primary goal of this library is widespread stability, so I am unlikely to add any additional features in the near future.

You might also like...

Nuxt.js 3 x Histoire x Vitest x VitePress x Turbo (pnpm)

Turborepo nuxt starter This is a monorepo with Nuxt, Histoire, Vitest & VitePress as a starter for any project that can be easily extended. You can al

Dec 19, 2022

This repository contains a fullstack chatbot project based on the ChatGPT `gpt-3.5-turbo` model.

This is a fullstack chatbot created with React, Nodejs, OpenAi, and ChatGPT while developing the following tutorial: How To Build A Chat Bot Applicati

May 10, 2023

Examples of how to do query, style, dom, ajax, event etc like jQuery with plain javascript.

You (Might) Don't Need jQuery Frontend environments evolve rapidly nowadays and modern browsers have already implemented a great deal of DOM/BOM APIs

Dec 24, 2022

💊 Event-driven DOM programming in a new style

Capsule v0.5.3 Event-driven DOM programming in a new style Features Supports event-driven style of frontend programming in a new way. Supports event d

Oct 1, 2022

Adds `long-press` event to the DOM in 1k of pure JavaScript

long-press-event A 1k script that adds a long-press event to the DOM using CustomEvent and pure JavaScript. Works in IE9+, Chrome, Firefox, Safari as

Jan 2, 2023

The invoker based on event model provides an elegant way to call your methods in another container via promisify functions

The invoker based on event model provides an elegant way to call your methods in another container via promisify functions. (like child-processes, iframe, web worker etc).

Dec 29, 2022

Custom Vitest matchers to test the state of the DOM, forked from jest-dom.

vitest-dom Custom Vitest matchers to test the state of the DOM This library is a fork of @testing-library/jest-dom. It shares that library's implement

Dec 16, 2022

LiveJSON provides LiveView-like updating for JSON objects rather than DOM elements.

live_json LiveJSON provides LiveView-like updating for JSON objects rather than DOM elements. It works within your existing LiveViews - just use push_

Dec 29, 2022

A declarative way of doing asynchronous programing with Typescript.

Deasyncify A declarative way of doing asynchronous programing with Typescript. Overview Getting started Issues Installation Usage Methods watch watchA

Jun 19, 2022
Releases(v1.0.1)
Owner
Whitefusion
A boutique web studio based in Portland, OR. Specializing in Bridgetown, Ruby on Rails, Shoelace, LitElement, and Stimulus.
Whitefusion
Grupprojekt för kurserna 'Javascript med Ramverk' och 'Agil Utveckling'

JavaScript-med-Ramverk-Laboration-3 Grupprojektet för kurserna Javascript med Ramverk och Agil Utveckling. Utvecklingsguide För information om hur utv

Svante Jonsson IT-Högskolan 3 May 18, 2022
Hemsida för personer i Sverige som kan och vill erbjuda boende till människor på flykt

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

null 4 May 3, 2022
Kurs-repo för kursen Webbserver och Databaser

Webbserver och databaser This repository is meant for CME students to access exercises and codealongs that happen throughout the course. I hope you wi

null 14 Jan 3, 2023
GraphErr is an open-source error handling library for GraphQL implementations in Deno. It's a lightweight solution that provides developers with descriptive error messages, reducing ambiguity and improving debugging.

GraphErr Descriptive GraphQL error handling for Deno/Oak servers. Features Provides additional context to GraphQL's native error messaging for faster

OSLabs Beta 35 Nov 1, 2022
Long shadow jQuery plugin

Long Shadow jQuery Plugin Easy text-shadow with long shadow jquery plugin UNPKG <script src="https://unpkg.com/jquery-longshadow"></script> Use $(sele

Dang Van Thanh 15 Jul 20, 2022
io-ts Typed Event Bus for the runtime of your Node.js application. A core for any event-driven architecture based app.

Typed Event Bus Based on io-ts types, this bus provides a handy interface to publish and consume events in the current runtime of the Node.js process.

Konstantin Knyazev 3 May 23, 2022
'event-driven' library aims to simplify building backends in an event driven style

'event-driven' library aims to simplify building backends in an event driven style(event driven architecture). For message broker, light weight Redis Stream is used and for event store, the well known NoSQL database, MongoDB, is used.

Sihoon Kim 11 Jan 4, 2023
This Is a Whatsapp Bot Made By Turbo Do Not Recode

This Is a Whatsapp Bot Made By Turbo Do Not Recode

TURBOMODS 7 Dec 6, 2022
`morphdom` integration for Turbo Streams

Turbo Morph turbo-morph is a morphdom integration for Turbo Streams. It provides a new Turbo Stream morph action. Note: Requires Turbo 7.2+ Getting St

Marco Roth 48 Dec 29, 2022
WIP: Power-pack for Turbo Streams

TurboPower turbo_power is a power-pack for Turbo Streams. It provides Turbo Streams with a bunch of new actions and additionally adds the morph action

Marco Roth 123 Jan 4, 2023