Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Actions: Export StreamActions module #479

Merged
merged 1 commit into from
Jul 18, 2022

Conversation

seanpdoyle
Copy link
Contributor

Closes #478
Closes #477

As an alternative to continuing to introduce support for more varied and
specialized <turbo-stream action="..."> values, this commit exports
the StreamActions module from the root-module in order to provide
applications with direct access.

Applications can add new actions or override existing actions, so long
as they adhere to the StreamAction interface: a function whose this
context is an instance of StreamElement.

@seanpdoyle
Copy link
Contributor Author

seanpdoyle commented Nov 26, 2021

This is a draft because a feature like this is not entirely free. By opening up the innards of Turbo's support for <turbo-stream> elements, there will be an increased maintenance burden, since the module name will be part of the public API. This could have implications on work like #146 that aims to make StreamActions more generally applicable outside the <turbo-stream> elements.

Similarly, since the turbo:before-stream-render event fires with the document as its target (instead of the StreamElement instance), the StreamElement interface is currently internal private API, and inaccessible from client applications. This change would change that by making instances of StreamElement available to custom rendering actions.

On the plus side, this grants applications more fine-grained control to expand the suite of Stream capabilities, or change existing actions to better suit their needs. This might also close issues like #474, since applications can fire whatever events they desire from within the actions.

@seanpdoyle
Copy link
Contributor Author

Instead of exporting the module, a different variation of this change could involve dispatching the turbo:before-stream-render with an event.detail.render function that applications can override in the same way.

That would align with the pattern that #431 is aiming to establish.

@indigotechtutorials
Copy link

indigotechtutorials commented Nov 26, 2021

Instead of exporting the module, a different variation of this change could involve dispatching the turbo:before-stream-render with an event.detail.render function that applications can override in the same way.

That would align with the pattern that #431 is aiming to establish.

I like the idea of having the stream actions being exported so that we can have additional stream actions defined inside our applications. Then it would be okay to use turbo_stream.action(our_custom_action) to create a turbo_stream element in ruby

@seanpdoyle
Copy link
Contributor Author

seanpdoyle commented Nov 26, 2021

I like the idea of having the stream actions being exported so that we can have additional stream actions defined inside our applications.

That's good feedback! I'm thinking about the developer experience in terms of when would be best to intervene.

I'll share some pseudo-code to demonstrate the two styles.

First, consider manipulating the StreamActions object directly:

import { Turbo } from "@hotwired/turbo"

const { update: originalUpdateAction } = Turbo.StreamActions

const customActions = {
  // override the default `update`
  update() {
    doSomethingBeforeCallingUpdate(this)
    originalUpdateAction()
  },

  myCustomAction() {
    // ...
  }
}

Turbo.StreamActions = { ...Turbo.StreamActions, ...customActions }

Next, consider doing so within an event listener. This example assumes that event.detail.render is a reference to the default function that corresponds to the action, and relatedTarget is a reference to the <turbo-stream> element. The event is dispatched with target as the document since it's before the <turbo-stream> element is inserted anywhere into the DOM:

addEventListener("turbo:before-stream-render", ({ relatedTarget, detail }) => {
  const { render: originalRender } = detail

  const customActions = {
    update() {
      doSomethingBeforeUpdate(this)
      originalRender()
    },
    myCustomAction() {
      callAnotherLibraryWith(this)
    }
  }

  event.detail.render = customActions[target.action] || function() { console.error("Failed to handle action", this) }
})

Just spitballing here. The event listener version is more verbose, but grants more of an opportunity to incorporate contextual information like the state of the document, the <turbo-stream> element, etc. The overrides are scoped, and un-mounting the event listener would "reverse" any changes made. It doesn't commit to a name for the StreamActions object export, and it limits the "public" interface to a single event.detail.render() function. It also provides an opportunity to respond to an unsupported [action] value.

It would still require committing to support some form of a StreamElement public interface.

Then it would be okay to use turbo_stream.action(our_custom_action) to create a turbo_stream element in ruby

Regardless of when the client-side extension happens, changes to support something like turbo_stream.action(our_custom_action) would need to be made in the server-side library (either officially, or by mixing-in changes at the application level).

As far as I know, there isn't any coercion or validation happening at the hotwired/turbo-rails layer that would prevent a response or broadcast from transmitting a <turbo-stream action="our_custom_action"> value. If that's the case, I don't see the client-side design decisions having an affect on the server-side construction of the element.

