mirror of
https://git.hmsn.ink/kospo/svcm/oa.git
synced 2026-03-19 21:35:11 +09:00
first
This commit is contained in:
25
server/config.ts
Normal file
25
server/config.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
import type { App } from 'h3'
|
||||
import type { StaticParams } from './types'
|
||||
import { defineLazyEventHandler } from 'h3'
|
||||
|
||||
/**
|
||||
* Extend h3 app with eventHandler
|
||||
*
|
||||
* @see https://h3.unjs.io/
|
||||
*/
|
||||
export function extendH3App(app: App) {
|
||||
app.use('/api/hello-world', defineLazyEventHandler(
|
||||
() => import('./handlers/hello-world').then(m => m.default),
|
||||
))
|
||||
}
|
||||
|
||||
export function generateStaticParams(): StaticParams {
|
||||
return {
|
||||
// '/path/with/dynamic/[slug]': async () => {
|
||||
// return [{ slug: 'first-slug' }, { slug: 'second-slug' }, { slug: 'third-slug' }]
|
||||
// },
|
||||
}
|
||||
}
|
||||
129
server/generate.ts
Normal file
129
server/generate.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
// Pre-render the app into static HTML.
|
||||
// run `pnpm ssg:build` and then `dist` can be served as a static site.
|
||||
|
||||
import fs from 'node:fs'
|
||||
import fsp from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { type ResolvedConfig, type InlineConfig, resolveConfig } from 'vite'
|
||||
import colors from 'picocolors'
|
||||
import { env } from 'std-env'
|
||||
|
||||
import { createRenderer } from './generate/renderer'
|
||||
import { buildApp } from './generate/builder'
|
||||
import { scanRoutes } from './generate/scan'
|
||||
import { populateRouteParams } from './generate/populate'
|
||||
import { renderToFile } from './generate/render-to-file'
|
||||
|
||||
async function build() {
|
||||
const mode = env.MODE || env.NODE_ENV || 'production'
|
||||
|
||||
const viteConfig: InlineConfig = {}
|
||||
const config = await resolveConfig(viteConfig, 'build', mode)
|
||||
|
||||
const cwd = process.cwd()
|
||||
const root = config.root || cwd
|
||||
const outDir = config.build.outDir || 'dist'
|
||||
const out = path.isAbsolute(outDir) ? outDir : path.join(root, outDir)
|
||||
|
||||
const outStatic = out
|
||||
const outServer = path.join(out, '.server')
|
||||
|
||||
if (fs.existsSync(out)) {
|
||||
await fsp.rm(out, { recursive: true })
|
||||
}
|
||||
|
||||
// scan base routes from src/pages
|
||||
const routes = await scanRoutes(cwd)
|
||||
|
||||
// build client and server vite apps
|
||||
await buildApp({
|
||||
config,
|
||||
viteConfig,
|
||||
outStatic,
|
||||
outServer,
|
||||
})
|
||||
|
||||
// load renderer from server build
|
||||
const {
|
||||
manifest,
|
||||
template,
|
||||
render,
|
||||
} = await createRenderer({
|
||||
outServer,
|
||||
outStatic,
|
||||
})
|
||||
|
||||
// generate urls for pre-rendering depending on static config
|
||||
const pages = await populateRouteParams({
|
||||
config,
|
||||
routes,
|
||||
})
|
||||
|
||||
// pre-render each page sequentially
|
||||
for (const page of pages) {
|
||||
const start = performance.now()
|
||||
const file = await renderToFile(render, {
|
||||
url: page.url,
|
||||
outStatic,
|
||||
manifest,
|
||||
template,
|
||||
})
|
||||
const duration = performance.now() - start
|
||||
|
||||
const formattedDuration = duration.toFixed(2).padStart(5) + 'ms'
|
||||
config.logger.info(
|
||||
colors.dim(`[${page.logPrefix}] ${colors.green(page.url)} - ${colors.cyan(file)} - ${formattedDuration}`),
|
||||
)
|
||||
}
|
||||
|
||||
// delete server build
|
||||
await fsp.rm(path.join(outServer), { recursive: true, force: true })
|
||||
|
||||
// regenerate PWA service worker with updated files
|
||||
await generatePWA({
|
||||
config,
|
||||
outStatic,
|
||||
})
|
||||
|
||||
config.logger.info(
|
||||
[
|
||||
`Pre-rendering done. You can now serve the ${colors.cyan(
|
||||
out.replace(cwd, '.'),
|
||||
)} directory with a static file server.`,
|
||||
`Example:`,
|
||||
` ${colors.green('npx serve dist -p 3000')}`,
|
||||
].join('\n'),
|
||||
)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await build()
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e)
|
||||
process.exit(1)
|
||||
}
|
||||
})()
|
||||
|
||||
async function generatePWA({
|
||||
config,
|
||||
outStatic,
|
||||
}: {
|
||||
config: ResolvedConfig
|
||||
outStatic: string
|
||||
}) {
|
||||
const pwaPlugin = config.plugins.find(plugin => plugin.name === 'vite-plugin-pwa')
|
||||
?.api
|
||||
if (pwaPlugin && !pwaPlugin.disabled && pwaPlugin.generateSW) {
|
||||
config.logger.info(colors.green('[SSG] Regenerate PWA...'))
|
||||
await pwaPlugin.generateSW()
|
||||
|
||||
// update sw.js to replace /index.html with nothing so that it can be served from /
|
||||
|
||||
const swPath = path.join(outStatic, 'sw.js')
|
||||
const swContent = await fsp.readFile(swPath, 'utf-8')
|
||||
await fsp.writeFile(swPath, swContent.replace(/\/index\.html/g, ''), 'utf-8')
|
||||
}
|
||||
}
|
||||
44
server/generate/builder.ts
Normal file
44
server/generate/builder.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import type { ResolvedConfig, InlineConfig } from 'vite'
|
||||
import colors from 'picocolors'
|
||||
import { mergeConfig, build as viteBuild } from 'vite'
|
||||
|
||||
export async function buildApp({
|
||||
config,
|
||||
viteConfig,
|
||||
outStatic,
|
||||
outServer,
|
||||
}: {
|
||||
config: ResolvedConfig
|
||||
viteConfig: InlineConfig
|
||||
outStatic: string
|
||||
outServer: string
|
||||
}) {
|
||||
config.logger.info(colors.green('[SSG] Build for client...'))
|
||||
await viteBuild(
|
||||
mergeConfig(viteConfig, {
|
||||
define: {
|
||||
__VUERO_SSR_BUILD__: true,
|
||||
},
|
||||
build: {
|
||||
ssrManifest: true,
|
||||
outDir: outStatic,
|
||||
},
|
||||
mode: config.mode,
|
||||
}),
|
||||
)
|
||||
|
||||
// server
|
||||
config.logger.info(colors.green('[SSG] Build for server...'))
|
||||
await viteBuild(
|
||||
mergeConfig(viteConfig, {
|
||||
define: {
|
||||
__VUERO_SSR_BUILD__: 'true',
|
||||
},
|
||||
build: {
|
||||
ssr: 'src/entry-server.ts',
|
||||
outDir: outServer,
|
||||
},
|
||||
mode: config.mode,
|
||||
}),
|
||||
)
|
||||
}
|
||||
146
server/generate/populate.ts
Normal file
146
server/generate/populate.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import type { ResolvedConfig } from 'vite'
|
||||
import colors from 'picocolors'
|
||||
import { generateStaticParams } from '../config'
|
||||
|
||||
export const routeParamRe = /(\[.*?\])/g
|
||||
|
||||
interface PrerenderPage {
|
||||
url: string
|
||||
logPrefix: string
|
||||
}
|
||||
|
||||
export async function populateRouteParams({
|
||||
routes,
|
||||
config,
|
||||
}: {
|
||||
config: ResolvedConfig
|
||||
routes: string[]
|
||||
}) {
|
||||
const staticParams = generateStaticParams()
|
||||
const pages: PrerenderPage[] = []
|
||||
|
||||
for (const index in routes) {
|
||||
const url: string
|
||||
= routes[index] === '/' ? '/' : routes[index].replace(/\/$/, '') // remove trailing slash
|
||||
|
||||
const logCount = `${1 + parseInt(index, 10)}/${routes.length}`
|
||||
|
||||
if (url.includes('[')) {
|
||||
const routeStaticParamsFn
|
||||
= url in staticParams ? staticParams[url as keyof typeof staticParams] : undefined
|
||||
|
||||
if (!routeStaticParamsFn) {
|
||||
config.logger.warn(
|
||||
`dynamic route (${logCount}) ${colors.yellow(
|
||||
url,
|
||||
)} - missing static config - update ${colors.cyan(
|
||||
'./build-ssg.config.ts',
|
||||
)} to generate static params for this route.`,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
// extract route params from url (e.g. /[id] or /[[slug]] or /[...all])
|
||||
const params = (url.match(routeParamRe) || []).map((p: string) => {
|
||||
const required = !p.includes('[[')
|
||||
const array = p.includes('...')
|
||||
const name = p.replaceAll(/\[/g, '').replaceAll(/\]/g, '').replaceAll(/\./g, '')
|
||||
|
||||
return {
|
||||
required,
|
||||
array,
|
||||
name,
|
||||
param: p,
|
||||
}
|
||||
})
|
||||
const routeStaticParams = await staticParams[url as keyof typeof staticParams]()
|
||||
|
||||
if (!routeStaticParams || !Array.isArray(routeStaticParams)) {
|
||||
config.logger.warn(
|
||||
`dynamic route (${logCount}) ${colors.yellow(
|
||||
url,
|
||||
)} - static params must be an array`,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
// check if static params are valid
|
||||
const invalidParams = routeStaticParams.filter((param) => {
|
||||
return params.some((p) => {
|
||||
if (p.required && !(p.name in param)) {
|
||||
config.logger.warn(
|
||||
`dynamic route (${logCount}) ${colors.yellow(
|
||||
url,
|
||||
)} - missing required param ${colors.cyan(p.name)}`,
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
if (p.array && p.name in param) {
|
||||
const value = param[p.name as keyof typeof param]
|
||||
const valid = Array.isArray(value)
|
||||
if (!valid) {
|
||||
config.logger.warn(
|
||||
`dynamic route (${logCount}) ${colors.yellow(url)} - param ${colors.cyan(
|
||||
p.name,
|
||||
)} must be an array, got string "${colors.cyan(value)}"`,
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
||||
else if (!p.array && p.name in param) {
|
||||
const value = param[p.name as keyof typeof param]
|
||||
const valid = !Array.isArray(value)
|
||||
if (!valid) {
|
||||
const values = `[${value.join(', ')}]`
|
||||
config.logger.warn(
|
||||
`dynamic route (${logCount}) ${colors.yellow(url)} - param ${colors.cyan(
|
||||
p.name,
|
||||
)} must be string, got array ${colors.cyan(values)}`,
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (invalidParams.length) {
|
||||
continue
|
||||
}
|
||||
|
||||
// render each static param
|
||||
for (const subindex in routeStaticParams) {
|
||||
const logSubCount = `${1 + parseInt(subindex, 10)}/${routeStaticParams.length}`
|
||||
const param = routeStaticParams[subindex]
|
||||
|
||||
const paramUrl = params.reduce((url, p) => {
|
||||
if (p.name in param) {
|
||||
const value = param[p.name as keyof typeof param]
|
||||
if (Array.isArray(value)) {
|
||||
return url.replace(p.param, value.join('/'))
|
||||
}
|
||||
else {
|
||||
return url.replace(p.param, value)
|
||||
}
|
||||
}
|
||||
else {
|
||||
return url.replace(p.param, '')
|
||||
}
|
||||
}, url)
|
||||
|
||||
pages.push({
|
||||
url: paramUrl,
|
||||
logPrefix: logSubCount,
|
||||
})
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
pages.push({
|
||||
url,
|
||||
logPrefix: logCount,
|
||||
})
|
||||
}
|
||||
|
||||
return pages
|
||||
}
|
||||
61
server/generate/render-to-file.ts
Normal file
61
server/generate/render-to-file.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { ServerResponse, IncomingMessage } from 'node:http'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { Socket } from 'node:net'
|
||||
import { H3Event } from 'h3'
|
||||
|
||||
import type { VueroServerRender } from '../types'
|
||||
import { resolve } from '../utils'
|
||||
|
||||
export async function renderToFile(render: VueroServerRender, {
|
||||
url,
|
||||
template,
|
||||
manifest,
|
||||
outStatic,
|
||||
}: {
|
||||
url: string
|
||||
template: string
|
||||
manifest: Record<string, string[]>
|
||||
outStatic: string
|
||||
}) {
|
||||
const sock = new Socket()
|
||||
const req = new IncomingMessage(sock)
|
||||
const res = new ServerResponse(req)
|
||||
const event = new H3Event(req, res)
|
||||
|
||||
const html = await render({
|
||||
event,
|
||||
manifest,
|
||||
template,
|
||||
})
|
||||
|
||||
const base = url.endsWith('/') ? `${url}` : `${url}/`
|
||||
const file = `${base}index.html`
|
||||
const filePath = path.join(outStatic, file)
|
||||
|
||||
const dirname = path.dirname(filePath)
|
||||
if (!fs.existsSync(dirname)) {
|
||||
fs.mkdirSync(dirname, { recursive: true })
|
||||
}
|
||||
|
||||
if (typeof html === 'string') {
|
||||
fs.writeFileSync(resolve(filePath), html)
|
||||
}
|
||||
else {
|
||||
const stream = fs.createWriteStream(resolve(filePath))
|
||||
|
||||
await html.pipeTo(new WritableStream({
|
||||
write(chunk) {
|
||||
stream.write(chunk)
|
||||
},
|
||||
close() {
|
||||
stream.end()
|
||||
},
|
||||
abort() {
|
||||
stream.end()
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
return filePath
|
||||
}
|
||||
30
server/generate/renderer.ts
Normal file
30
server/generate/renderer.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import fsp from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
|
||||
import type { VueroServerRender } from '../types'
|
||||
|
||||
export async function createRenderer({
|
||||
outServer,
|
||||
outStatic,
|
||||
}: {
|
||||
outServer: string
|
||||
outStatic: string
|
||||
}) {
|
||||
const template = await fsp.readFile(path.join(outStatic, './index.html'), 'utf-8')
|
||||
const manifest = JSON.parse(
|
||||
await fsp.readFile(path.join(outStatic, './.vite/ssr-manifest.json'), 'utf-8'),
|
||||
)
|
||||
|
||||
const prefix = process.platform === 'win32' ? 'file://' : ''
|
||||
const entryServer = path.join(prefix, outServer, 'entry-server.mjs')
|
||||
|
||||
// const _require = createRequire(import.meta.url)
|
||||
|
||||
const render: VueroServerRender = (await import(entryServer)).render
|
||||
|
||||
return {
|
||||
manifest,
|
||||
template,
|
||||
render,
|
||||
}
|
||||
}
|
||||
19
server/generate/scan.ts
Normal file
19
server/generate/scan.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import path from 'node:path'
|
||||
import fg from 'fast-glob'
|
||||
|
||||
export async function scanRoutes(cwd: string) {
|
||||
const files = await fg([path.resolve(cwd, 'src/pages/**/*.vue').replace(/\\/g, '/')])
|
||||
|
||||
return files
|
||||
.filter(path => !path.includes('src/pages/[...all].vue')) // ignore root catch-all route
|
||||
.map((file) => {
|
||||
const name = file
|
||||
.replace(/\.vue$/, '')
|
||||
.replace(cwd.replace(/\\/g, '/'), '')
|
||||
.replace(/\/+/g, '/')
|
||||
.replace('/src/pages/', '')
|
||||
.toLowerCase()
|
||||
|
||||
return '/' + name.replace(/index$/, '')
|
||||
})
|
||||
}
|
||||
5
server/handlers/hello-world.ts
Normal file
5
server/handlers/hello-world.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { defineEventHandler } from 'h3'
|
||||
|
||||
export default defineEventHandler(() => {
|
||||
return 'Hello World!'
|
||||
})
|
||||
80
server/serve.ts
Normal file
80
server/serve.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import {
|
||||
createApp,
|
||||
fromNodeMiddleware,
|
||||
toNodeListener,
|
||||
} from 'h3'
|
||||
import { listen } from 'listhen'
|
||||
|
||||
import { isProduction, env } from 'std-env'
|
||||
import { resolve } from './utils'
|
||||
import { createRenderer, loadAssets } from './serve/renderer'
|
||||
import { createEventHandler } from './serve/event'
|
||||
import { extendH3App } from './config'
|
||||
import { registerProcessHandlers } from './serve/process-handlers'
|
||||
|
||||
async function createServer() {
|
||||
const app = createApp({
|
||||
debug: !isProduction,
|
||||
})
|
||||
|
||||
const { vite, render } = await createRenderer()
|
||||
const { template, manifest } = await loadAssets()
|
||||
const handler = createEventHandler({
|
||||
vite,
|
||||
render,
|
||||
template,
|
||||
manifest,
|
||||
})
|
||||
|
||||
// During dev, we use vite's connect instance as middleware
|
||||
// @see https://vitejs.dev/guide/ssr.html
|
||||
if (!isProduction && vite) {
|
||||
app.use(fromNodeMiddleware(vite.middlewares))
|
||||
}
|
||||
|
||||
if (isProduction) {
|
||||
const [
|
||||
compression,
|
||||
serveStatic,
|
||||
] = await Promise.all([
|
||||
import('compression').then(m => m.default || m),
|
||||
import('serve-static').then(m => m.default || m),
|
||||
])
|
||||
|
||||
// @ts-expect-error - express middleware
|
||||
app.use(fromNodeMiddleware(compression()))
|
||||
app.use(
|
||||
fromNodeMiddleware(
|
||||
serveStatic(resolve('../dist/client'), {
|
||||
index: false,
|
||||
fallthrough: true,
|
||||
maxAge: '1w',
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Extend h3 app with user eventHandler via config
|
||||
extendH3App(app)
|
||||
|
||||
// Register the catch-all handler which will render our app
|
||||
app.use(handler)
|
||||
|
||||
return { app }
|
||||
}
|
||||
|
||||
// start h3 server
|
||||
const port = env.PORT || 3000
|
||||
createServer()
|
||||
.then(({ app }) => listen(toNodeListener(app), { port }))
|
||||
.catch((error) => {
|
||||
if (!isProduction) {
|
||||
console.error('[dev] [serverError] ', error)
|
||||
}
|
||||
else {
|
||||
console.error('[serverError] ' + error)
|
||||
}
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
registerProcessHandlers()
|
||||
67
server/serve/event.ts
Normal file
67
server/serve/event.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
import {
|
||||
setResponseStatus,
|
||||
setResponseHeader,
|
||||
getRequestURL,
|
||||
eventHandler,
|
||||
} from 'h3'
|
||||
import { isProduction, isDebug } from 'std-env'
|
||||
import type { ViteDevServer } from 'vite'
|
||||
|
||||
import type { VueroServerRender } from '../types'
|
||||
import { resolve } from '../utils'
|
||||
|
||||
export function createEventHandler({
|
||||
vite,
|
||||
render,
|
||||
template: baseTemplate,
|
||||
manifest,
|
||||
}: {
|
||||
vite?: ViteDevServer
|
||||
render: VueroServerRender
|
||||
template: string
|
||||
manifest: Record<string, any>
|
||||
}) {
|
||||
return eventHandler(async (event) => {
|
||||
try {
|
||||
// load template and render function from vue app
|
||||
let template = baseTemplate
|
||||
if (!isProduction && vite) {
|
||||
const url = getRequestURL(event)
|
||||
// always read fresh template in dev
|
||||
template = readFileSync(resolve('../index.html'), 'utf-8')
|
||||
template = await vite.transformIndexHtml(url.pathname, template)
|
||||
|
||||
// reload the server entrypoint on every request in dev
|
||||
render = (await vite.ssrLoadModule('/src/entry-server.ts')).render
|
||||
}
|
||||
|
||||
// render the vue app to HTML
|
||||
return await render({
|
||||
event,
|
||||
manifest,
|
||||
template,
|
||||
})
|
||||
}
|
||||
catch (error: any) {
|
||||
// handle error 500 page
|
||||
if (!isProduction || isDebug) {
|
||||
setResponseHeader(event, 'Cache-Control', 'no-cache, no-store, must-revalidate')
|
||||
setResponseStatus(event, 500)
|
||||
|
||||
vite?.ssrFixStacktrace(error)
|
||||
console.error('[dev] [pageError] ', error)
|
||||
|
||||
return error.message
|
||||
}
|
||||
else {
|
||||
setResponseHeader(event, 'Cache-Control', 'no-cache, no-store, must-revalidate')
|
||||
setResponseStatus(event, 500)
|
||||
|
||||
console.error('[pageError] ' + error)
|
||||
return 'Internal Server Error'
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
20
server/serve/process-handlers.ts
Normal file
20
server/serve/process-handlers.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { isProduction, isDebug } from 'std-env'
|
||||
|
||||
export function registerProcessHandlers() {
|
||||
if (!isProduction || isDebug) {
|
||||
process.on('unhandledRejection', error =>
|
||||
console.error('[dev] [unhandledRejection]', error),
|
||||
)
|
||||
process.on('uncaughtException', error =>
|
||||
console.error('[dev] [uncaughtException]', error),
|
||||
)
|
||||
}
|
||||
else {
|
||||
process.on('unhandledRejection', error =>
|
||||
console.error('[unhandledRejection] ' + error),
|
||||
)
|
||||
process.on('uncaughtException', error =>
|
||||
console.error('[uncaughtException] ' + error),
|
||||
)
|
||||
}
|
||||
}
|
||||
68
server/serve/renderer.ts
Normal file
68
server/serve/renderer.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
|
||||
import { readFileSync } from 'node:fs'
|
||||
import type { ViteDevServer } from 'vite'
|
||||
|
||||
import type { VueroServerRender } from '../types.js'
|
||||
import { isProduction } from 'std-env'
|
||||
import { resolve } from '../utils.js'
|
||||
|
||||
export async function createRenderer() {
|
||||
let vite: ViteDevServer | undefined
|
||||
let render: VueroServerRender
|
||||
|
||||
if (!isProduction) {
|
||||
const createServer = await import('vite').then(m => m.createServer)
|
||||
|
||||
vite = await createServer({
|
||||
root: process.cwd(),
|
||||
logLevel: 'info',
|
||||
appType: 'custom',
|
||||
server: {
|
||||
middlewareMode: true,
|
||||
},
|
||||
define: {
|
||||
__VUERO_SSR_BUILD__: true,
|
||||
},
|
||||
})
|
||||
|
||||
// mock renderer, it will be reloaded on each request in dev
|
||||
render = async () => ''
|
||||
}
|
||||
else {
|
||||
/**
|
||||
* Otherwise, we load compiled version,
|
||||
* and we register compression and serve-static express handlers in h3
|
||||
*
|
||||
* @see https://github.com/expressjs/compression
|
||||
* @see https://github.com/expressjs/serve-static
|
||||
*/
|
||||
|
||||
// @ts-ignore - file present only when built
|
||||
render = await import('../../dist/server/entry-server.mjs').then(m => m.render)
|
||||
}
|
||||
|
||||
return {
|
||||
vite,
|
||||
render,
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadAssets() {
|
||||
const manifest: Record<string, any> = isProduction
|
||||
? await import(
|
||||
// @ts-ignore - file present only when built
|
||||
'../../dist/client/.vite/ssr-manifest.json',
|
||||
{ assert: { type: 'json' } }
|
||||
)
|
||||
: {}
|
||||
|
||||
const template = isProduction
|
||||
? readFileSync(resolve('../dist/client/index.html'), 'utf-8')
|
||||
: ''
|
||||
|
||||
return {
|
||||
manifest,
|
||||
template,
|
||||
}
|
||||
}
|
||||
21
server/types.ts
Normal file
21
server/types.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { H3Event } from 'h3'
|
||||
|
||||
export interface VueroSSRContext extends Record<string, any> {
|
||||
event: H3Event
|
||||
}
|
||||
|
||||
export interface VueroInitialState extends Record<string, any> {
|
||||
pinia?: Record<string, any>
|
||||
}
|
||||
|
||||
export type VueroServerRender = (ctx: {
|
||||
event: H3Event
|
||||
manifest: Record<string, any>
|
||||
template: string
|
||||
}) => Promise<string | ReadableStream>
|
||||
|
||||
type PageParam = Record<string, string | string[]>
|
||||
export type StaticParams = Record<
|
||||
string,
|
||||
() => PageParam[] | Promise<PageParam[]>
|
||||
>
|
||||
5
server/utils.ts
Normal file
5
server/utils.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
export const resolve = (p: string) =>
|
||||
path.resolve(path.dirname(fileURLToPath(import.meta.url)), p)
|
||||
Reference in New Issue
Block a user