SSR 中介層檔案有一個特殊用途:它們為執行 SSR 應用程式的 Nodejs 伺服器準備額外功能(相容於 Expressjs 的中介層)。
透過 SSR 中介層檔案,可以將中介層邏輯拆分為獨立、易於維護的檔案。停用任何 SSR 中介層檔案,甚至透過 /quasar.config
檔案配置來判斷哪些 SSR 中介層檔案會被納入建置,也十分容易。
提示
若要進行更進階的使用,您需要熟悉 Expressjs API。
警告
您至少需要一個 SSR 中介層檔案來處理使用 Vue 渲染頁面(應將其置於中介層列表的最後)。當 SSR 模式新增至您的 Quasar CLI 專案時,這將被添加到 src-ssr/middlewares/render.js
中。
中介層檔案的結構
SSR 中介層檔案是一個簡單的 JavaScript 檔案,它匯出一個函式。當 Quasar 準備 Nodejs 伺服器 (Expressjs) 應用程式時,將會呼叫匯出的函式,並額外傳遞一個 Object 作為參數(這將在下一節中詳細說明)。
// 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 中介層檔案的預設匯出函式接收作為參數的 Object。
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 應用程式實例。由於您將使用它來配置網頁伺服器,因此它是任何中介層的「命脈」。
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」 資料夾的路徑opts
與express.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 })
content_paste
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
檔案中新增該檔案。
ssr: {
middlewares: [
// references /src-ssr/middlewares/<name>.js
'<name>'
]
}
在建置 SSR 應用程式時,您可能希望某些啟動檔案僅在生產環境或僅在開發環境中執行,在這種情況下,您可以像下面這樣做
ssr: {
middlewares: [
ctx.prod ? '<name>' : '', // I run only on production!
ctx.dev ? '<name>' : '' // I run only on development
]
}
如果您想從 node_modules 指定 SSR 中介層檔案,可以透過在路徑前面加上 ~
(波浪符號) 字元來做到。
ssr: {
middlewares: [
// boot file from an npm package
'~my-npm-package/some/file'
]
}
警告
您指定 SSR 中介層的順序很重要,因為它決定了中介層應用於 Nodejs 伺服器的方式。因此,它們會影響伺服器如何回應客戶端。
SSR 渲染中介層
重要!
在您應用程式中所有可能的 SSR 中介層中,這個是絕對必要的,因為它處理使用 Vue 的實際 SSR 渲染。
在下面的範例中,我們強調此中介層需要是列表中的最後一個。這是因為它也使用頁面的 HTML 回應客戶端(我們將在下面的第二個程式碼範例中看到)。因此,任何後續的中介層都無法設定標頭。
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
]
}
現在讓我們看看它包含什麼
// 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()
})
}