unhead icon indicating copy to clipboard operation
unhead copied to clipboard

`headTags` are empty when using `pipeToWebWritable`

Open daliborgogic opened this issue 2 years ago • 5 comments

import { createHead, useHead } from '@unhead/vue'
import { createSSRApp, ref } from 'vue'
import { pipeToWebWritable } from '@vue/server-renderer'
import { renderSSRHead } from '@unhead/ssr'
import { describe, it, expect } from 'vitest'

const template = String.raw`<!doctype html><html lang="en"><!--head--><div id="app"><!--app--></div></html>`

describe('vue ssr', () => {
  it('pipeToWebWritable', async () => {
    const head = createHead()
    const app = createSSRApp({
      async setup() {
        const title = ref('initial title')
        useHead({
          title,
        })
        await new Promise(resolve => setTimeout(resolve, 200))
        title.value = 'new title'
        return () => '<div>hi</div>'
      },
    })
    app.use(head)

    const [prepend, append] = template.split('<!--app-->')
    const { headTags } = await renderSSRHead(head)
    const encoder = new TextEncoder()

    const { writable } = new TransformStream({
      start(controller) {
        // prepend.replace('<!--head-->', headTags)
        controller.enqueue(encoder.encode(prepend))
      },
      flush(controller) {
        controller.enqueue(encoder.encode(append))
      }
    })

    pipeToWebWritable(app, {}, writable)

    expect(headTags).eq('<title>new title</title>')
  })
})

 ❯ test/stream.test.js:29:22
     27|     await pipeToWebWritable(app, {}, writable)
     28| 
     29|     expect(headTags).eq('<title>new title</title>')
       |                      ^
     30|   })
     31| })
-    Expected   "<title>new title</title>"
+    Received   ""

daliborgogic avatar Nov 28 '22 13:11 daliborgogic

Hey @daliborgogic

Thanks for the issue.

I'm not entirely sure how to solve this, as head tags are written as the Vue app is rendered. Do you have any ideas?

harlan-zw avatar Dec 01 '22 01:12 harlan-zw

@harlan-zw For now no idea. Will check what we can and what we can with hooks. You can close this issue. If we found a solution we will make a PR.

daliborgogic avatar Dec 14 '22 12:12 daliborgogic

have you tried this code @daliborgogic @harlan-zw ?

const appHtml = await renderToString(app);
const { headTags } = await renderSSRHead(head);

I had the same case, then I was able to solve it by changing the order, first renderToString then renderSSRRHead

bukandicki avatar Dec 27 '22 09:12 bukandicki

Isn't that just regular SSR? My understanding of the Vue rendering stream is that it's sending chunks as they are rendered so the head data would need to be appended as it's rendered, which it isn't for performance reasons.

harlan-zw avatar Dec 27 '22 09:12 harlan-zw

@harlan-zw yes, it's regular SSR. You are right. Workaround until there is a better way

- const { headTags } = await renderSSRHead(head)
+ const decoder = new TextDecoder()
+ let c
+ let once = false
  const { readable, writable } = new TransformStream({
-   start() {/**/},
+   async transform(chunk, controller) {     
+     if (!once) {
+      const decodedChunk = decoder.decode(chunk)
+      const { headTags } = await renderSSRHead(head)
+      const replacedHead = prepend.replace(`<!--head-->`, headTags)
+      const encodedChunk = encoder.encode(replacedHead + decodedChunk)
+      c = encodedChunk
+      once = true 
+     } else {
+       c = chunk
+     }
+     controller.enqueue(c)
+   },
    flush(controller) {
      controller.enqueue(encoder.encode(append))
    }
  })

daliborgogic avatar Apr 10 '23 19:04 daliborgogic

Going to track here https://github.com/unjs/unhead/issues/396 which is part of the v2 roadmap.

harlan-zw avatar Sep 07 '24 16:09 harlan-zw