@indigotechtutorials
Copy link

indigotechtutorials commented Nov 26, 2021

I think the first option would be better for defining custom stream actions to do something like #477

@seanpdoyle
Copy link
Contributor Author

seanpdoyle commented Dec 22, 2021

It's currently possible, without any internal or external changes to Turbo, to implement custom Turbo Stream actions.

As an alternative to extending StreamActions, it might be worth exploring and documenting how to integrate an application-specific action with the current plumbing.

Turbo Stream elements expect their first child to be a <template> element. There are no restrictions about the descendants of that <template> or their contents. In the case of an [action="append"], the entirety of the <template> is appended into the element with an [id] that matches the [target="..."] value.

If that content were itself a <template> element that declared a [data-controller="custom-stream"] attribute, the corresponding custom-stream controller's connect() hook could implement any bespoke DOM operation code.

Consider the following example code (available as a JSFilddle: https://jsfiddle.net/o1kLsq6p/)

<html>
  <head>
    <script type="module">
      import "https://unpkg.com/@hotwired/turbo@7.1.0"
      import { Application, Controller } from "https://unpkg.com/@hotwired/stimulus@3.0.1/dist/stimulus.js"

      const Stimulus = Application.start()

      Stimulus.register("custom-stream", class extends Controller {
        static get values() { return { action: String } }
       
        connect() {
          const { content, parentElement } = this.element
          switch (this.actionValue) {
            case "append": {
              parentElement.append(content)
              parentElement.style.color = "red"
              break
            }
            default: console.error(`Don't know how to handle ${this.actionValue}!`)
          }
        }
      }
    })
  </script>
  </head>
  <body>
    <ul id="my_list">
      <li>First Element</li>
    </ul>
  </body>
</html>

Consider receiving the following <turbo-stream> (either in response to a Form Submission or from a broadcast over Action Cable):

<turbo-stream action="append" target="my_list">
  <template>
    <template data-controller="custom-stream" data-custom-stream-action-value="append">
      <li>Another Element</li>
    </template>
  </template>
</turbo-stream>

When executed, the [action="append"] and [target="my_list"] pairing would direct the Stream to append the <template> element to the <ul id="my_list"> document. Once appended, the custom-stream controller's connect() hook would fire. That connect reads from the controller's values to decide the details of the operation (in this case, the operation is a custom "append").

The connect() hook executes the operation, extracting this.element.content, appending the content to the <ul> element, and marking the list to have "red" text (as a demonstration of the "custom" part of the action). The operation knows which element to "target" because the <template> was appending into the <ul>. This is another avenue of customization, since callers could append the <template> to the <body> element, then encode whatever selectors or element resolution logic they'd need into the <template> element's attributes.

The <template> element itself is inert unless acted upon. Appending or prepending it onto the page modifies the structure of the DOM, but doesn't "render" any visual changes. That behavior could be leveraged to encode additional information alongside the [data-controller] declaration. For example, its own [data-custom-stream-action-value], [data-custom-stream-target], etc. There's nothing preventing the element from encoding JSON objects of options that are ready to be forwarded along to another library like Morphdom.

The turbo-stream[target] value is as significant as you need it to be. You could "append" the inner <template> element to the <body> and encode the entirety of the operation's logic into the <template> attributes, or you could append the <template> into the correct place in the DOM and have it evaluate without any additional context. It's open ended and up to you and your needs to decide. The same is true for the turbo-stream[action] value. So long as the inner <template> is connected to the document in some way, the controller's connect() hook will fire and the logic for executing the operation can be modified to suit whatever the situation dictates.

@indigotechtutorials
Copy link

This proves that stimulus + the default stream actions are enough to build any functionality that we desire even having custom dom actions.

@seb-jean
Copy link

What remains to be done to finish this PR?

@marcoroth
Copy link
Member

@seb-jean this PR is pretty much done - at least code-wise.

It's more a question about the philosophy around it.

I agree, it's a sharp knife, as with most of the things in the Rails ecosystem. But I think, that in the end it's probably worth the flexibility and the endless possibilities it provides.

@seb-jean
Copy link

@marcoroth thanks for your answer

@dhh
Copy link
Member

dhh commented Jul 17, 2022

