bluwy/create-vite-extra

Vite + Vue + Streaming SSR

phonzammi opened this issue ยท 7 comments

Hi @bluwy,
I want to ask for a favor.
Could you create a template of Vite + Vue + Streaming SSR ?

FYI, I have created one, but I'm not sure if it is correct. Maybe if you have some spare time, I hope you could take a look and review it too. You can check it in my repo (vite-vue-streaming-ssr).

Hope you have a nice day and night

Your implementation looks great ๐Ÿ‘ I think it could be simplified a bit to use res.write() instead of generators, but I think it's also a matter of preference then.

I think it would be nice to add a streaming implementation. Perhaps as an option in the CLI with this flow: "Vue" > "Streaming or no-streaming" > "JS or TS". Which means we probably need to create a new template-ssr-vue-streaming and template-ssr-vue-streaming-ts templates which are largely duplicates of template-ssr-vue and template-ssr-vue-ts. Feel free to contribute them!

Your implementation looks great ๐Ÿ‘

Thanks

I think it could be simplified a bit to use res.write() instead of generators

Yeah, maybe you're right, about using res.write(), I think you mean to write head & end of the html, right?

I've tried this way, but this way results with hydration mismatch :

const stream = renderToNodeStream(app, ctx)

stream.once('readable', () => {
  res.status(200).set({ 'Content-Type': 'text/html' })
  res.write(head)
})
stream.on('data', () => {
  stream.pipe(res, { end: false })
})
stream.on('end', () => {
  res.write(tail)
  res.end()
})
stream.on('error', (error) => {
  res.setHeader('Content-Type', 'text/json')
  res.end(JSON.stringify({ error }))
})

I've also tried this way & doesn't result with hydration mismatch : ๐Ÿ’ฏ

 const [head, tail] = template.split('<!--app-->')

const encoder = new TextEncoder()
// import { TransformStream } from 'stream/web'
const webStream = new TransformStream({ 
  start(controller) {
    controller.enqueue(encoder.encode(head))
  },
  flush(controller) {
    controller.enqueue(encoder.encode(tail))
  }
})

// import { Readable, Writable } from 'stream'
const readable = Readable.fromWeb(webStream.readable)
const writable = Writable.fromWeb(webStream.writable)

// import { pipeToNodeWritable } from 'vue/server-renderer'
pipeToNodeWritable(app, ctx, writable)

readable.pipe(res)

And I also want to try it (properly) with Node Transform

Maybe if you have any suggestions, I like to know them ๐Ÿ™

I think it would be nice to add a streaming implementation. Perhaps as an option in the CLI with this flow: "Vue" > "Streaming or no-streaming" > "JS or TS".

Yes, that would very nice ๐Ÿ‘

Feel free to contribute them!

Sure, I'll try

I think your current implementation is perfectly fine too if you used that as the template, as long as the implementation is short and concise. Don't need to sweat on the ideal way to do it for now ๐Ÿ˜…

Initially though, I was thinking along the lines of (not tested yet):

const [head, tail] = template.split('<!--app-->')

const { stream } = render()

res.write(head)
for await (const chunk of stream) {
  res.write(chunk)
}
res.write(tail)
res.end()

If there's errors from the stream, I think it should propagate to the catch handler. Even with my implementation, we might also need to handle the case where a request could be closed before we finish streaming, which means we can stop calling res.write() thereafter to save on memory. Usually that is detected through the close event.

I think your current implementation is perfectly fine too if you used that as the template, as long as the implementation is short and concise. Don't need to sweat on the ideal way to do it for now ๐Ÿ˜…

Thanks. That's right, cause it's just an example template. Let me polish it a bit then

Initially though, I was thinking along the lines of (not tested yet):

So I've just try that and surprisingly it works & no hydration mismatch. Awesome ๐Ÿ’ฏ

If there's errors from the stream, I think it should propagate to the catch handler. Even with my implementation, we might also need to handle the case where a request could be closed before we finish streaming, which means we can stop calling res.write() thereafter to save on memory. Usually that is detected through the close event.

Nice, thanks for elaborating more