Description
UPDATE 2025-02-23
Tip
If you want to have a good example supporting this suggestion : #2447 (comment)
Original post below
TL;DR; - Turbo Stream
Proposal to introduce new helper / classes
- StreamSourceUrlGenerator
- turbo-stream-source-controller.js
- turbo_stream_source_url
- <twig:turbo:stream:source />
With all implementation details being hidden behind the StreamSourceUrlGenerator.
This would ease turbo stream usage, and open features to custom implementations while keeping things seamless for users.
Open to feedback, ideas, comments !
1. Main Goal
We aim to improve the developer experience (DX) offered by UX Turbo when using Turbo, especially Turbo Streams.
Several recent pull requests have helped us move forward:
- New
TurboStreamResponse
andTurboStream
helper classes in #2196, completed in #2298 - New
<twig:Turbo:Stream />
components in #2227, completed in #2302 - New
<twig:Turbo:Frame />
in #2303
(Apologies if I missed one—please correct me! And thank you to everyone involved.)
2. Current Situation
@norkunas recently had to find clever internal tricks (#2407) to allow registration on multiple topics at once.
In #2447, @Fan2Shrek tried to adapt the current Twig function to pass Mercure options to handle authorization.
This, however, raised questions about implementation, coupling, current limitations, and DX.
Challenges
As demonstrated by @norkunas and @Fan2Shrek, we must ensure compliance with Symfony's BC (backward compatibility) promise.
For a full explanation of what this entails, refer to Symfony’s BC guidelines.
In this context, our actions are mostly constrained by:
- The
TurboStreamListenRendererInterface
- The
TurboStreamListenRenderer
Limitations
But there is also several issues with the current DX.
The turbo_render_listen
function does not render a full HTML element, only attributes.
This requires users to create the HTML tag themselves, which is not intuitive and slightly inconsistent with other UX features.
Additionally:
- Stimulus attributes cannot be chained, making it impossible to add another Stimulus controller to the same tag.
- Directly rendering attributes as escaped strings makes the function incompatible with Twig components, custom Stimulus attributes, or JavaScript. This also makes it unsafe for use with includes, embeds, etc., without requiring a
|raw
filter.
Coupling
The PHP code utilizes interfaces, so the coupling with the Mercure bridge is currently low.
However, on the JavaScript/controller side, the situation is different. The turbo_stream_controller.js
is exclusively used as a Mercure stream controller, making it currently impossible to use it with other types of event sources. The controller requires multiple inputs/values (hub, topics, etc.), but ultimately only needs a URL for the EventSource required by Turbo. We should construct this URL in PHP and pass it directly.
Less important, the Twig function renderer interface
depends on the Environment
class. Yet, this dependency is not currently used in TurboStreamListenRenderer
, as it is injected into the StimulusHelper
.
3. Future Changes
Mercure Internals
We should avoid implementing anything in the Turbo Bundle that duplicates functionality from the Mercure Bundle. Instead, it should be clear that the Mercure Bundle is a requirement for using Mercure streams with Turbo. All aspects related to security, authorization, etc., should be delegated to the Mercure Bundle.
Simultaneously, any direct usage of Mercure Bundle code (or assumptions about its existence) should include safeguards to avoid issues arising from future updates to the bundle.
Turbo Needs
Taken from the Turbo documentation:
Turbo's
<turbo-stream-source>
custom element connects to a stream source through its[src]
attribute. When declared with aws://
orwss://
URL, the underlying stream source will be a WebSocket instance. Otherwise, the connection uses an EventSource.
Ultimately, we only need a URL and possibly some options. Constructing this (including creating cookies or JWTs if necessary) should be handled internally.
4. Proposal
- Keep existing methods/interfaces (?)
- Introduce a generic StreamSourceUrlGenerator
- Introduce a new generic function/component to render Turbo Stream sources
- Create a new
turbo_stream_source_controller
to handle EventSource
Stream Source URL
Interface: TurboStreamUrlGenerator
Introduce an interface:
interface UX\Turbo\StreamSourceUrlGenerator {
public function generateUrl(): string;
}
This interface would function similarly to the Router component, allowing for implementation flexibility.
We may need to pass additional parameters or introduce a Value Object (VO). I'm open to ideas as long as it remains generic and future-proof.
This allows users to use the assembled URL for any purpose.
Stream Source Controller
A new Stimulus Controller could be introduced, doing exactly what does the existing one, but manipulating only one value (the URL).
<div
class="foo"
data-controller="[...]-stream-source"
data-...-stream-source-url-value="{{ event_source }}"
>
...
</div>
Stream Source Tag
Rather than rendering and escaping HTML attributes (which requires a Stimulus controller), we could generate a simple HTML element:
<turbo-stream-source src="..." />
This approach provides a clean DX for most users.
Twig Function: turbo_stream_source()
A new turbo_stream_source()
function could be introduced. This function would:
- Use the same signature as
turbo_stream_url()
(which it would call internally) ? - ...Or maybe accept only an url parameter ?
- Handle escaping and other concerns
{{ turbo_stream_source() }}
{{ turbo_stream_source(url: ...) }}
Twig Component: <twig:turbo:stream:source />
The same functionality could be offered as a Twig component. The exact name is up for discussion (<twig:turbo:source />
might be simpler but less descriptive).
Example:
<twig:turbo:stream:source foo="..." topic="{{ [topicA, topicB] }}" ... />
or
<twig:turbo:stream:source url="{{ urlParams }}" />
Next Steps
I’d love to hear your feedback, ideas, and desires on this topic (as long as they stay within scope, haha).
I’m happy to start working on this or assist anyone interested in contributing!