After letting this marinate for a while, I like the simplicity of exporting the stream actions, and allowing the app to extend as they see fit. We should document it as a sharp knife. But I like sharp knives 😄.

@dhh dhh added this to the 7.2.0 milestone Jul 17, 2022
@seanpdoyle seanpdoyle marked this pull request as ready for review July 18, 2022 00:36
@dhh
Copy link
Member

dhh commented Jul 18, 2022

Should add a doc PR to explain how to actually extend the actions 👍

@seanpdoyle
Copy link
Contributor Author

Should add a doc PR to explain how to actually extend the actions

@dcyoung-dev @enoch-tamulonis since your original issues were the inspiration for this change, would either of you be interested in opening a pull request against https://github.com/hotwired/turbo-site/ outlining a use case or two that would motivate a customization? @marcoroth maybe you're interested as well?

The how-to is captured by the page.evaluate block in the test suite. Since the function() is evaluated in the context of the <turbo-stream> Custom element, it's important that it's declared as function(){ ... } and not () => ....

@seanpdoyle seanpdoyle force-pushed the custom-actions branch 3 times, most recently from f90bc14 to 5020760 Compare July 18, 2022 01:28
@seanpdoyle seanpdoyle force-pushed the custom-actions branch 2 times, most recently from f2c5a3d to 09c4acc Compare July 18, 2022 02:17
@dhh
Copy link
Member

dhh commented Jul 18, 2022

Rebase needed?

Closes hotwired#478
Closes hotwired#477

As an alternative to continuing to introduce support for more varied and
specialized `<turbo-stream action="...">` values, this commit exports
the `StreamActions` module from the root-module in order to provide
applications with direct access.

Applications can add new actions or override existing actions, so long
as they adhere to the `StreamAction` interface: a function whose `this`
context is an instance of `StreamElement`.
@dhh dhh merged commit 11f9592 into hotwired:main Jul 18, 2022
@seanpdoyle seanpdoyle deleted the custom-actions branch July 18, 2022 21:59
seanpdoyle added a commit to seanpdoyle/turbo that referenced this pull request Aug 13, 2022
Follow-up to hotwired#479
Related to hotwired/turbo-site#107

As an alternative to exposing an otherwise private and internal
`StreamActions` "class", support custom `<turbo-stream action="...">`
values the same way as custom `<turbo-frame>` rendering or `<body>`
rendering: as part of its `turbo:before-render-stream` event.

To change how Turbo renders the document during page rendering, client
applications declare a `turbo:before-render` event listener that
overrides its `CustomEvent.detail.render` function from its
[default][PageRenderer.renderElement].

Similarly, to change how Turbo renders a frame, client applications
declare a `turbo:before-frame-render` and override its
`CustomEvent.detail.render` function from its [default][].

This commit introduces the `StreamElement.renderElement` function, and
extends the existing `turbo:before-stream-render` event to support the
same pattern with `StreamElement.renderElement` server as the default
`CustomEvent.detail.render` value.

With those changes in place, callers can declare a document-wide event
listener and override based on the value of `StreamElement.action`:

```javascript
const CustomActions = {
  customUpdate: (stream) => { /* ... */ }
  customReplace: (stream) => { /* ... */ }
  // ...
}

document.addEventListener("turbo:before-stream-render", (({ target, detail }) => {
  const defaultRender = detail.render

  detail.render = CustomActions[target.action] || defaultRender
}))
```

[PageRenderer.renderElement]: https://github.com/hotwired/turbo/blob/256418fee0178ee483d82cd9bb579bd5df5a151f/src/core/drive/page_renderer.ts#L7-L13
[FrameRenderer.renderElement]: https://github.com/hotwired/turbo/blob/256418fee0178ee483d82cd9bb579bd5df5a151f/src/core/frames/frame_renderer.ts#L13-L24
seanpdoyle added a commit to seanpdoyle/turbo that referenced this pull request Aug 13, 2022
Follow-up to hotwired#479
Related to hotwired/turbo-site#107

As an alternative to exposing an otherwise private and internal
`StreamActions` "class", support custom `<turbo-stream action="...">`
values the same way as custom `<turbo-frame>` rendering or `<body>`
rendering: as part of its `turbo:before-render-stream` event.

