,
+ manifest?: object) {
const $ = load(template)
- await router.push(url)
- await router.isReady()
+ $('#app').html(rendered)
- let redirect = null
+ const preloadLinks = renderPreloadLinks(ctx.modules, manifest ?? {})
+ $('head').append(preloadLinks)
- const ctx: SSRContext = {
- request,
- response,
- redirect: (url: string) => {
- redirect = url
- },
+ if (cssModules !== undefined) {
+ const styles = renderCssForSsr(cssModules)
+ $('head').append(styles)
}
- const html = await renderToString(app, ctx)
- $('#app').html(html)
-
- const preloadLinks = renderPreloadLinks(ctx.modules, manifest)
- $('head').append(preloadLinks)
+ if (state !== undefined) {
+ const devalue = (await import('@nuxt/devalue')).default
+ $('body').append(``)
+ }
- const resolvedTags = await head.resolveTags() as HeadTag[]
+ const resolvedTags = await head.resolveTags()
let tags = ['title', 'meta', 'link', 'base', 'style', 'script', 'noscript']
@@ -103,7 +96,8 @@ export async function generateTemplate(
}
tags.map(tag => {
- resolvedTags.filter(t => t.tag === tag)
+ resolvedTags
+ .filter(t => t.tag === tag)
.map(t => {
let props = ''
@@ -135,15 +129,5 @@ export async function generateTemplate(
}
}
- if (state !== undefined) {
- $('body').append(``)
- }
-
- const teleports = ctx.teleports ?? {}
-
- if (teleports['#teleports'] !== undefined) {
- $('body').append(`${teleports['#teleports']}
`)
- }
-
- return { html: $.html(), redirect }
+ return $.html()
}
diff --git a/src/plugin/index.ts b/src/plugin/index.ts
index 8341f80..83fe0e9 100644
--- a/src/plugin/index.ts
+++ b/src/plugin/index.ts
@@ -1,13 +1,13 @@
import { readFileSync } from 'node:fs'
-import { basename, resolve } from 'node:path'
+import { resolve } from 'node:path'
import { cwd } from 'node:process'
-import { Plugin } from 'vite'
+import type { Plugin } from 'vite'
import type { App } from 'vue'
+import { renderToString, SSRContext } from 'vue/server-renderer'
// @ts-ignore
import cookieParser from 'cookie-parser'
import { transformEntrypoint } from './transformEntrypoint'
-import { generateTemplate } from './generateTemplate'
-
+import { generateHtml } from './generateHtml'
import type { Params, CallbackFn } from '../types'
declare function vueSSRFn(App: App, params: Params, cb: CallbackFn): { App: App } & Params & { cb: CallbackFn }
@@ -81,10 +81,41 @@ export default function vueSsrPlugin(): Plugin {
let template: string | undefined = readFileSync(resolve(cwd(), 'index.html'), 'utf-8')
template = await server.transformIndexHtml(url!, template)
- const main: ReturnType = (await server.ssrLoadModule(resolve(cwd(), ssr as string))).default
+ const { App, routes, cb, scrollBehavior }: ReturnType = (await server.ssrLoadModule(resolve(cwd(), ssr as string))).default
+
+ const { vueSSR } = (await import('./vue'))
- // @ts-ignore
- const { html, redirect } = await generateTemplate(main, url!, template, req, res)
+ const { app, router, state, head } = vueSSR(App, { routes, scrollBehavior }, undefined, true, true)
+
+ if (cb !== undefined) {
+ cb({ app, router, state })
+ // cb({ app, router, state, req, res })
+ }
+
+ await router.push(url!)
+ await router.isReady()
+
+ let redirect = null
+
+ const ctx: SSRContext = {
+ req,
+ res,
+ redirect: (url: string) => {
+ redirect = url
+ },
+ }
+
+ const rendered = await renderToString(app, ctx)
+
+ const loadedModules = server.moduleGraph.getModulesByFile(resolve(cwd(), ssr as string))
+
+ const html = await generateHtml(
+ template,
+ rendered,
+ ctx,
+ state,
+ head,
+ loadedModules)
if (redirect !== null) {
// https://github.com/vitejs/vite/discussions/6562#discussioncomment-1999566
@@ -103,4 +134,44 @@ export default function vueSsrPlugin(): Plugin {
}
}
+async function generateTemplate(
+ { App, routes, scrollBehavior, cb }: { App: App } & Params & { cb: CallbackFn },
+ url: string,
+ template: string,
+ request: Request,
+ response: Response,
+ manifest: object = {})
+{
+ const { vueSSR } = (await import('./vue'))
+
+ const { app, router, state, head } = vueSSR(App, { routes, scrollBehavior }, undefined, true, true)
+
+ if (cb !== undefined) {
+ cb({ app, router, state })
+ // cb({ app, router, state, req, res })
+ }
+
+ await router.push(url!)
+ await router.isReady()
+
+ let redirect = null
+
+ const ctx: SSRContext = {
+ req: request,
+ res: response,
+ redirect: (url: string) => {
+ redirect = url
+ },
+ }
+
+ const rendered = await renderToString(app, ctx)
+
+ const html = await generateHtml(template, rendered, ctx, state, head, undefined, manifest)
+
+ return {
+ html,
+ redirect,
+ }
+}
+
export { generateTemplate }
diff --git a/src/plugin/renderCssForSsr.ts b/src/plugin/renderCssForSsr.ts
new file mode 100644
index 0000000..852ad08
--- /dev/null
+++ b/src/plugin/renderCssForSsr.ts
@@ -0,0 +1,27 @@
+import { ModuleNode } from 'vite'
+
+export function renderCssForSsr(mods: Set, styles = new Map(), checkedComponents = new Set()) {
+ for (const mod of mods) {
+ if ((mod.file?.endsWith('.scss')
+ || mod.file?.endsWith('.css')
+ || mod.id?.includes('vue&type=style')) &&
+ mod.ssrModule
+ ) {
+ styles.set(mod.id!, mod.ssrModule.default)
+ }
+
+ if (mod.importedModules.size > 0 && !checkedComponents.has(mod.id)) {
+ checkedComponents.add(mod.id)
+
+ renderCssForSsr(mod.importedModules, styles, checkedComponents)
+ }
+ }
+
+ let result = ''
+
+ styles.forEach((content, id) => {
+ result = result.concat(``)
+ })
+
+ return result
+}
diff --git a/src/plugin/vue.ts b/src/plugin/vue.ts
index cae1624..83a1545 100644
--- a/src/plugin/vue.ts
+++ b/src/plugin/vue.ts
@@ -1,14 +1,15 @@
-import { type Component, createSSRApp, createApp } from 'vue'
+import { type Component, type App, createSSRApp, createApp } from 'vue'
import {
+ Router,
createMemoryHistory,
createRouter,
createWebHistory
} from 'vue-router'
-import { createHead } from '@vueuse/head'
+import { MergeHead, VueHeadClient, createHead } from '@unhead/vue'
import type { State, CallbackFn, Params } from '../types'
-export function vueSSR(App: Component, params: Params, cb?: CallbackFn, ssrBuild = false, ssr = false) {
- const { routes, head: headDefaults, scrollBehavior } = params
+export function vueSSR(App: Component, params: Params, cb?: CallbackFn, ssrBuild = false, ssr = false): { app: App, router: Router, state: State, head: VueHeadClient, scrollBehavior: any, cb: CallbackFn | undefined } {
+ const { routes, scrollBehavior } = params
const state: State = {
value: undefined,
@@ -28,7 +29,7 @@ export function vueSSR(App: Component, params: Params, cb?: CallbackFn, ssrBuild
})
app.use(router)
- const head = createHead(headDefaults)
+ const head = createHead()
app.use(head)
if (cb !== undefined) {
diff --git a/src/types.ts b/src/types.ts
index 2600290..c95015a 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,13 +1,11 @@
import type { App } from 'vue'
import type { RouteRecordRaw, Router, RouterScrollBehavior } from 'vue-router'
import type { Request, Response } from 'express'
-import type { Head } from '@unhead/schema'
export type State = { value?: any }
export type Params = {
routes: RouteRecordRaw[]
- head?: Head
scrollBehavior?: RouterScrollBehavior
}