背景介紹
需要做服務器端渲染的app是:可以點擊添加組件,保存以後生成一個獨立的web app。就是一個生成web app的web app。因爲不一定每個組件都被添加到用戶創建的web app中,所以,組件的資源是動態加載的,只有選擇某個組件的時候,纔會去加載這個組件的資源。前端資源使用AMD標準進行加載。
困難點
首先說明,這裏不分析SSR中常見的問題,比如路由匹配、css loader處理、常見的一些webpack配置的不同、SSR的一些打包工具(例如:universal-webpack、webpack-isomorphic-tools)這裏不做分析。
以下是開發過程中遇到的幾個困難點,或者說比一般SSR有特點的地方。不分析解決過程,過程中有多少坑,開發同學們都懂,看起來很簡單的結論,其實經過了各種小問題、大問題,各種方案對比。今天這裏只總結解決方案。
資源加載
首先,這裏說的是資源,指的的js文件、css文件、圖片等,不是服務器端返回的、用於前端渲染的數據。
如背景中介紹的,前端的資源相當於是按需加載的,當資源沒有返回時,首屏會顯示類似Config is loading
這種提示,無法和服務器端返回的markup匹配,造成頁面閃爍一下。目前的解決辦法是,首屏的資源同步加載,也就是隻有首屏是服務器端渲染的,進而就沒有了一般服務器端渲染需要解決的路由匹配問題。
require的重寫
同構的好處就是一份代碼瀏覽器端和服務器端都可以使用。這個項目中,前端使用AMD標準的require方法進行資源加載,例如:
amdRequire('path/to/fille', function(src){
//do something with src
});
這個require方法,是前端通過javscript標籤引用一個庫所帶來的,直接用webpack打包爲commjs會報一系列錯誤,解決這些錯誤並且保證其穩定性是比較難的。服務器端使用commjs的require方法進行了重寫:
amdRequire = function(m, cb){
if(typeof m === 'string'){
let _m = require(m);
_m = _m.__esModule ? _m.default : _m;
cb(_m);
}else{
let _mArr = m.map((ele) => {
let _ele = require(ele).__esModule ? require(ele).default : require(ele);
return _ele;
});
cb(_mArr);
}
}
webpack編譯
在瀏覽器端,使用webpack將js文件編譯爲libraryTarget: 'amd'
的模塊,每個組件爲一個入口,打一個包。常用公共模塊,比如react,打到一個vendor中。框架資源,也就是頁面主體打一個包。頁面除了首屏外(首屏所需資源全部一次返回),服務器首先返回vendor和框架資源包。根據用戶的操作(添加組件)再去請求其他組件包。
在服務器端,編譯爲libraryTarget: 'commonjs2'
,所有資源打一個包,根據前端請求的id,找到app對應的config文件,直接去這個大資源包中找模塊,最終返回一個html字符串(renderToString
返回結果),前端接受到這個字符串以後進行水合(hydrate)。
具體config文件涉及到項目代碼,不再展示。