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

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 ''
}
}