SSR中间件文件实现了一个特殊的目的:它们为运行SSR应用程序的Nodejs服务器准备了额外的功能(兼容Expressjs的中间件)。
通过SSR中间件文件,可以将中间件逻辑分割成独立的、易于维护的文件。禁用任何SSR中间件文件,甚至通过quasar.config.js
配置来决定哪些SSR中间件文件进入构建,也是非常简单的。
TIP
对于更高级的用法,你将需要熟悉Expressjs API。
WARNING
你将需要至少一个SSR中间件文件,它负责用Vue处理页面的渲染(它应该被定位在中间件列表的最后)。当SSR模式被添加到你的Quasar CLI项目中时,它将被架设在src-ssr/middlewares/render.js
中。
中间件文件的剖析
SSR中间件文件是一个简单的JavaScript文件,它导出一个函数。Quasar在准备Nodejs服务器(Expressjs)应用程序时,将调用导出的函数,并另外传递一个Object作为参数(将在下一节中详细说明)。
// 在这里导入一些东西
export default ({ app, resolve, publicPath, folders, render, serve }) => {
// 与服务器"app"有关的东西
}
启动文件也可以是异步的:
//在这里导入一些东西
export default async ({ app, resolve, publicPath, folders, render, serve }) => {
// 与服务器"app"有关的东西
await something()
}
你可以用ssrMiddleware
帮助器包裹返回的函数,以获得更好的IDE自动完成体验(通过Typescript):
import { ssrMiddleware } from 'quasar/wrappers'
export default ssrMiddleware(async ({ app, resolve, publicPath, folders, render, serve }) => {
// 要做的事情
await something()
})
注意我们使用的是ES6结构化赋值。只赋值你实际需要/使用的东西。
中间件对象参数
我们在这里指的是由SSR中间件文件的默认导出函数作为参数接收的对象。
export default ({ app, resolve, publicPath, folders, render, serve }) => {
Detailing the Object:
{
app, // Expressjs应用实例
resolve: {
urlPath(path)
root(arg1, arg2),
public(arg1, arg2)
},
publicPath, // 字符串
folders: {
root, // 字符串
public // 字符串
},
render(ssrContext),
serve: {
static(path, opts),
error({ err, req, res })
}
}
app
这是Expressjs的应用实例。它是任何中间件的“面包和黄油”,因为你将用它来配置网络服务器。
resolve
属性名 | 描述 |
---|---|
urlPath(path) | 每当你定义一个路由(用app.use(), app.get(), app.post()等)时,你应该使用resolve.urlPath() 方法,这样你也会考虑到配置的publicPath(quasar.conf.js > build > publicPath)。 |
root(path1[, path2, ...pathN]) | 解析文件夹路径到根(开发中的项目根目录和生产中的可分发文件根目录)。暗中它做了一个path.join() 。 |
public(path1[, path2, ...pathN]) | 解析文件夹路径到"public"文件夹。暗中它做了一个path.join() 。 |
publicPath
配置的quasar.conf.js > build > publicPath
folders
有时需要使用folders
,因为在生产构建中,根文件夹和公共文件夹的确切路径与开发构建中不同。所以通过使用folders
,你就不需要在意这些了。
属性名 | 描述 |
---|---|
root | 根(开发中的项目根目录和生产中的可分发文件根目录)的完整路径。 |
public | "public "文件夹的完整路径。 |
render
- 语法:
<Promise(String)> render.vue(ssrContext)
. - 描述。使用Vue和Vue Router来渲染请求的URL路径。返回渲染后的HTML字符串以返回给客户端。
serve
serve.static():
语法:
<middlewareFn> serve.static(pathFromPublicFolder, opts)
描述。它本质上是对
express.static()
的一个封装,并做了一些方便的调整。pathFromPublicFolder
是一个解析到"public"文件夹的路径。opts
与express.static()
的相同。opts.maxAge
是默认使用的,同时考虑quasar.conf.js > ssr > maxAge配置;这设置了相关文件在浏览器缓存中的生存时间
serve.static('my-file.json') // 等价于: express.static(resolve.public('my-file.json'), { maxAge: ... // quasar.conf.js > ssr > maxAge })
serve.error():
- 语法:
<void> render.error({ err, req, res })
- 说明。显示大量有用的调试信息(包括堆栈跟踪)。
- 它只在开发中可用,不能用于生产中。
SSR中间件的使用
第一步是使用Quasar CLI生成一个新的SSR中间件文件:
$ quasar new ssrmiddleware <name>
其中<name>
应该由你的SSR中间件文件的一个合适的名字来代替。
该命令创建了一个新文件。/src-ssr/middlewares/<name>.js
,内容如下:
//在这里导入一些东西
// "async "是可选的!
// 如果你不需要它,就把它去掉
export default async ({ app, resolveUrlPath, publicPath, folders, render }) => {
//做一些与服务器"app"有关的事
}
你可以返回一个Promise:
//在这里导入一些东西
export default ({ app, resolve, publicPath, folders, render, serve }) => {
return new Promise((resolve, reject) => {
//做一些与服务器"app"有关的事
})
}
现在你可以根据你的SSR中间件文件的预期用途,向该文件添加内容。
最后一步是告诉Quasar使用你的新SSR中间件文件。要做到这一点,你需要在/quasar.conf.js
中添加该文件
// quasar.conf.js
ssr: {
middlewares: [
// 引用/src-ssr/middlewares/<name>.js
'<name>'
]
}
当构建一个SSR应用程序时,你可能希望一些启动文件只在生产或只在开发上运行,在这种情况下,你可以像下面这样做:
// quasar.conf.js
ssr: {
middlewares: [
ctx.prod ? '<name>' : '', // 我只在生产系统上运行!
ctx.dev ? '<name>' : '' // 我只在开发中运行
]
}
如果你想从node_modules中指定SSR中间件文件,你可以通过在路径前加上~
(tilde)字符来实现。
// quasar.conf.js
ssr: {
middlewares: [
//来自npm包的启动文件
'~my-npm-package/some/file'
]
}
WARNING
你指定SSR中间件的顺序很重要,因为它决定了中间件被应用到Nodejs服务器的方式。所以它们会影响它对客户端的响应方式。
SSR渲染中间件
Important!
在你的应用程序中所有可能的SSR中间件中,这个是绝对需要的,因为它用Vue处理实际的SSR渲染。
在下面的例子中,我们强调这个中间件需要放在列表的最后。这是因为它也会向客户端响应(正如我们在下面的第二个代码示例中看到的那样),提供页面的HTML。所以任何后续的中间件都不能设置头文件。
// quasar.conf.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)
}
}
// hmm, Vue Router找不到请求的路由
else if (err.code === 404) {
// 只有在/src/routes中没有定义"全能"路由
// 的情况下才会到达这里。
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')
}
})
})
}
注意中间件的导出函数调用的render
参数(来自上面的代码样本)。这就是发生SSR渲染的地方。
热模块重新加载
在开发过程中,每当你改变SSR中间件中的任何内容,Quasar App CLI将自动触发客户端资源的重新编译,并将中间件的变化应用到Nodejs服务器(Expressjs)
SSR中间件的例子
TIP
你可以使用任何兼容Expressjs的中间件。
压缩
这个只在生产中使用才有意义。
import compression from 'compression'
export default ({ app }) => {
app.use(
compression({ threshold: 0 })
)
}
记录器/拦截器
应用SSR中间件的顺序很重要。因此,明智的做法是将下面这个设置为第一个(在quasar.conf.js > ssr > middlewares中),这样它就能拦截所有的客户端请求。
export default ({ app, resolve }) => {
app.all(resolve.urlPath('*'), (req, _, next) => {
console.log('someone requested:', req.url)
next()
})
}