mirror of
https://git.hmsn.ink/kospo/svcm/oa.git
synced 2026-03-20 02:52:18 +09:00
145 lines
4.3 KiB
TypeScript
145 lines
4.3 KiB
TypeScript
import devalue from '@nuxt/devalue'
|
|
import { getRequestURL, setResponseHeader } from 'h3'
|
|
import { renderToWebStream } from 'vue/server-renderer'
|
|
import { renderSSRHead } from '@unhead/ssr'
|
|
import { minify } from 'html-minifier-terser'
|
|
|
|
import type { VueroServerRender, VueroSSRContext } from '/@server/types'
|
|
import { createApp } from '/@src/app'
|
|
|
|
const placeholderAppRe = /<div id="app"([\s\w\-"'=[\]]*)><\/div>/
|
|
const placeholderStream = '<!--vueroplaceholder-->'
|
|
|
|
export const render: VueroServerRender = async ({
|
|
event,
|
|
manifest,
|
|
template,
|
|
}) => {
|
|
const url = getRequestURL(event)
|
|
|
|
event.context.initialState = {}
|
|
|
|
const { app, router, pinia, head } = await createApp(event)
|
|
|
|
// set the router to the desired URL before rendering
|
|
router.push(url.pathname)
|
|
await router.isReady()
|
|
|
|
// passing SSR context object which will be available via useSSRContext()
|
|
// @vitejs/plugin-vue injects code into a component's setup() that registers
|
|
// itself on ctx.modules. After the render, ctx.modules would contain all the
|
|
// components that have been instantiated during this render call.
|
|
const ctx: VueroSSRContext = {
|
|
event,
|
|
}
|
|
|
|
const stream = renderToWebStream(app, ctx)
|
|
const {
|
|
headTags,
|
|
htmlAttrs,
|
|
bodyAttrs,
|
|
bodyTags,
|
|
bodyTagsOpen,
|
|
} = await renderSSRHead(head)
|
|
|
|
event.context.initialState.pinia = pinia?.state.value
|
|
|
|
// the SSR manifest generated by Vite contains module -> chunk/asset mapping
|
|
// which we can then use to determine what files need to be preloaded for this
|
|
// request.
|
|
const preloadLinks = renderPreloadLinks(ctx.modules, manifest)
|
|
|
|
let wrapper = template
|
|
.replace(`<html>`, `<html${htmlAttrs}>`)
|
|
.replace(`<head>`, `<head>${headTags}`)
|
|
.replace(`</head>`, `${preloadLinks}</head>`)
|
|
.replace(`<body>`, `<body${bodyAttrs}>${bodyTagsOpen}`)
|
|
.replace(`</body>`, `${bodyTags}</body>`)
|
|
|
|
// minify app wrapper
|
|
wrapper = await minify(wrapper, {
|
|
collapseWhitespace: true,
|
|
collapseInlineTagWhitespace: true,
|
|
removeComments: true,
|
|
minifyJS: true,
|
|
})
|
|
|
|
wrapper = wrapper.replace(
|
|
placeholderAppRe,
|
|
`<div id="app" data-server-rendered="true"$1>${placeholderStream}</div><script>window.__vuero__=${devalue(
|
|
event.context.initialState,
|
|
)}</script>`,
|
|
)
|
|
|
|
setResponseHeader(event, 'Content-Type', 'text/html')
|
|
|
|
return stream.pipeThrough(wrapTemplate(wrapper))
|
|
}
|
|
|
|
function wrapTemplate(html: string) {
|
|
const [pre, post] = html.split(placeholderStream) as [string, string]
|
|
|
|
return new TransformStream<Uint8Array, Uint8Array>({
|
|
start(controller) {
|
|
controller.enqueue(new TextEncoder().encode(pre))
|
|
},
|
|
flush(controller) {
|
|
controller.enqueue(new TextEncoder().encode(post))
|
|
},
|
|
})
|
|
}
|
|
|
|
function renderPreloadLinks(modules?: any, manifest?: any) {
|
|
let links = ''
|
|
if (!modules) return links
|
|
if (!manifest) return links
|
|
|
|
const seen = new Set()
|
|
modules?.forEach((id: string) => {
|
|
const files = manifest[id]
|
|
if (files) {
|
|
files.forEach((file: any) => {
|
|
if (!seen.has(file)) {
|
|
seen.add(file)
|
|
links += renderPreloadLink(file)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
return links
|
|
}
|
|
|
|
function renderPreloadLink(file: string) {
|
|
if (file.endsWith('.js') || file.endsWith('.mjs')) {
|
|
return `<link rel="modulepreload" crossorigin href="${file}">`
|
|
}
|
|
else if (file.endsWith('.css')) {
|
|
return `<link rel="stylesheet" href="${file}">`
|
|
}
|
|
else if (file.endsWith('.woff')) {
|
|
return `<link rel="preload" href="${file}" as="font" type="font/woff" crossorigin>`
|
|
}
|
|
else if (file.endsWith('.woff2')) {
|
|
return `<link rel="preload" href="${file}" as="font" type="font/woff2" crossorigin>`
|
|
}
|
|
else if (file.endsWith('.gif')) {
|
|
return `<link rel="preload" href="${file}" as="image" type="image/gif">`
|
|
}
|
|
else if (file.endsWith('.jpg') || file.endsWith('.jpeg')) {
|
|
return `<link rel="preload" href="${file}" as="image" type="image/jpeg">`
|
|
}
|
|
else if (file.endsWith('.png')) {
|
|
return `<link rel="preload" href="${file}" as="image" type="image/png">`
|
|
}
|
|
else if (file.endsWith('.webp')) {
|
|
return `<link rel="preload" href="${file}" as="image" type="image/webp">`
|
|
}
|
|
else if (file.endsWith('.svg')) {
|
|
return `<link rel="prefetch" href="${file}" as="image" type="image/svg+xml"/>`
|
|
}
|
|
else {
|
|
console.log('missing preload link for', file)
|
|
return ''
|
|
}
|
|
}
|