To change how Turbo renders the document during page rendering, client
applications declare a `turbo:before-render` event listener that
overrides its `CustomEvent.detail.render` function from its
[default][PageRenderer.renderElement].

Similarly, to change how Turbo renders a frame, client applications
declare a `turbo:before-frame-render` and override its
`CustomEvent.detail.render` function from its [default][].

This commit introduces the `StreamElement.renderElement` function, and
extends the existing `turbo:before-stream-render` event to support the
same pattern with `StreamElement.renderElement` server as the default
`CustomEvent.detail.render` value.

With those changes in place, callers can declare a document-wide event
listener and override based on the value of `StreamElement.action`:

```javascript
const CustomActions = {
  customUpdate: (stream) => { /* ... */ }
  customReplace: (stream) => { /* ... */ }
  // ...
}

document.addEventListener("turbo:before-stream-render", (({ target, detail }) => {
  const defaultRender = detail.render

  detail.render = CustomActions[target.action] || defaultRender
}))
```

[PageRenderer.renderElement]: https://github.com/hotwired/turbo/blob/256418fee0178ee483d82cd9bb579bd5df5a151f/src/core/drive/page_renderer.ts#L7-L13
[FrameRenderer.renderElement]: https://github.com/hotwired/turbo/blob/256418fee0178ee483d82cd9bb579bd5df5a151f/src/core/frames/frame_renderer.ts#L13-L24
seanpdoyle added a commit to seanpdoyle/turbo that referenced this pull request Aug 13, 2022
Follow-up to hotwired#479
Related to hotwired/turbo-site#107

As an alternative to exposing an otherwise private and internal
`StreamActions` "class", support custom `<turbo-stream action="...">`
values the same way as custom `<turbo-frame>` rendering or `<body>`
rendering: as part of its `turbo:before-render-stream` event.

To change how Turbo renders the document during page rendering, client
applications declare a `turbo:before-render` event listener that
overrides its `CustomEvent.detail.render` function from its
[default][PageRenderer.renderElement].

Similarly, to change how Turbo renders a frame, client applications
declare a `turbo:before-frame-render` and override its
`CustomEvent.detail.render` function from its [default][].

This commit introduces the `StreamElement.renderElement` function, and
extends the existing `turbo:before-stream-render` event to support the
same pattern with `StreamElement.renderElement` server as the default
`CustomEvent.detail.render` value.

With those changes in place, callers can declare a document-wide event
listener and override based on the value of `StreamElement.action`:

```javascript
const CustomActions = {
  customUpdate: (stream) => { /* ... */ }
  customReplace: (stream) => { /* ... */ }
  // ...
}

document.addEventListener("turbo:before-stream-render", (({ target, detail }) => {
  const defaultRender = detail.render

  detail.render = CustomActions[target.action] || defaultRender
}))
```

[PageRenderer.renderElement]: https://github.com/hotwired/turbo/blob/256418fee0178ee483d82cd9bb579bd5df5a151f/src/core/drive/page_renderer.ts#L7-L13
[FrameRenderer.renderElement]: https://github.com/hotwired/turbo/blob/256418fee0178ee483d82cd9bb579bd5df5a151f/src/core/frames/frame_renderer.ts#L13-L24
seanpdoyle added a commit to seanpdoyle/turbo that referenced this pull request Aug 13, 2022
Follow-up to hotwired#479
Related to hotwired/turbo-site#107

As an alternative to exposing an otherwise private and internal
`StreamActions` "class", support custom `<turbo-stream action="...">`
values the same way as custom `<turbo-frame>` rendering or `<body>`
rendering: as part of its `turbo:before-render-stream` event.

To change how Turbo renders the document during page rendering, client
applications declare a `turbo:before-render` event listener that
overrides its `CustomEvent.detail.render` function from its
[default][PageRenderer.renderElement].

Similarly, to change how Turbo renders a frame, client applications
declare a `turbo:before-frame-render` and override its
`CustomEvent.detail.render` function from its [default][].

This commit introduces the `StreamElement.renderElement` function, and
extends the existing `turbo:before-stream-render` event to support the
same pattern with `StreamElement.renderElement` server as the default
`CustomEvent.detail.render` value.

With those changes in place, callers can declare a document-wide event
listener and override based on the value of `StreamElement.action`:

