为什么捐赠
API 资源管理器
升级指南
新增!
quasar.config 文件
转换为使用 Webpack 的 CLI
浏览器兼容性
支持 TypeScript
目录结构
命令列表
CSS 预处理器
路由
延迟加载 - 代码分割
处理资源
启动文件
预取功能
API 代理
处理 Webpack
处理 process.env
使用 Pinia 进行状态管理
使用 Vuex 进行状态管理
代码检查器
测试与审计
开发移动应用
Ajax 请求
对公众开放开发服务器
Quasar CLI 与 Webpack - @quasar/app-webpack
服务器端渲染中间件

SSR 中间件文件只有一个特殊用途:它们为运行 SSR 应用的 Nodejs 服务器准备额外的功能(与 Expressjs 兼容的中间件)。

使用 SSR 中间件文件,可以将中间件逻辑分割成独立的、易于维护的文件。还可以轻松禁用任何 SSR 中间件文件,甚至可以通过 /quasar.config 文件配置来确定哪些 SSR 中间件文件进入构建。

提示

对于更高级的使用,您需要熟悉 Expressjs API

警告

您至少需要一个处理 Vue 页面渲染的 SSR 中间件文件(应将其放在中间件列表的最后)。当向 Quasar CLI 项目添加 SSR 模式时,它将被脚手架到 src-ssr/middlewares/render.js 中。

中间件文件结构

SSR 中间件文件是一个简单的 JavaScript 文件,它导出一个函数。然后,Quasar 会在准备 Nodejs 服务器(Expressjs)应用时调用导出的函数,并额外传递一个对象作为参数(将在下一节中详细介绍)。

// import something here

export default ({ app, resolve, publicPath, folders, render, serve }) => {
  // something to do with the server "app"
}

SSR 中间件文件也可以是异步的

// import something here

export default async ({ app, resolve, publicPath, folders, render, serve }) => {
  // something to do with the server "app"
  await something()
}

您可以使用 ssrMiddleware 助手包装返回的函数,以获得更好的 IDE 代码自动补全体验(通过 Typescript)

import { ssrMiddleware } from 'quasar/wrappers'

export default ssrMiddleware(async ({ app, resolve, publicPath, folders, render, serve }) => {
  // something to do
  await something()
})

请注意,我们正在使用 ES6 解构赋值。只分配您实际需要/使用的内容。

中间件对象参数

这里指的是 SSR 中间件文件默认导出函数接收到的对象参数。

export default ({ app, resolve, publicPath, folders, render, serve }) => {

对象细节

{
  app, // Expressjs app instance
  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 })
  }
}

app

这是 Expressjs 应用实例。“核心”的任何中间件,因为您将使用它来配置 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

folders

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('my-file.json')
    
    // is equivalent to:
    
    express.static(resolve.public('my-file.json'), {
      maxAge: ... // quasar.config file > ssr > maxAge
    })

serve.error()

  • 语法:<void> serve.error({ err, req, res })
  • 描述:显示大量有用的调试信息(包括堆栈跟踪)。
  • 它仅在开发环境中可用,**在生产环境中不可用**。

SSR 中间件的使用

第一步始终是使用 Quasar CLI 生成新的 SSR 中间件文件

$ quasar new ssrmiddleware <name>

其中 <name> 应替换为您 SSR 中间件文件的合适名称。

此命令创建一个新文件:/src-ssr/middlewares/<name>.js,内容如下

// import something here

// "async" is optional!
// remove it if you don't need it
export default async ({ app, resolveUrlPath, publicPath, folders, render }) => {
  // something to do with the server "app"
}

您还可以返回一个 Promise

// import something here

export default ({ app, resolve, publicPath, folders, render, serve }) => {
  return new Promise((resolve, reject) => {
    // something to do with the server "app"
  })
}

现在,您可以根据 SSR 中间件文件的预期用途向该文件添加内容。

最后一步是告诉 Quasar 使用新的 SSR 中间件文件。为此,您需要在 /quasar.config 文件中添加该文件

