I was a bit confused by the <turbo-stream>
tag, so much I ended up reading the source code till I figured them out. Here’s what I found.
<turbo-stream
? More like <turbo-mutation
The <turbo-stream
tag is a web component / custom element that when added to the DOM it will have some jQuery type side effect on the DOM. That’s it!
(Registered as web components here: turbo/src/elements/index.ts)
For example if there’s a div:
<div id="asdf">Before Side Effect</div>
and you add this to the DOM (you can use right-click and inspect element
to do this)
<turbo-stream action="replace" target="asdf">
<template>
<div id="asdf">LOOK, I HAVE BEEN REPLACED</div>
</template>
</turbo-stream>
Then it will result in the DOM looking like this (replacing its contents with the template:)
<div id="asdf">
LOOK, I HAVE BEEN REPLACED
</div>
Does <turbo-stream open HTTP/Websocket connections? Nope.
At first, I thought that adding <turbo-stream
to the DOM would open some web socket connection or something. Not so, they don’t do anything but mutate the DOM when they mount. This is done in Turbo by having the browser call connectedCallback which is automatically called whenever a custom element is added to the dom or mutated in any way.
The only “magic” left to understand is how turbo listens to fetch
requests and form submissions using it’s StreamObserver. This class will listen to the fetch
requests that meet the following criteria:
- match on the content type of:
text/html; turbo-stream
(it’s ok if there’s other stuff there in there about UTF8 and such, it just checks that it’s a substring). -
<turbo-stream>
elements are at the root of the request (no wrapping in<body>
tags or anything else) -
<turbo-stream>
elements have a<template
tag as it’s direct child
When it finds a fetch
that matches these criteria then it’ll just add those tags to the DOM and they have the before mentioned side effect(s).
But what about Websockets!?!
Admittedly I haven’t got websockets working yet in phoenix but it seems that websockets are actually just normal fetch
requests to the browser, they just get upgraded to a socket. So by listening to fetch
requests turbo is also listening to websockets.
All the connecting to a websocket and such has to be handled by your backend framework of choice. This is sadly a bit tricky to do in Phoenix because it doesn’t seem to let you set the headers of a phoenix channel, but with the understanding from above it’s easy to see that it’s not hard to hack around by just listening to a channel and adding the resulting HTML to the DOM.
Conclusion
hopefully that was helpful! I now think of <turbo-stream
as a simple way for the server to execute jQuery
like commands to the DOM. It’s surprisingly simple and yet works very well. Also I would say don’t be afraid to read Trubo’s code, it’s pretty cleanly written and github was letting me look up where classes were used like this:
Cheers!