從頭建立一套 web-based 服務架構 (7) 啟動 Frontend Server

前面大致介紹完初始環境建置,今天就來看看 frontend server 的設計,並實際安裝套件和啟動 server。

安裝套件

雖然 Node.js 在安裝時已自帶一套 package manager npm,我們這次會另外安裝別的 package manager 來協助專案的套件安裝過程。 內建的 npm 有什麼不足之處呢? 第一個問題在於速度,3.x 版後的 npm 在盡可能攤平 node_modules 巢狀 dependency 結構的計算上犧牲了安裝速度; 當然在後續的版本中已大幅改善,且開發者自己也能用其他方式加速 npm install 的速度。

第二個比較大的問題是缺少一般套件管理工具常見的 lock 機制。 所謂的 lock 機制是在安裝時期記錄實際安裝於環境內的套件版本,而非套件宣告檔指定的版本。 一般而言,套件宣告檔會以較寬鬆的版本指定形式撰寫,再交由安裝時期的管理工具進行 version resolving。 有了 lock 機制,就能確保在不同的環境、不同的時間下進行套件安裝時,都能安裝到一致的套件版本。

以 iOS 開發常用的套件管理工具 CocoaPods 為例,套件安裝的過程大致可整理為下列步驟:

  1. 開發者根據專案需求撰寫 Podfile 來指定需要的套件
  2. 執行 pod install 安裝套件
  3. CocoaPods 寫入 Podfile.lock 檔案,並產生給 Xcode 使用的 xcworkspace 檔案

如果專案中已存在 Podfile.lock,CocoaPods 在第二步會根據檔案內指定的套件版本進行安裝,只有在 Podfile 有版本更動時會更新 Podfile.lock 的內容。

回到 npm,其實 npm 本身有稱為 shrinkwrap 的 lock 機制,但 shrinkwrap 是個 optional 功能,需要手動執行 npm shrinkwrap 產生 lock 檔。 因此,Facebook 於今年提出了一個新的套件管理工具 Yarn(註1),除了宣稱安裝速度極快,也提供 yarn.lock 為 lock 機制,讓 Node.js 開發能夠擁有 lock 機制的好處。

使用 npm install -g yarn 安裝 Yarn 後,接下來就能直接使用 yarnyarn add <package-name> 等指令做套件安裝,每次均會更新 yarn.lock。 不過,Yarn 當然不會是最佳選擇,目前(12/7 現在)還在 0.17.x 的階段、使用上也容易遇到一堆 bug(尤其是剛推出時非常令人困擾的 issue),所以還是會常搭配 npm 進行安裝。

Frontend Server 結構

本次專案使用 single page application (SPA) 架構設計,frontend server 只運行一個簡單的 Express.js app 負責提供 HTML 給瀏覽器,Routing 機制全部交由前端的 JS 處理。(Routing 的細節預計在後續的章節介紹) 因此 Express.js side 的 router 就只有單純的把所有 URL 導給單一頁面:

app.get('*', (req, res) => {
    res.render('index.server.pug', {
        src: {
            script: scriptSrc
        },
        title: 'Online Course'
    });
});

在 SPA 架構下 frontend server 做的事情很少,理論上在 production 階段,連上面這個提供 HTML 頁面的 router 都能以靜態形式搬移到 CDN 或 AWS Lambda 上; 不過這次的專案中預計會使用 token-based authentication 機制,屆時 frontend server 還會負責一些 authentication 的工作,這部份會等 API Server 完成後回來實作 frontend server 的註冊/登入機制。

React.js 的 Server-Side Rendering

React.js 被稱為 universal (isomorphic) 的原因是其支援於 Node.js 執行部分 React.js 程式的特性。 借助這個特性,Node.js server 可以在收到頁面的 request 時先在 server 進行一次 ReactDOMServer.renderToString 過程,輸出靜態 HTML code 作為 response body,瀏覽器即可直接檢視頁面內容而不用等待前端的 ReactDOM.render 執行完畢。 這個機制稱為 server-side rendering (SSR),能夠強化 search engine optimization (SEO)、程式效能和 code reuse 能力。

要注意的是,SSR 並非 drop-in feature,若是需要使用 SSR 機制,在撰寫 client-side code 時要注意程式碼是否有 universal 性質,例如:

  1. Universal code 不能包含 Webpack 限定的特殊 require 呼叫
  2. 所有 DOM manipulation 必須放在不會於 server side runtime 執行的地方,例如 React component 的 componentDidMount method
  3. 原本在前端需要 AJAX 呼叫後才能 render 的流程(例如 Angular ui-routerresolve 機制可達到非同步 routing 功能)需要能在 server side 執行,所以需要 isomorphic-fetchsuperagentaxios 等 universal AJAX library

在實作 React SSR 時,Webpack 的 entry 和 server side 的進入點會分開各自執行 ReactDOM.renderReactDOMServer.renderToString,比較麻煩的是需要各自準備 initial state 交給 root component。 以使用 Redux 為例,通常的作法是在 server side 設定 initial state、進行 @connectReactDOMServer.renderToString,然後將 initial state 塞在 HTML (本專案用 Pug 進行 server side HTML rendering)內:

script(type="text/javascript").
    window.__INITIAL_STATE__='!{initialState}';

並從 Webpack 的 entry 提取出來:

const initialState = window.__INITIAL_STATE__;
window.__INITIAL_STATE__ = null;
// feed initialState to Redux store...

註1:yarn 原為名叫 kpm 的內部專案,後來 Facebook 公開後改了個和其他領域的現有工具同樣的名稱

results matching ""

    No results matching ""