/quasar.config 文件

ssr: {
  middlewares: [
    // references /src-ssr/middlewares/<name>.js
    '<name>'
  ]
}

在构建 SSR 应用时,您可能希望某些启动文件仅在生产环境中运行或仅在开发环境中运行,在这种情况下,您可以像下面这样操作

/quasar.config 文件

ssr: {
  middlewares: [
    ctx.prod ? '<name>' : '', // I run only on production!
    ctx.dev ? '<name>' : '' // I run only on development
  ]
}

如果您想指定来自 node_modules 的 SSR 中间件文件,可以通过在路径前添加 ~(波浪号)字符来实现

/quasar.config 文件

ssr: {
  middlewares: [
    // boot file from an npm package
    '~my-npm-package/some/file'
  ]
}

警告

指定 SSR 中间件的顺序很重要,因为它决定了中间件应用于 Nodejs 服务器的方式。因此,它们会影响服务器如何响应客户端。

SSR 渲染中间件

重要!

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

在下面的示例中,我们强调了这个中间件需要位于列表的最后。这是因为它也使用页面的 HTML 响应客户端(我们将在下面的第二个代码示例中看到)。因此,任何后续的中间件都不能设置头信息。

/quasar.config 文件

ssr: {
  middlewares: [
    // ..... all other middlewares

    'render' // references /src-ssr/middlewares/render.js;
             // you can name the file however you want,
             // just make sure that it runs as last middleware
  ]
}

现在让我们看看它包含什么

/src-ssr/middlewares/render.js

// This middleware should execute as last one
// since it captures everything and tries to
// render the page with Vue

export default ({ app, resolve, render, serve }) => {
  // we capture any other Express route and hand it
  // over to Vue and Vue Router to render our page
  app.get(resolve.urlPath('*'), (req, res) => {
    res.setHeader('Content-Type', 'text/html')

    render({ req, res })
      .then(html => {
        // now let's send the rendered html to the client
        res.send(html)
      })
      .catch(err => {
        // oops, we had an error while rendering the page

        // we were told to redirect to another URL
        if (err.url) {
          if (err.code) {
            res.redirect(err.code, err.url)
          }
          else {
            res.redirect(err.url)
          }
        }
        // hmm, Vue Router could not find the requested route
        else if (err.code === 404) {
          // Should reach here only if no "catch-all" route
          // is defined in /src/routes
          res.status(404).send('404 | Page Not Found')
        }
        // well, we treat any other code as error;
        // if we're in dev mode, then we can use Quasar CLI
        // to display a nice error page that contains the stack
        // and other useful information
        else if (process.env.DEV) {
          // serve.error is available on dev only
          serve.error({ err, req, res })
        }
        // we're in production, so we should have another method
        // to display something to the client when we encounter an error
        // (for security reasons, it's not ok to display the same wealth
        // of information as we do in development)
        else {
          // Render Error Page on production or
          // create a route (/src/routes) for an error page and redirect to it
          res.status(500).send('500 | Internal Server Error')
        }
      })
  })
}

请注意中间件导出的函数被调用的 render 参数(来自上面的代码示例)。SSR 渲染发生在该处。

热模块替换

在开发过程中,每当你更改 SSR 中间件中的任何内容时,Quasar App CLI 都会自动触发客户端资源的重新编译,并将中间件更改应用到 Nodejs 服务器(Expressjs)。

SSR 中间件示例

提示

您可以使用任何与 Expressjs 兼容的中间件。

压缩

仅在生产环境中使用这个中间件才有意义。

import compression from 'compression'

export default ({ app }) => {
  app.use(
    compression({ threshold: 0 })
  )
}

日志记录/拦截器

应用 SSR 中间件的顺序很重要。因此,明智的做法是将以下中间件设置为第一个(在 quasar.config 文件 > ssr > middlewares 中),以便它能够拦截所有客户端请求。

export default ({ app, resolve }) => {
  app.all(resolve.urlPath('*'), (req, _, next) => {
    console.log('someone requested:', req.url)
    next()
  })
}