不久前涮毫,vue 升級至了 2.3.0 版本翘魄,是一個 minor 的版本。該版本除了一些組件功能的優(yōu)化之外颜凯,主要是升級 vue 的 ssr 功能谋币,甚至于為之建立了一個獨立的 Git Book。
我的博客之前用的就是 ssr症概,這次升級自然也是要嘗試一把蕾额。ssr 的優(yōu)勢和實現(xiàn)在這里就不再贅述了,不太了解的可以看之前的文章穴豫,這里主要還是來看看升級的變化之處。
升級的第一件事自然就是先升級依賴逼友,將 vue, vue-server-renderer 等依賴的版本升級至最新 npm up -S
(作者 vue 的版本為 v2.3.3)精肃。升級之后,直接啟動服務看看帜乞,應該是沒有問題的司抱,文檔也提到可以使用之前的配置,但建議改為新版本的方式黎烈。
雖然习柠,依賴升級之后同樣能運行,但還是來看看有哪些提升或變化的地方照棋?
<a name="renderer-create-options"></a>
Renderer Create Options
更新之后资溃,在創(chuàng)建 renderer 時可以為它添加配置,其中的 template
屬性可以為我們省去之前的許多繁雜的小工作烈炭,比如:
- 在 html 中使用 ``溶锭,renderer 會自動將 app 生成的 html 插入此處,而不用自己再進行替換操作
- 將
context.state
插入到 html 中符隙,并自動使用 serialize-javascript 進行轉義來防止 XSS 攻擊 - 直接通過
cache
屬性配置組件緩存
以上這些都是在之前版本中常被使用到的趴捅,剩下一些 clientManifest
, inject
, runInNewContext
等新增的東西后面會再提到垫毙。
<a name="lifesycle-data-prefetch"></a>
Lifesycle & data prefetch
由于在 ssr 階段不會有一系列的變更,所以更新之后 vue 在 ssr 階段只會執(zhí)行 beforeCreate
和 created
這個兩個生命周期函數(shù)拱绑。
相信你一定會問那如果遇到異步請求該怎么辦哪综芥?這里同之前并沒有變化,仍舊是通過設置組件的自定義方法來獲取數(shù)據(jù)猎拨,最終通過 vuex 將數(shù)據(jù)傳遞回客戶端膀藐。沒什么變化就不展開了,不清楚的可以看一下文檔迟几,寫得已經(jīng)相當詳細了消请。
不過此處有一點優(yōu)化,由于數(shù)據(jù)已經(jīng)在服務器端已準備完成类腮,客戶端就無需再像服務器端發(fā)送異步請求臊泰,而是可以直接從 store 中獲取數(shù)據(jù)。
<a name="code-structure"></a>
代碼結構與同構
文檔的這一節(jié)在內(nèi)容上和之前的文檔基本沒有區(qū)別蚜枢,不過其中提到一點指出了我原有代碼的不足之處缸逃,也給了我不少啟發(fā)。
通常大家的 app.js 會是這樣
// 省略其他依賴...
import store from './vuex';
import router from './router';
sync(store, router);
const app = new Vue({
store,
router,
render: h => h(/* ... */)
});
export {app, router, store};
這看上去并沒有任何問題厂抽。在平時的瀏覽器環(huán)境中需频,每次刷新頁面都會重新加載一次文件,是一個全新的環(huán)境(或沙盒)筷凤。但當同構了代碼之后昭殉,服務器端同樣運行這段代碼時,就可能出現(xiàn)問題藐守。
因為 node 端服務啟動后挪丢,vue 的實例就被初始化完成,所有的請求會公用這同一個實例卢厂,這就可能造成混亂乾蓬。所以為每個請求返回一個新的 vue 的實例是一個比較好的處理方法,router 和 store 同樣適用這個道理慎恒。
// 省略其他依賴...
import createStore from './vuex';
import createRouter from './router';
const createApp = () => {
const store = createStore();
const router = createRouter();
sync(store, router);
const app = new Vue({
store,
router,
render: h => h(/* ... */)
});
return {app, router, store};
};
export default createApp;
雖然任内,我至今還沒有遇到過實例沖突的問題,不過我還是覺得文檔說的很有道理融柬,可能會發(fā)生這樣的情況死嗦。多個實例會克服沖突的問題,但它同時也增加服務器的負擔粒氧。
這樣處理之后越走,就可能將之前提到的 runInNewContext
配置設為 false
,默認為 true
會為每個 bundle 創(chuàng)建新的上下文。
<a name="webpack-build-plugin"></a>
Webpack build plugin
升級的最大變化在于對 webpack 提供更強大的支持廊敌,在 vue-server-renderer
包中新增了兩個 webpack plugin: server-plugin
和 client-plugin
铜跑,分別用于服務器端和客戶端。
server-plugin
server-plugin
會默認創(chuàng)建一個名為 vue-ssr-server-bundle.json
的文件作為 createBundleRenderer
的第一個參數(shù)骡澈。
這里 webpack 的 output.filename
設置還是要定義的锅纺,不然打包的時候會報錯。
上面這點上一個版本就能做到肋殴,使用 server-plugin
的好處是在于囤锉,它提供了服務端的 source-map
功能,這可是開發(fā)利器护锤。另一大好處是官地,支持 hot-reload
,不過我之前使用的是 webpack-middleware 就已經(jīng)支持該特性了烙懦。
熟悉 webpack 的都知道驱入,webpack-middleware 是將文件放在內(nèi)存里的,而這里的 createBundleRenderer
用的是文件訪問氯析,所以亏较,直接傳路徑是有問題的。不過掩缓,它也支持傳一個對象雪情,所以記得每次服務端代碼更新之后要重新創(chuàng)建 renderer,還有讀文件之后要將 string 轉換為 object 傳給 createBundleRenderer
你辣。
// 省略...
const updateRenderer = () => {
try {
const options = {
clientManifest: JSON.parse(expressDevMiddleware.fileSystem.readFileSync(clientManifestFilePath, 'utf-8'))
};
createRenderer(JSON.parse(mfs.readFileSync(serverBundleFilePath, 'utf-8')), options);
} catch(e) {
createRenderer(JSON.parse(mfs.readFileSync(serverBundleFilePath, 'utf-8')));
}
console.log('Renderer is updated.');
};
// watch and update server renderer
const serverCompiler = webpack(serverConfig);
serverCompiler.outputFileSystem = mfs;
serverCompiler.watch({}, (err, stats) => {
if (err) throw err;
stats = stats.toJson();
stats.errors.forEach(err => console.error(err));
stats.warnings.forEach(err => console.warn(err));
updateRenderer();
});
client-plugin
如果你使用過升級之前 vue ssr 的功能巡通,那你肯定會對一系列有關 html 的操作有映象,比如替換 html舍哄,插入 state 等⊙缌梗現(xiàn)在,有了 client-plugin
它就能代替原有的 html-webpack-plugin
來生成 html蠢熄,并把之前那些繁雜的事都替你處理了跪解。
上面這些對已經(jīng)實現(xiàn) ssr 的你可能不是很有吸引力炉旷,不過签孔,下面這點可能會讓你感興趣。這個插件還自帶為你的 ccs 或 js 添加 preload
和 prefetch
功能窘行,它可以加快你網(wǎng)站的加載速度饥追,如果你還不清楚 prefetch
和 preload
是什么的話,可以先讀一下這篇文章罐盔。
如果你使用的是 webpack-server但绕,那么,你按文檔上的例子來應該沒什么問題。但如果你和我一樣使用的是 webpack-middleware捏顺,那么六孵,這里還是有些別扭的,需要和之前一樣每次 plugin 生成后去重新構建 renderer幅骄。
// 省略...
clientCompiler.plugin('done', updateRenderer);
同 server-plugin
一樣文件讀出來的是 string劫窒,你要將它轉換為對象。其他基本的配置按文檔上的來就行拆座,遇到問題的可以參考下我的代碼主巍。
吹了這么多,不足之處還是得指出來挪凑,client-plugin
還不能像 html-webpack-plugin
監(jiān)聽 html 文件孕索,每次修改 html 都得手動重啟服務有點麻煩,可以優(yōu)化一波...
升級所要注意的就差不多就這些了躏碳。還有一點搞旭,之前 vue 推薦使用 renderToStream
來返回頁面,如果組件生命周期中有請求的話唐断,使用 stream 可能導致組件還未構建完成就已經(jīng)發(fā)送选脊。所以,更新之后 vue 推薦使用 renderToString
脸甘。
<a name="concultion"></a>
結尾
vue 的確是非常緊跟潮流恳啥,就像這次加入的 preload
和 prefetch
功能,但因開發(fā)團隊人員太少(相對于 react 和 angular)丹诀,導致版本并不是很穩(wěn)定钝的。
如果,你問我 vue 好不好铆遭?我會說硝桩,好。
如果枚荣,你問我要不要學 vue碗脊?我會說,學橄妆。
如果衙伶,你問我 vue 能不能上生產(chǎn)?我的建議是害碾,不如咋們半年后再談...