mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 07:05:11 +00:00
fix(nuxt): proxy headers to islands + returned prerender hints (#21740)
This commit is contained in:
parent
5b7f52870c
commit
88bc32d42a
@ -4,6 +4,8 @@ import { hash } from 'ohash'
|
||||
import { appendResponseHeader } from 'h3'
|
||||
import { useHead } from '@unhead/vue'
|
||||
import { randomUUID } from 'uncrypto'
|
||||
import { withQuery } from 'ufo'
|
||||
|
||||
// eslint-disable-next-line import/no-restricted-paths
|
||||
import type { NuxtIslandResponse } from '../../core/runtime/nitro/renderer'
|
||||
import { getFragmentHTML, getSlotProps } from './utils'
|
||||
@ -40,6 +42,7 @@ export default defineComponent({
|
||||
const hashId = computed(() => hash([props.name, props.props, props.context]))
|
||||
const instance = getCurrentInstance()!
|
||||
const event = useRequestEvent()
|
||||
const eventFetch = process.server ? event.fetch : globalThis.fetch
|
||||
const mounted = ref(false)
|
||||
onMounted(() => { mounted.value = true })
|
||||
|
||||
@ -78,13 +81,18 @@ export default defineComponent({
|
||||
appendResponseHeader(event, 'x-nitro-prerender', url)
|
||||
}
|
||||
// TODO: Validate response
|
||||
const result = await $fetch<NuxtIslandResponse>(url, {
|
||||
responseType: 'json',
|
||||
params: {
|
||||
...props.context,
|
||||
props: props.props ? JSON.stringify(props.props) : undefined
|
||||
const r = await eventFetch(withQuery(url, {
|
||||
...props.context,
|
||||
props: props.props ? JSON.stringify(props.props) : undefined
|
||||
}))
|
||||
const result = await r.json() as NuxtIslandResponse
|
||||
// TODO: support passing on more headers
|
||||
if (process.server && process.env.prerender) {
|
||||
const hints = r.headers.get('x-nitro-prerender')
|
||||
if (hints) {
|
||||
appendResponseHeader(event, 'x-nitro-prerender', hints)
|
||||
}
|
||||
})
|
||||
}
|
||||
nuxtApp.payload.data[key] = {
|
||||
__nuxt_island: {
|
||||
key,
|
||||
|
@ -367,7 +367,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
||||
|
||||
await nitroApp.hooks.callHook('render:island', islandResponse, { event, islandContext })
|
||||
|
||||
const response: RenderResponse = {
|
||||
const response = {
|
||||
body: JSON.stringify(islandResponse, null, 2),
|
||||
statusCode: event.node.res.statusCode,
|
||||
statusMessage: event.node.res.statusMessage,
|
||||
@ -375,7 +375,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
||||
'content-type': 'application/json;charset=utf-8',
|
||||
'x-powered-by': 'Nuxt'
|
||||
}
|
||||
}
|
||||
} satisfies RenderResponse
|
||||
if (process.env.prerender) {
|
||||
ISLAND_CACHE!.set(`/__nuxt_island/${islandContext!.name}_${islandContext!.id}`, response)
|
||||
}
|
||||
@ -383,7 +383,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
||||
}
|
||||
|
||||
// Construct HTML response
|
||||
const response: RenderResponse = {
|
||||
const response = {
|
||||
body: renderHTMLDocument(htmlContext),
|
||||
statusCode: event.node.res.statusCode,
|
||||
statusMessage: event.node.res.statusMessage,
|
||||
@ -391,7 +391,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
||||
'content-type': 'text/html;charset=utf-8',
|
||||
'x-powered-by': 'Nuxt'
|
||||
}
|
||||
}
|
||||
} satisfies RenderResponse
|
||||
|
||||
return response
|
||||
})
|
||||
@ -456,7 +456,7 @@ async function renderInlineStyles (usedModules: Set<string> | string[]) {
|
||||
}
|
||||
|
||||
function renderPayloadResponse (ssrContext: NuxtSSRContext) {
|
||||
return <RenderResponse> {
|
||||
return {
|
||||
body: process.env.NUXT_JSON_PAYLOADS
|
||||
? stringify(splitPayload(ssrContext).payload, ssrContext._payloadReducers)
|
||||
: `export default ${devalue(splitPayload(ssrContext).payload)}`,
|
||||
@ -466,7 +466,7 @@ function renderPayloadResponse (ssrContext: NuxtSSRContext) {
|
||||
'content-type': process.env.NUXT_JSON_PAYLOADS ? 'application/json;charset=utf-8' : 'text/javascript;charset=utf-8',
|
||||
'x-powered-by': 'Nuxt'
|
||||
}
|
||||
}
|
||||
} satisfies RenderResponse
|
||||
}
|
||||
|
||||
function renderPayloadJsonScript (opts: { id: string, ssrContext: NuxtSSRContext, data?: any, src?: string }) {
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { readdir } from 'node:fs/promises'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { joinURL, withQuery } from 'ufo'
|
||||
import { isCI, isWindows } from 'std-env'
|
||||
import { normalize } from 'pathe'
|
||||
import { $fetch, createPage, fetch, isDev, setup, startServer, url } from '@nuxt/test-utils'
|
||||
import { join, normalize } from 'pathe'
|
||||
import { $fetch, createPage, fetch, isDev, setup, startServer, url, useTestContext } from '@nuxt/test-utils'
|
||||
import { $fetchComponent } from '@nuxt/test-utils/experimental'
|
||||
|
||||
import type { NuxtIslandResponse } from '../packages/nuxt/src/core/runtime/nitro/renderer'
|
||||
@ -418,39 +419,6 @@ describe('pages', () => {
|
||||
await page.close()
|
||||
})
|
||||
|
||||
it('/islands', async () => {
|
||||
const page = await createPage('/islands')
|
||||
await page.waitForLoadState('networkidle')
|
||||
await page.locator('#increase-pure-component').click()
|
||||
await page.waitForResponse(response => response.url().includes('/__nuxt_island/') && response.status() === 200)
|
||||
await page.waitForLoadState('networkidle')
|
||||
expect(await page.locator('#slot-in-server').first().innerHTML()).toContain('Slot with in .server component')
|
||||
expect(await page.locator('#test-slot').first().innerHTML()).toContain('Slot with name test')
|
||||
|
||||
// test fallback slot with v-for
|
||||
expect(await page.locator('.fallback-slot-content').all()).toHaveLength(2)
|
||||
// test islands update
|
||||
expect(await page.locator('.box').innerHTML()).toContain('"number": 101,')
|
||||
await page.locator('#update-server-components').click()
|
||||
await Promise.all([
|
||||
page.waitForResponse(response => response.url().includes('/__nuxt_island/LongAsyncComponent') && response.status() === 200),
|
||||
page.waitForResponse(response => response.url().includes('/__nuxt_island/AsyncServerComponent') && response.status() === 200)
|
||||
])
|
||||
await page.waitForLoadState('networkidle')
|
||||
expect(await page.locator('#async-server-component-count').innerHTML()).toContain(('1'))
|
||||
expect(await page.locator('#long-async-component-count').innerHTML()).toContain('1')
|
||||
|
||||
// test islands slots interactivity
|
||||
await page.locator('#first-sugar-counter button').click()
|
||||
expect(await page.locator('#first-sugar-counter').innerHTML()).toContain('Sugar Counter 13')
|
||||
|
||||
// test islands mounted client side with slot
|
||||
await page.locator('#show-island').click()
|
||||
expect(await page.locator('#island-mounted-client-side').innerHTML()).toContain('Interactive testing slot post SSR')
|
||||
|
||||
await page.close()
|
||||
})
|
||||
|
||||
it('/legacy-async-data-fail', async () => {
|
||||
const response = await fetch('/legacy-async-data-fail').then(r => r.text())
|
||||
expect(response).not.toContain('don\'t look at this')
|
||||
@ -1246,6 +1214,51 @@ describe.skipIf(isDev() || isWebpack)('inlining component styles', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('server components/islands', () => {
|
||||
it('/islands', async () => {
|
||||
const page = await createPage('/islands')
|
||||
await page.waitForLoadState('networkidle')
|
||||
await page.locator('#increase-pure-component').click()
|
||||
await page.waitForResponse(response => response.url().includes('/__nuxt_island/') && response.status() === 200)
|
||||
await page.waitForLoadState('networkidle')
|
||||
expect(await page.locator('#slot-in-server').first().innerHTML()).toContain('Slot with in .server component')
|
||||
expect(await page.locator('#test-slot').first().innerHTML()).toContain('Slot with name test')
|
||||
|
||||
// test fallback slot with v-for
|
||||
expect(await page.locator('.fallback-slot-content').all()).toHaveLength(2)
|
||||
// test islands update
|
||||
expect(await page.locator('.box').innerHTML()).toContain('"number": 101,')
|
||||
await page.locator('#update-server-components').click()
|
||||
await Promise.all([
|
||||
page.waitForResponse(response => response.url().includes('/__nuxt_island/LongAsyncComponent') && response.status() === 200),
|
||||
page.waitForResponse(response => response.url().includes('/__nuxt_island/AsyncServerComponent') && response.status() === 200)
|
||||
])
|
||||
await page.waitForLoadState('networkidle')
|
||||
expect(await page.locator('#async-server-component-count').innerHTML()).toContain(('1'))
|
||||
expect(await page.locator('#long-async-component-count').innerHTML()).toContain('1')
|
||||
|
||||
// test islands slots interactivity
|
||||
await page.locator('#first-sugar-counter button').click()
|
||||
expect(await page.locator('#first-sugar-counter').innerHTML()).toContain('Sugar Counter 13')
|
||||
|
||||
// test islands mounted client side with slot
|
||||
await page.locator('#show-island').click()
|
||||
expect(await page.locator('#island-mounted-client-side').innerHTML()).toContain('Interactive testing slot post SSR')
|
||||
|
||||
await page.close()
|
||||
})
|
||||
|
||||
it.skipIf(isDev)('should allow server-only components to set prerender hints', async () => {
|
||||
// @ts-expect-error ssssh! untyped secret property
|
||||
const publicDir = useTestContext().nuxt._nitro.options.output.publicDir
|
||||
expect(await readdir(join(publicDir, 'some', 'url', 'from', 'server-only', 'component')).catch(() => [])).toContain(
|
||||
isRenderingJson
|
||||
? '_payload.json'
|
||||
: '_payload.js'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe.skipIf(isDev() || isWindows || !isRenderingJson)('prefetching', () => {
|
||||
it('should prefetch components', async () => {
|
||||
await expectNoClientErrors('/prefetch/components')
|
||||
|
@ -4,6 +4,12 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { appendResponseHeader } from 'h3'
|
||||
|
||||
appendResponseHeader(useRequestEvent(), 'x-nitro-prerender', '/some/url/from/server-only/component')
|
||||
</script>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--server-only: 'server-only';
|
||||
|
Loading…
Reference in New Issue
Block a user