为什么捐赠
API 浏览器
联系站长
Quasar CLI with Vite - @quasar/app-vite
SSR Middleware

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” 目录的路径,可以直接使用
    • optsexpress.static() 的配置对象相同
    • opts.maxAge 默认使用 quasar.config 文件 > ssr > maxAge 的配置值,它定义了文件在浏览器缓存中的存活时间
    serve.static({ urlPath: '/my-file.json', pathToServe: '.', opts = {} })
    
    // 等同于:
    
    express.static(resolve.public('my-file.json'), {})

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 文件中:

/quasar.config file

ssr: {
  middlewares: [
    // 引用 /src-ssr/middlewares/<name>.js
    "<name>",
  ];
}

当构建 SSR 应用时,您可能希望某些中间件只在生产环境或开发环境中运行,可以这样做:

/quasar.config file

ssr: {
  middlewares: [
    ctx.prod ? "<name>" : "", // 只在生产环境下运行!
    ctx.dev ? "<name>" : "", // 只在开发环境下运行
  ];
}

如果您想使用来自 node_modules 的 SSR 中间件文件,可以在路径前加上 ~(波浪号):

/quasar.config file

ssr: {
  middlewares: [
    // 来自 npm 包的中间件文件
    "~my-npm-package/some/file",
  ];
}

WARNING

指定 SSR 中间件的顺序很重要,因为它决定了中间件应用到 Node.js 服务器的方式,从而影响服务器对客户端的响应。

SSR 渲染中间件

重要!

在所有可能的 SSR 中间件中,这个是绝对必需的,因为它负责使用 Vue 进行实际的 SSR 渲染。

在下面的示例中,我们强调这个中间件需要放在列表的最后。因为它会将页面的 HTML 响应给客户端(如下面第二个代码示例所示),所以之后的中间件无法再设置响应头。

/quasar.config file

ssr: {
  middlewares: [
    // ..... 所有其他的中间件

    "render", // 引用 /src-ssr/middlewares/render.js;
    // 您可以随意命名该文件,
    // 只需确保它作为最后一个中间件运行
  ];
}

现在我们来看看它的内容:

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();
  });
});