```javascript
const CustomActions = {
  customUpdate: (stream) => { /* ... */ }
  customReplace: (stream) => { /* ... */ }
  // ...
}

document.addEventListener("turbo:before-stream-render", (({ target, detail }) => {
  const defaultRender = detail.render

  detail.render = CustomActions[target.action] || defaultRender
}))
```

[PageRenderer.renderElement]: https://github.com/hotwired/turbo/blob/256418fee0178ee483d82cd9bb579bd5df5a151f/src/core/drive/page_renderer.ts#L7-L13
[FrameRenderer.renderElement]: https://github.com/hotwired/turbo/blob/256418fee0178ee483d82cd9bb579bd5df5a151f/src/core/frames/frame_renderer.ts#L13-L24
seanpdoyle added a commit to seanpdoyle/turbo that referenced this pull request Aug 13, 2022
Follow-up to hotwired#479
Related to hotwired/turbo-site#107

As an alternative to exposing an otherwise private and internal
`StreamActions` "class", support custom `<turbo-stream action="...">`
values the same way as custom `<turbo-frame>` rendering or `<body>`
rendering: as part of its `turbo:before-render-stream` event.

To change how Turbo renders the document during page rendering, client
applications declare a `turbo:before-render` event listener that
overrides its `CustomEvent.detail.render` function from its
[default][PageRenderer.renderElement].

Similarly, to change how Turbo renders a frame, client applications
declare a `turbo:before-frame-render` and override its
`CustomEvent.detail.render` function from its [default][].

This commit introduces the `StreamElement.renderElement` function, and
extends the existing `turbo:before-stream-render` event to support the
same pattern with `StreamElement.renderElement` server as the default
`CustomEvent.detail.render` value.

With those changes in place, callers can declare a document-wide event
listener and override based on the value of `StreamElement.action`:

```javascript
const CustomActions = {
  customUpdate: (stream) => { /* ... */ }
  customReplace: (stream) => { /* ... */ }
  // ...
}

document.addEventListener("turbo:before-stream-render", (({ target, detail }) => {
  const defaultRender = detail.render

  detail.render = CustomActions[target.action] || defaultRender
}))
```

[PageRenderer.renderElement]: https://github.com/hotwired/turbo/blob/256418fee0178ee483d82cd9bb579bd5df5a151f/src/core/drive/page_renderer.ts#L7-L13
[FrameRenderer.renderElement]: https://github.com/hotwired/turbo/blob/256418fee0178ee483d82cd9bb579bd5df5a151f/src/core/frames/frame_renderer.ts#L13-L24
seanpdoyle added a commit to seanpdoyle/turbo that referenced this pull request Sep 12, 2022
Follow-up to hotwired#479
Related to hotwired/turbo-site#107

As an alternative to exposing an otherwise private and internal
`StreamActions` "class", support custom `<turbo-stream action="...">`
values the same way as custom `<turbo-frame>` rendering or `<body>`
rendering: as part of its `turbo:before-render-stream` event.

To change how Turbo renders the document during page rendering, client
applications declare a `turbo:before-render` event listener that
overrides its `CustomEvent.detail.render` function from its
[default][PageRenderer.renderElement].

Similarly, to change how Turbo renders a frame, client applications
declare a `turbo:before-frame-render` and override its
`CustomEvent.detail.render` function from its [default][].

This commit introduces the `StreamElement.renderElement` function, and
extends the existing `turbo:before-stream-render` event to support the
same pattern with `StreamElement.renderElement` server as the default
`CustomEvent.detail.render` value.

With those changes in place, callers can declare a document-wide event
listener and override based on the value of `StreamElement.action`:

```javascript
const CustomActions = {
  customUpdate: (stream) => { /* ... */ }
  customReplace: (stream) => { /* ... */ }
  // ...
}

document.addEventListener("turbo:before-stream-render", (({ target, detail }) => {
  const defaultRender = detail.render

  detail.render = CustomActions[target.action] || defaultRender
}))
```

[PageRenderer.renderElement]: https://github.com/hotwired/turbo/blob/256418fee0178ee483d82cd9bb579bd5df5a151f/src/core/drive/page_renderer.ts#L7-L13
[FrameRenderer.renderElement]: https://github.com/hotwired/turbo/blob/256418fee0178ee483d82cd9bb579bd5df5a151f/src/core/frames/frame_renderer.ts#L13-L24
seanpdoyle added a commit to seanpdoyle/turbo that referenced this pull request Sep 12, 2022
Follow-up to hotwired#479
Related to hotwired/turbo-site#107

