Quasar CLI with Webpack - @quasar/app-webpack

SSR Middleware

SSR middleware(中间件)文件用于:它们为运行 SSR 应用程序的 Nodejs 服务器添加额外的附加功能(Express.js 兼容的中间件)

利用 SSR 的 middleware 文件可以将中间件的逻辑拆分到独立的文件中,易于维护。可以通过quasar.config.js 来配置禁用某些中间件,或者根据上下文环境来判断启用哪些中间件。

提示

关于更高级的用法,您需要熟悉Expressjs API

警告

至少需要一个处理 Vue 页面渲染的 SSR 中间件(这个中间件应该始终位于中间件数组的最后一个位置上)。当使用 Quasar CLI 添加 SSR 模式时,这个中间件会自动被创建到 src-ssr/middlewares/render.js中。

middleware 文件解析

一个 SSR middleware 文件是一个导出了一个函数的 javascipt 文件。Quasar 会在准备 Nodejs 服务器 (Expressjs)的时候调用这个被导出的函数,并额外传递一个对象作为参数(会在下一节详细介绍)

// 在这里可以导入一些包

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

SSR middleware 文件中导出的函数也可以是异步的:

// 在这里可以导入一些包

export default async ({ app, port, 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, port, resolve, publicPath, folders, render, serve }) => {
  // something to do
  await something()
})

请注意我们在示例中使用了ES6 的参数解构语法,请自行斟酌是否需要解构这个对象参数。

Middleware 对象参数解析

上面多次提到了 SSR middleware 的默认导出函数中,有一个对象参数,我们来解析一下它:

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

Detailing the Object:

{
  app, // Node.js app 实例
  port, // Nodej.js 服务器的端口号配置
  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

他是 Node.js 的 app 实例,是所有中间件的基础设施,通过它来配置 Node.js 的 web 服务

port

为 Node.js webserver 配置的端口。

resolve

属性名描述
urlPath(path)当您定义一个路由时(通过 app.use(), app.get(), app.post()等),您需要使用resolve.urlPath()方法来设置路由的路径。这样才能将路由添加到配置的 pulicPath 中 (quasar.config.js > build > publicPath)。
root(path1[, path2, ...pathN])解析 root 目录到指定目录的路径,低层使用了path.join()方法。
public(path1[, path2, ...pathN])解析 public 目录到指定目录的路径,低层使用了path.join()方法。

publicPath

在 quasar.config.js > build > publicPath 中配置的 publicPath

folders

由于在开发环境下和生产环境下的 root 目录和 public 目录不同,您需要使用 folders 来帮您消除这些差异。

属性名描述
rootroot 目录的绝对路径 (of the project in dev and of the distributables in production).
publicpublic 目录的绝对路径

render

  • 语法: <Promise(String)> render(ssrContext)
  • 描述: 根据客户端请求的 URL 路径,使用 Vue 和 Vue Router 来渲染页面,并返回一个 html 字符串给客户端。

serve

serve.static():

  • 语法: <middlewareFn> serve.static(pathFromPublicFolder, opts)

  • 描述: 本质上,它是express.static()的一个封装,添加了一些方便了的调整:

    • 其中pathFromPublicFolder是可以直接使用的,从 public 目录解析的路径。
    • opts对象与的 express.static()配置对象相同。
    • opts.maxAge:在默认情况下,它是从 quasar.config.js > ssr > maxAge 中读取的配置,它定义请求返回的文件可以在浏览器的缓存中存活多久。
    serve.static('my-file.json')
    
    // 等同于:
    
    express.static(resolve.public('my-file.json'), {
      maxAge: ... // quasar.config.js > ssr > maxAge
    })
    

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 ({ app, port, resolve, publicPath, folders, render, serve }) => {
  return new Promise((resolve, reject) => {
    // 在这里可以使用"app"做一些事情
  })
}

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

最后一步是告诉 Quasar 启用您创建的新的中间件文件,您需要将其文件名添加到/quasar.config.js配置文件的 ssr -> middlewares 数组中:

// quasar.config.js

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

当构建一个 SSR 应用时,您可能希望某些中间件文件只运行在开发模式或者生产环境中,示例:

// quasar.config.js

ssr: {
  middlewares: [
    ctx.prod ? '<name>' : '', // 只运行在生产模式下
    ctx.dev ? '<name>' : '' // 只运行在开发模式下
  ]
}

如果您想启用来自 node_modules 中的某个 SSR 中间件文件,可以在路径前加上 ~

// quasar.config.js

ssr: {
  middlewares: [
    // 启用某个 npm 包中的中间件文件
    '~my-npm-package/some/file'
  ]
}

警告

指定 SSR 中间件的顺序很重要,因为它决定了将中间件被 Nodejs 服务器调用的方式。因此,中间件的调用顺序会影响客户端得到的响应结果。

SSR 渲染中间件

重要!

在所有的 SSR 中间中,只有这个 SSR 渲染中间件是必须的,因为它是 SSR 中实际使用 Vue 的渲染页面的中间件。

下面的示例中,我们强调了这个中间必须位于中间件数组中的最后一个位置上。因为它会给客户端返回页面的 HTML(下面第二个示例),所以后续的中间件无法再修改请求的 headers(res.setHeader)。

// quasar.config.js

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)
          }
        }
        // 处理 404 请求,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')
          // console.error(err.stack)
        }
      })
  })
}

注意在导出的函数中调用了render参数,那就是 SSR 中渲染的实现。

Hot Module Reload

当开发时,只要您在 SSR 中间件文件中做出任何的修改,Quasar CLI 都会自动触发客户端资源的重新编译然后在 Node.js 的服务端 (Expressjs)应用中间件的修改。

SSR 中间件示例

提示

您可以使用任何连接 API 兼容的中间件。

You can use any connect API compatible middleware.

日志/拦截器

所有的 SSR 中间件都是按照指定的顺序执行的(在 quasar.config.js > ssr > middlewares 中指定顺序),所以,第一个中间件很适合充当一个拦截器,它可以拦截所有的客户端请求:

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