當(dāng)編寫純客戶端 (client-only) 代碼時,我們習(xí)慣于每次在新的上下文中對代碼進(jìn)行取值。但是,Node.js 服務(wù)器是一個長期運行的進(jìn)程。當(dāng)我們的代碼進(jìn)入該進(jìn)程時,它將進(jìn)行一次取值并留存在內(nèi)存中。這意味著如果創(chuàng)建一個單例對象,它將在每個傳入的請求之間共享。
如基本示例所示,我們為每個請求創(chuàng)建一個新的根 Vue 實例。這與每個用戶在自己的瀏覽器中使用新應(yīng)用程序的實例類似。如果我們在多個請求之間使用一個共享的實例,很容易導(dǎo)致交叉請求狀態(tài)污染 (cross-request state pollution)。
因此,我們不應(yīng)該直接創(chuàng)建一個應(yīng)用程序?qū)嵗?,而是?yīng)該暴露一個可以重復(fù)執(zhí)行的工廠函數(shù),為每個請求創(chuàng)建新的應(yīng)用程序?qū)嵗?/p>
// app.js
const Vue = require('vue')
module.exports = function createApp (context) {
return new Vue({
data: {
url: context.url
},
template: `<div>訪問的 URL 是: {{ url }}</div>`
})
}
并且我們的服務(wù)器代碼現(xiàn)在變?yōu)椋?/p>
// server.js
const createApp = require('./app')
server.get('*', (req, res) => {
const context = { url: req.url }
const app = createApp(context)
renderer.renderToString(app, (err, html) => {
// 處理錯誤……
res.end(html)
})
})
同樣的規(guī)則也適用于 router、store 和 event bus 實例。你不應(yīng)該直接從模塊導(dǎo)出并將其導(dǎo)入到應(yīng)用程序中,而是需要在 createApp
中創(chuàng)建一個新的實例,并從根 Vue 實例注入。
在使用帶有
{ runInNewContext: true }
的 bundle renderer 時,可以消除此約束,但是由于需要為每個請求創(chuàng)建一個新的 vm 上下文,因此伴隨有一些顯著性能開銷。
到目前為止,我們還沒有討論過如何將相同的 Vue 應(yīng)用程序提供給客戶端。為了做到這一點,我們需要使用 webpack 來打包我們的 Vue 應(yīng)用程序。事實上,我們可能需要在服務(wù)器上使用 webpack 打包 Vue 應(yīng)用程序,因為:
vue-loader
構(gòu)建,并且許多 webpack 特定功能不能直接在 Node.js 中運行(例如通過 file-loader
導(dǎo)入文件,通過 css-loader
導(dǎo)入 CSS)。所以基本看法是,對于客戶端應(yīng)用程序和服務(wù)器應(yīng)用程序,我們都要使用 webpack 打包 - 服務(wù)器需要「服務(wù)器 bundle」然后用于服務(wù)器端渲染(SSR),而「客戶端 bundle」會發(fā)送給瀏覽器,用于混合靜態(tài)標(biāo)記。
我們將在后面的章節(jié)討論規(guī)劃結(jié)構(gòu)的細(xì)節(jié) - 現(xiàn)在,先假設(shè)我們已經(jīng)將構(gòu)建過程的規(guī)劃都弄清楚了,我們可以在啟用 webpack 的情況下編寫我們的 Vue 應(yīng)用程序代碼。
現(xiàn)在我們正在使用 webpack 來處理服務(wù)器和客戶端的應(yīng)用程序,大部分源碼可以使用通用方式編寫,可以使用 webpack 支持的所有功能。同時,在編寫通用代碼時,有一些事項要牢記在心。
一個基本項目可能像是這樣:
src
├── components
│?? ├── Foo.vue
│?? ├── Bar.vue
│?? └── Baz.vue
├── App.vue
├── app.js # 通用 entry(universal entry)
├── entry-client.js # 僅運行于瀏覽器
└── entry-server.js # 僅運行于服務(wù)器
app.js
app.js
是我們應(yīng)用程序的「通用 entry」。在純客戶端應(yīng)用程序中,我們將在此文件中創(chuàng)建根 Vue 實例,并直接掛載到 DOM。但是,對于服務(wù)器端渲染(SSR),責(zé)任轉(zhuǎn)移到純客戶端 entry 文件。app.js
簡單地使用 export 導(dǎo)出一個 createApp
函數(shù):
import Vue from 'vue'
import App from './App.vue'
// 導(dǎo)出一個工廠函數(shù),用于創(chuàng)建新的
// 應(yīng)用程序、router 和 store 實例
export function createApp () {
const app = new Vue({
// 根實例簡單的渲染應(yīng)用程序組件。
render: h => h(App)
})
return { app }
}
entry-client.js
:客戶端 entry 只需創(chuàng)建應(yīng)用程序,并且將其掛載到 DOM 中:
import { createApp } from './app'
// 客戶端特定引導(dǎo)邏輯……
const { app } = createApp()
// 這里假定 App.vue 模板中根元素具有 `id="app"`
app.$mount('#app')
entry-server.js
:服務(wù)器 entry 使用 default export 導(dǎo)出函數(shù),并在每次渲染中重復(fù)調(diào)用此函數(shù)。此時,除了創(chuàng)建和返回應(yīng)用程序?qū)嵗?,它不會做太多事?- 但是稍后我們將在此執(zhí)行服務(wù)器端路由匹配 (server-side route matching) 和數(shù)據(jù)預(yù)取邏輯 (data pre-fetching logic)。
import { createApp } from './app'
export default context => {
const { app } = createApp()
return app
}
更多建議: