SSR middleware(中间件)文件有一个专门的用途:为运行 SSR 应用的 Node.js 服务器添加额外的功能(Express 兼容的中间件)。
通过 SSR middleware 文件,可以将中间件逻辑拆分到独立且易于维护的文件中。通过 /quasar.config 文件的配置,可以轻松地禁用任何 SSR middleware 文件,甚至根据上下文环境来决定哪些中间件会被包含在构建中。
TIP
关于更高级的用法,您需要熟悉 Express API。
WARNING
至少需要一个处理 Vue 页面渲染的 SSR 中间件(这个中间件应该始终位于中间件列表的最后一个位置上)。当使用 Quasar CLI 添加 SSR 模式时,这个中间件会自动创建到 src-ssr/middlewares/render.js 中。
middleware 文件解析
一个 SSR middleware 文件是一个简单的 JavaScript 文件,它导出一个函数。Quasar 会在准备 Node.js 服务器(Express)应用时调用这个导出的函数,并传递一个对象作为参数(将在下一节详细介绍)。
import { defineSsrMiddleware } from "#q-app/wrappers";
export default defineSsrMiddleware(({ app, port, resolve, publicPath, folders, render, serve }) => {
// 在这里对服务器 "app" 进行操作
});SSR middleware 文件中导出的函数也可以是异步的:
// 在这里导入一些包
export default defineSsrMiddleware(
async ({ app, port, resolve, publicPath, folders, render, serve }) => {
// 在这里对服务器 "app" 进行操作
await something();
},
);注意 defineSsrMiddleware 的导入。它本质上是一个空操作函数,但有助于 IDE 的自动补全。
请注意我们在示例中使用了 ES6 的解构赋值语法,请只解构您实际需要使用的属性。
Middleware 对象参数解析
这里我们说的是 SSR middleware 文件默认导出函数所接收的对象参数。
export default defineSsrMiddleware(({ app, port, resolve, publicPath, folders, render, serve }) => {对象详解:
{
app, // Express app 或 src-ssr/server -> create() 返回的任何对象
port, // 开发环境:devServer 端口;生产环境:process.env.PORT 或 quasar.config > ssr > prodPort
resolve: {
urlPath, // (url) => 确保包含 publicPath 的路径字符串,
root, // (pathPart1, ...pathPartN) => 拼接到根目录的路径字符串,
public // (pathPart1, ...pathPartN) => 拼接到 public 目录的路径字符串
},
publicPath, // string
folders: {
root, // 根目录的路径字符串
public // public 目录的路径字符串
},
render, // (ssrContext) => html 字符串
serve: {
static, // ({ urlPath = '/', pathToServe = '.', opts = {} }) => void(或 src-ssr/server -> serveStaticContent() 的返回值)
error // 仅开发环境;({ err, req, res }) => void
}
}app
它是 Node.js 的 app 实例,是所有中间件的核心,通过它来配置 web 服务器。
port
为 Node.js web 服务器配置的端口。
resolve
| 属性名 | 描述 |
|---|---|
urlPath(path) | 当您定义路由时(通过 app.use()、app.get()、app.post() 等),应该使用 resolve.urlPath() 方法来设置路由路径,这样才能将配置的 publicPath(quasar.config 文件 > build > publicPath)考虑在内。 |
root(path1[, path2, ...pathN]) | 将路径解析到根目录(开发环境下是项目目录,生产环境下是构建产物目录)。底层使用了 path.join() 方法。 |
public(path1[, path2, ...pathN]) | 将路径解析到 “public” 目录。底层使用了 path.join() 方法。 |
publicPath
在 quasar.config 文件 > build > publicPath 中配置的 publicPath。
folders
由于在开发环境和生产环境下,根目录和 public 目录的实际路径不同,使用 folders 可以帮您消除这些差异。
| 属性名 | 描述 |
|---|---|
root | 根目录的完整路径(开发环境下是项目目录,生产环境下是构建产物目录)。 |
public | “public” 目录的完整路径。 |
render
- 语法:
<Promise(String)> render(ssrContext)。 - 描述:使用 Vue 和 Vue Router 来渲染请求的 URL 路径,返回渲染好的 HTML 字符串给客户端。
serve
serve.static():
语法:
<middlewareFn> serve.static(pathFromPublicFolder, opts)描述:本质上是
express.static()的封装,做了一些便利的调整:pathFromPublicFolder是已经解析到 “public” 目录的路径,可以直接使用opts与express.static()的配置对象相同opts.maxAge默认使用 quasar.config 文件 > ssr > maxAge 的配置值,它定义了文件在浏览器缓存中的存活时间
serve.static({ urlPath: '/my-file.json', pathToServe: '.', opts = {} }) // 等同于: express.static(resolve.public('my-file.json'), {})content_paste
serve.error():
- 语法:
<void> serve.error({ err, req, res }) - 描述:显示丰富的调试信息(包括调用栈)。
- 仅在开发环境下可用,在生产环境下不可用。
SSR middleware 的用法
第一步是使用 Quasar CLI 生成一个新的 SSR middleware 文件:
$ quasar new ssrmiddleware <name>其中 <name> 需要替换为合适的 SSR middleware 文件名称。
这个命令会创建一个新文件:/src-ssr/middlewares/<name>.js,内容如下:
// 在这里导入一些包
// "async" 是可选的!
// 如果不需要的话可以移除它
export default async ({ app, port, resolveUrlPath, publicPath, folders, render, serve }) => {
// 在这里对服务器 "app" 进行操作
};也可以返回一个 Promise:
// 在这里导入一些包
export default defineSsrMiddleware(({ app, port, resolve, publicPath, folders, render, serve }) => {
return new Promise((resolve, reject) => {
// 在这里对服务器 "app" 进行操作
});
});现在可以根据 SSR 中间件文件的预期用途向该文件添加内容。
最后一步是告诉 Quasar 使用您新创建的 SSR middleware 文件。您需要将其添加到 /quasar.config 文件中:
ssr: {
middlewares: [
// 引用 /src-ssr/middlewares/<name>.js
"<name>",
];
}当构建 SSR 应用时,您可能希望某些中间件只在生产环境或开发环境中运行,可以这样做:
ssr: {
middlewares: [
ctx.prod ? "<name>" : "", // 只在生产环境下运行!
ctx.dev ? "<name>" : "", // 只在开发环境下运行
];
}如果您想使用来自 node_modules 的 SSR 中间件文件,可以在路径前加上 ~(波浪号):
ssr: {
middlewares: [
// 来自 npm 包的中间件文件
"~my-npm-package/some/file",
];
}WARNING
指定 SSR 中间件的顺序很重要,因为它决定了中间件应用到 Node.js 服务器的方式,从而影响服务器对客户端的响应。
SSR 渲染中间件
重要!
在所有可能的 SSR 中间件中,这个是绝对必需的,因为它负责使用 Vue 进行实际的 SSR 渲染。
在下面的示例中,我们强调这个中间件需要放在列表的最后。因为它会将页面的 HTML 响应给客户端(如下面第二个代码示例所示),所以之后的中间件无法再设置响应头。
ssr: {
middlewares: [
// ..... 所有其他的中间件
"render", // 引用 /src-ssr/middlewares/render.js;
// 您可以随意命名该文件,
// 只需确保它作为最后一个中间件运行
];
}现在我们来看看它的内容:
// 这个中间件需要在最后执行
// 因为它会捕获所有请求并尝试
// 使用 Vue 渲染页面
export default ({ app, resolve, render, serve }) => {
// 我们捕获所有 Express 路由
// 交给 Vue 和 Vue Router 来渲染页面
app.get(resolve.urlPath("*"), (req, res) => {
res.setHeader("Content-Type", "text/html");
render({ req, res })
.then((html) => {
// 将渲染好的 HTML 发送给客户端
res.send(html);
})
.catch((err) => {
// 渲染页面时发生了错误
// 需要重定向到另一个 URL
if (err.url) {
if (err.code) {
res.redirect(err.code, err.url);
} else {
res.redirect(err.url);
}
}
// Vue Router 找不到请求的路由
else if (err.code === 404) {
// 只有当 /src/routes 中没有定义 "catch-all" 路由时才会到达这里
res.status(404).send("404 | Page Not Found");
}
// 其他错误;
// 在开发模式下,可以使用 Quasar CLI
// 显示包含调用栈等有用信息的错误页面
else if (process.env.DEV) {
// serve.error 仅在开发环境下可用
serve.error({ err, req, res });
}
// 在生产环境下,需要用另一种方式
// 向客户端展示错误信息
// (出于安全考虑,不能展示与开发模式同样详细的信息)
else {
// 渲染错误页面,或者
// 创建一个路由(/src/routes)用于错误页面并重定向到它
res.status(500).send("500 | Internal Server Error");
if (process.env.DEBUGGING) {
console.error(err.stack);
}
}
});
});
};注意导出函数接收的 render 参数,那就是 SSR 渲染实际发生的地方。
热模块重载
在开发过程中,只要您修改了 SSR 中间件文件中的任何内容,Quasar App CLI 就会自动触发客户端资源的重新编译,并将中间件的更改应用到 Node.js 服务器(Express)。
SSR 中间件示例
TIP
您可以使用任何 connect API 兼容的中间件。
日志/拦截器
SSR 中间件的应用顺序很重要。因此,将以下中间件设置为第一个(在 quasar.config 文件 > ssr > middlewares 中)可能是明智的,这样它就能拦截所有客户端请求。
export default defineSsrMiddleware(({ app, resolve }) => {
app.all(resolve.urlPath("*"), (req, _, next) => {
console.log("someone requested:", req.url);
next();
});
});