请注意,您生成的 /src-ssr
包含一个名为 server.js
的文件。此文件定义了您的 SSR Web 服务器是如何创建、管理和提供的。您可以开始侦听端口或为您的无服务器基础架构提供处理程序。这取决于您。
解剖
该 /src-ssr/server.[js|ts]
文件是一个简单的 JavaScript/Typescript 文件,它启动您的 SSR Web 服务器并定义 Web 服务器如何启动和处理请求,以及它导出了什么(如果有导出)。
警告
该 /src-ssr/server.[js|ts]
文件用于 DEV 和 PROD,因此请谨慎配置它。要区分这两种状态,可以使用 process∙env∙DEV
和 process∙env∙PROD
。
/**
* More info about this file:
* https://v2.quasar.dev/quasar-cli-vite/developing-ssr/ssr-webserver
*
* Runs in Node context.
*/
/**
* Make sure to yarn/npm/pnpm/bun install (in your project root)
* anything you import here (except for express and compression).
*/
import express from 'express'
import compression from 'compression'
/**
* Create your webserver and return its instance.
* If needed, prepare your webserver to receive
* connect-like middlewares.
*
* Should NOT be async!
*/
export function create (/* { ... } */) {
const app = express()
// attackers can use this header to detect apps running Express
// and then launch specifically-targeted attacks
app.disable('x-powered-by')
// place here any middlewares that
// absolutely need to run before anything else
if (process.env.PROD) {
app.use(compression())
}
return app
}
/**
* You need to make the server listen to the indicated port
* and return the listening instance or whatever you need to
* close the server with.
*
* The "listenResult" param for the "close()" definition below
* is what you return here.
*
* For production, you can instead export your
* handler for serverless use or whatever else fits your needs.
*/
export async function listen ({ app, port, isReady, ssrHandler }) {
await isReady()
return await app.listen(port, () => {
if (process.env.PROD) {
console.log('Server listening at port ' + port)
}
})
}
/**
* Should close the server and free up any resources.
* Will be used on development only when the server needs
* to be rebooted.
*
* Should you need the result of the "listen()" call above,
* you can use the "listenResult" param.
*
* Can be async.
*/
export function close ({ listenResult }) {
return listenResult.close()
}
const maxAge = process.env.DEV
? 0
: 1000 * 60 * 60 * 24 * 30
/**
* Should return middleware that serves the indicated path
* with static content.
*/
export function serveStaticContent (path, opts) {
return express.static(path, {
maxAge,
...opts
})
}
const jsRE = /\.js$/
const cssRE = /\.css$/
const woffRE = /\.woff$/
const woff2RE = /\.woff2$/
const gifRE = /\.gif$/
const jpgRE = /\.jpe?g$/
const pngRE = /\.png$/
/**
* Should return a String with HTML output
* (if any) for preloading indicated file
*/
export function renderPreloadTag (file) {
if (jsRE.test(file) === true) {
return `<link rel="modulepreload" href="${file}" crossorigin>`
}
if (cssRE.test(file) === true) {
return `<link rel="stylesheet" href="${file}">`
}
if (woffRE.test(file) === true) {
return `<link rel="preload" href="${file}" as="font" type="font/woff" crossorigin>`
}
if (woff2RE.test(file) === true) {
return `<link rel="preload" href="${file}" as="font" type="font/woff2" crossorigin>`
}
if (gifRE.test(file) === true) {
return `<link rel="preload" href="${file}" as="image" type="image/gif">`
}
if (jpgRE.test(file) === true) {
return `<link rel="preload" href="${file}" as="image" type="image/jpeg">`
}
if (pngRE.test(file) === true) {
return `<link rel="preload" href="${file}" as="image" type="image/png">`
}
return ''
}
提示
请记住,无论 listen()
函数返回什么(如果有),都会从您的构建 dist/ssr/index.js
中导出。如果您需要无服务器架构,可以返回您的 ssrHandler。
参数
export function <functionName> ({
app, port, isReady, ssrHandler,
resolve, publicPath, folders, render, serve
}) => {
详细说明对象
{
app, // Expressjs app instance (or whatever you return from create())
port, // on production: process∙env∙PORT or quasar.config file > ssr > prodPort
// on development: quasar.config file > devServer > port
isReady, // Function to call returning a Promise
// when app is ready to serve clients
ssrHandler, // Prebuilt app handler if your serverless service
// doesn't require a specific way to provide it.
// Form: ssrHandler (req, res, next)
// Tip: it uses isReady() under the hood already
// all of the following are the same as
// for the SSR middlewares (check its docs page);
// normally you don't need these here
// (use a real SSR middleware instead)
resolve: {
urlPath(path)
root(arg1, arg2),
public(arg1, arg2)
},
publicPath, // String
folders: {
root, // String
public // String
},
render(ssrContext),
serve: {
static(path, opts),
error({ err, req, res })
}
}
使用
警告
- 如果您从 node_modules 中导入任何内容,请确保该软件包在 package.json > “dependencies” 中指定,而不是在“devDependencies” 中指定。
- 通常这不是添加中间件的地方(但您可以这样做)。使用 SSR 中间件 添加中间件。您也可以配置 SSR 中间件,使其仅在开发环境或生产环境中运行。
替换 express.js
您可以用任何其他兼容 connect API 的服务器替换默认的 Express.js Node 服务器。只需确保首先 yarn/npm 安装其软件包。
import connect from 'connect'
export function create (/* { ... } */) {
const app = connect()
// place here any middlewares that
// absolutely need to run before anything else
if (process.env.PROD) {
app.use(compression())
}
return app
}
监听端口
这是在 Quasar CLI 项目中添加 SSR 支持时获得的默认选项。它开始监听配置的端口(process∙env∙PORT 或 quasar.config 文件 > ssr > prodPort)。
export async function listen ({ app, port, isReady }) {
await isReady()
return await app.listen(port, () => {
if (process.env.PROD) {
console.log('Server listening at port ' + port)
}
})
}
无服务器
如果您有无服务器基础设施,则通常需要导出一个处理程序,而不是开始监听端口。
假设您的无服务器服务要求您
module.exports.handler = __your_handler__
那么您需要做的是
export async function listen ({ app, port, ssrHandler }) {
if (process.env.DEV) {
await isReady()
return await app.listen(port, () => {
if (process.env.PROD) {
console.log('Server listening at port ' + port)
}
})
}
else { // in production
// "ssrHandler" is a prebuilt handler which already
// waits for all the middlewares to run before serving clients
// whatever you return here is equivalent to module.exports.<key> = <value>
return { handler: ssrHandler }
}
}
请注意,提供的 ssrHandler
是一个函数,形式为:(req, res, next) => void
。如果您需要导出形式为 (event, context, callback) => void
的处理程序,那么您很可能需要使用 serverless-http
软件包(见下文)。
示例:serverless-http
您需要手动 yarn/npm 安装 serverless-http
软件包。
import serverless from 'serverless-http'
export async function listen (({ app, port, ssrHandler }) => {
if (process.env.DEV) {
await isReady()
return await app.listen(port, () => {
// we're ready to serve clients
})
}
else { // in production
return { handler: serverless(ssrHandler) }
}
})
示例:Firebase 函数
import * as functions from 'firebase-functions'
export async function listen (({ app, port, ssrHandler }) => {
if (process.env.DEV) {
await isReady()
return await app.listen(port, () => {
// we're ready to serve clients
})
}
else { // in production
return {
handler: functions.https.onRequest(ssrHandler)
}
}
})