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