As an alternative to exposing an otherwise private and internal
`StreamActions` "class", support custom `<turbo-stream action="...">`
values the same way as custom `<turbo-frame>` rendering or `<body>`
rendering: as part of its `turbo:before-render-stream` event.

To change how Turbo renders the document during page rendering, client
applications declare a `turbo:before-render` event listener that
overrides its `CustomEvent.detail.render` function from its
[default][PageRenderer.renderElement].

Similarly, to change how Turbo renders a frame, client applications
declare a `turbo:before-frame-render` and override its
`CustomEvent.detail.render` function from its [default][].

This commit introduces the `StreamElement.renderElement` function, and
extends the existing `turbo:before-stream-render` event to support the
same pattern with `StreamElement.renderElement` server as the default
`CustomEvent.detail.render` value.

With those changes in place, callers can declare a document-wide event
listener and override based on the value of `StreamElement.action`:

```javascript
const CustomActions = {
  customUpdate: (stream) => { /* ... */ }
  customReplace: (stream) => { /* ... */ }
  // ...
}

document.addEventListener("turbo:before-stream-render", (({ target, detail }) => {
  const defaultRender = detail.render

  detail.render = CustomActions[target.action] || defaultRender
}))
```

[PageRenderer.renderElement]: https://github.com/hotwired/turbo/blob/256418fee0178ee483d82cd9bb579bd5df5a151f/src/core/drive/page_renderer.ts#L7-L13
[FrameRenderer.renderElement]: https://github.com/hotwired/turbo/blob/256418fee0178ee483d82cd9bb579bd5df5a151f/src/core/frames/frame_renderer.ts#L13-L24
dhh pushed a commit that referenced this pull request Sep 13, 2022
* Test harness: Prevent infinite recusion

When serializing `CustomEvent.detail` objects from the browser to the
Playwright test harness, the current `serializeToChannel` implementation
is prone to recurse infinitely if an object nests another object that
refers to the outer object.

To prevent that, this commit tracks which objects have been visited
during the current serialization process, and avoid an infinitely
recursing loop.

Related to that change, this commit also add more descriptive types to
the `EventLog` and `MutationLog` arrays to clarify which positional
elements correspond to their serialized values.

Finally, extend the test server's `<turbo-stream>` creation actions to
support serializing an element with an `[id]` value, so that it can be
serialized from the browser to the test harness, and serve as a
`target.id` value.

* Add `render` to `turbo:before-stream-render` event

Follow-up to #479
Related to hotwired/turbo-site#107

As an alternative to exposing an otherwise private and internal
`StreamActions` "class", support custom `<turbo-stream action="...">`
values the same way as custom `<turbo-frame>` rendering or `<body>`
rendering: as part of its `turbo:before-render-stream` event.

To change how Turbo renders the document during page rendering, client
applications declare a `turbo:before-render` event listener that
overrides its `CustomEvent.detail.render` function from its
[default][PageRenderer.renderElement].

Similarly, to change how Turbo renders a frame, client applications
declare a `turbo:before-frame-render` and override its
`CustomEvent.detail.render` function from its [default][].

This commit introduces the `StreamElement.renderElement` function, and
extends the existing `turbo:before-stream-render` event to support the
same pattern with `StreamElement.renderElement` server as the default
`CustomEvent.detail.render` value.

With those changes in place, callers can declare a document-wide event
listener and override based on the value of `StreamElement.action`:

```javascript
const CustomActions = {
  customUpdate: (stream) => { /* ... */ }
  customReplace: (stream) => { /* ... */ }
  // ...
}

document.addEventListener("turbo:before-stream-render", (({ target, detail }) => {
  const defaultRender = detail.render

  detail.render = CustomActions[target.action] || defaultRender
}))
```

[PageRenderer.renderElement]: https://github.com/hotwired/turbo/blob/256418fee0178ee483d82cd9bb579bd5df5a151f/src/core/drive/page_renderer.ts#L7-L13
[FrameRenderer.renderElement]: https://github.com/hotwired/turbo/blob/256418fee0178ee483d82cd9bb579bd5df5a151f/src/core/frames/frame_renderer.ts#L13-L24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

5 participants