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 }) { // 创建 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 = ` ${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 `` } else if (file.endsWith('.css')) { return `` } else if (file.endsWith('.woff')) { return ` ` } else if (file.endsWith('.woff2')) { return ` ` } else if (file.endsWith('.gif')) { return ` ` } else if (file.endsWith('.jpg') || file.endsWith('.jpeg')) { return ` ` } else if (file.endsWith('.png')) { return ` ` } else { return '' } }