React-AMD標準、動態載入資源的Web APP如何做伺服器端渲染
背景介紹
需要做伺服器端渲染的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檔案涉及到專案程式碼,不再展示。