bigskysoftware / htmx

</> htmx - high power tools for HTML
https://htmx.org
Other
37.63k stars 1.27k forks source link

Newlines in SSE data #2292

Open itamarhaber opened 7 months ago

itamarhaber commented 7 months ago

Howdy all.

Firstly, I'd like to thank the maintainers of this project - very cool! - first fe app that I'm building with(almost)out any JS! - yippie!

Secondly, and hopefully due to ignorance, I'm having difficulties with returning events from the server with arbitrary HTML (templated) that contains \n both because of my indention as well as in content (eg <pre> sections).

My very very very ugly workaround is replacing all \n with '' in the data, which works, but isn't correct obviously. Without that workaround, the stream's message is truncated early on.

I think the v2+ solution would be to support some sort (json?) encoding, but perhaps there's an easier/trivial/obvious/elementry solution that I'm missing?

Possible alternatives:

  1. Use websockets, although I don't need bi-directional comms
  2. Encode the event's data somehow, use JS in the client to read/decode/handle instead of sse-swap (too hacky to be true)
  3. Use SSE's triggers and hx-get the fragments instead
guidog commented 7 months ago

Have a look at https://html.spec.whatwg.org/multipage/server-sent-events.html.

Two newlines are treated as end of event. One newline as end of line. That's the protocol. SSE has no other encoding. So your solution no. 1 is the only way.

Nothing HTMX can change.

itamarhaber commented 7 months ago

@guidog thank you :)

Before opening the issue I've read the SSE specs. I think HTMX's use of the feature is perhaps a bit naive. Given HTMX's spirit, it expects to get HTML "fragments" as SSE data. However, ensuring that HTML doesn't break SSE's specs is impossible.

So, in a future version perhaps, one can imagine doing something like:

 <div hx-ext="sse" sse-connect="/chatroom" sse-swap="message" sse-json="true">
      Contents of this box will be updated in real time
      with every SSE message received from the chatroom.
  </div>

And have the server send events where the data part is a jsonifyied value:

event: EventName
data: json.stringify(HTML)

This would allow embedding arbitrary HTML with relative ease (albeit at the cost of encoding/decoding json).

nickchomey commented 7 months ago

I'm curious about this as I intend to use SSE to return HTML fragments as well.

What exactly is the issue? If I'm not mistaken, html doesn't actually need any line breaks at all (in fact, minification will remove them). So, as far as the browser is concerned, your example should be the equivalent of this:

data: <div hx-ext="sse" sse-connect="/chatroom" sse-swap="message" sse-json="true">Contents of this box will be updated in real time with every SSE message received from the chatroom.</div>\n\n

What's wrong with that? Is it that you want the text content to literally appear on different lines? If so, couldn't you add a <br> tag, such as this?

 <div hx-ext="sse" sse-connect="/chatroom" sse-swap="message" sse-json="true">
      Contents of this box will be updated in real time<br>
      with every SSE message received from the chatroom.
  </div>

Or, perhaps the entire thing in one line/string, such as this:

data: <div hx-ext="sse" sse-connect="/chatroom" sse-swap="message" sse-json="true">Contents of this box will be updated in real time<br>with every SSE message received from the chatroom.</div>\n\n

Also, isnt CSS the appropriate tool for indentation? e.g. https://www.w3schools.com/cssref/tryit.php?filename=trycss_text-indent

nickchomey commented 7 months ago

I just did some testing of my own, and the best options seem to be

  1. Have the server-side script loop through the fragment and emit each line sequentially
  2. remove all line breaks and just send the fragment in one line, "minified" - probably better that way as it is less CPU and often CDNs etc (e.g. Cloudflare) minify html anyway

In php, it looks something like this $htmlFragment = str_replace(array("\r", "\n"), '', $htmlFragment);

Edit: I'm now seeing that this is precisely the "workaround" that you tried. So, we're back to the same question - what exactly is the issue? Why isn't it "correct"? I suspect you just need to use <br> and CSS where necessary.

roharvey commented 6 months ago

SSE spec lets you do what you want with pre. Just append data: to each line:

image

Produces:

image image