You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
132 lines
4.1 KiB
132 lines
4.1 KiB
import { renderToString } from 'vue/server-renderer'
|
|
import { createApp } from './main'
|
|
import { createSSRContext } from 'x/composables/ssrContext'
|
|
import { basename } from 'node:path'
|
|
import { createHead } from '@unhead/vue/server'
|
|
|
|
export async function render(url: string, manifest: any, init?: { cookies?: Record<string, string> }) {
|
|
// 创建 SSR 上下文,包含数据缓存与 cookies
|
|
const ssrContext = createSSRContext()
|
|
if (init?.cookies) {
|
|
ssrContext.cookies = { ...init.cookies }
|
|
}
|
|
|
|
// 将 SSR 上下文传递给应用创建函数
|
|
const { app, pinia, router } = createApp(ssrContext)
|
|
|
|
const unHead = createHead({
|
|
disableDefaults: true
|
|
})
|
|
app.use(unHead)
|
|
|
|
// https://github.com/antfu-collective/vitesse
|
|
// https://github.com/unjs/unhead/blob/main/examples/vite-ssr-vue/src/entry-server.ts
|
|
useSeoMeta({
|
|
title: 'My Awesome Site',
|
|
description: 'My awesome site description',
|
|
}, { head: unHead })
|
|
|
|
useHead({
|
|
title: "aa",
|
|
htmlAttrs: {
|
|
lang: "zh-CN"
|
|
},
|
|
meta: [
|
|
{
|
|
charset: "UTF-8"
|
|
},
|
|
{
|
|
name: "viewport",
|
|
content: "width=device-width, initial-scale=1.0",
|
|
},
|
|
{
|
|
name: "description",
|
|
content: "Welcome to our website",
|
|
},
|
|
],
|
|
link: [
|
|
{
|
|
rel: "icon",
|
|
type: "image/svg+xml",
|
|
// href: () => (preferredDark.value ? "/favicon-dark.svg" : "/favicon.svg"),
|
|
href: () => "/vite.svg",
|
|
},
|
|
],
|
|
}, { head: unHead })
|
|
|
|
router.push(url); // 根据请求 URL 设置路由
|
|
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 = { cache: ssrContext.cache }
|
|
const html = await renderToString(app, ctx)
|
|
|
|
// 将 SSR 上下文数据序列化到 HTML 中
|
|
// 使用更安全的方式序列化 Map
|
|
const cacheEntries = ssrContext.cache ? Array.from(ssrContext.cache.entries()) : []
|
|
const ssrData = JSON.stringify(cacheEntries)
|
|
|
|
// @ts-ignore
|
|
const preloadLinks = renderPreloadLinks(ctx.modules, manifest)
|
|
|
|
console.log('[SSR] 序列化缓存数据:', cacheEntries)
|
|
|
|
const head = `
|
|
<script>
|
|
window.__SSR_CONTEXT__ = {
|
|
cache: new Map(${ssrData}),
|
|
piniaState: ${JSON.stringify(pinia.state.value || {})}
|
|
};
|
|
</script>
|
|
${preloadLinks}
|
|
`
|
|
|
|
return { html, head, unHead, setCookies: ssrContext.setCookies || [] }
|
|
}
|
|
|
|
function renderPreloadLinks(modules: any, manifest: any) {
|
|
let links = ''
|
|
const seen = new Set()
|
|
modules.forEach((id: any) => {
|
|
const files = manifest[id]
|
|
if (files) {
|
|
files.forEach((file: any) => {
|
|
if (!seen.has(file)) {
|
|
seen.add(file)
|
|
const filename = basename(file)
|
|
if (manifest[filename]) {
|
|
for (const depFile of manifest[filename]) {
|
|
links += renderPreloadLink(depFile)
|
|
seen.add(depFile)
|
|
}
|
|
}
|
|
links += renderPreloadLink(file)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
return links
|
|
}
|
|
|
|
function renderPreloadLink(file: string) {
|
|
if (file.endsWith('.js')) {
|
|
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 {
|
|
return ''
|
|
}
|
|
}
|
|
|