前端編譯期頁面渲染做爲優化方案的想法

前面有篇文章已經寫了一遍, 就是在 Respo 站點上有的優化方案,
https://segmentfault.com/a/11...
這種方案當中, 頁面的靜態內容是提早編譯好的, 但同時前端也作渲染,
大體的流程是html

  • 編譯階段, 調用 SSR API 對頁面進行一次渲染前端

  • 頁面加載到前端, HTML 先於 js 在頁面上展現出來vue

  • js 啓動後, 生成和 HTML 幾乎同樣的 DOM, 作一次初始化git

  • 以後按照正常的更新步驟作界面的更新github

這個作法其實近似於單頁面技術流行以前, jQuery 加模板引擎的作法,
服務端首先會用模板引擎渲染好帶數據的頁面, 前端再動態更新,
但單純用模板引擎作渲染存在的一些侷限性:gulp

  • 對後端依賴嚴重, 必需有一個獨立的渲染服務器segmentfault

  • 調試不方便, 好比沒有熱替換技術, 並且與模板引擎相關後端

  • 開發方式, 單頁面技術有完整的前端路由等等方案, 用模板引擎增長複雜度緩存

因此更好的辦法是既是前端渲染單頁面, 又能原樣拿到後端運行,
也就是最近出現頻率更高的服務端渲染, 簡稱 SSR,
然而 SSR 實現起來須要處理一些配套的技術細節, 不然麻煩挺多:服務器

  • 數據請求用戶驗證相關的問題, 須要往兩邊轉發 Cookies

  • 首屏可能須要不止一個甚至不一樣時發出的請求, 須要額外的抽象

  • 服務端的性能不太好控制, 除非能很好利用緩存, 或者對性能要求不高

所以在 SSR 周邊的方案成熟以前, 我認爲提供過渡的方案比較可行,
也就是, 在編譯階段先渲染靜態內容, 而動態內容仍然採用純客戶端渲染.
這也是 Addy Osmani 因此的 App Shell 渲染的問題,
這個方案能帶來一些基礎的好處:

  • 用戶能更快地看 App Shell, 頁面也能緩存, 於是用戶覺得頁面更快了

  • 編譯階段渲染, 於是不存在服務器開銷, 暫時也不會涉及數據請求的麻煩

  • 前端開發的方案更加一致, 也就是目前熟悉的單頁面應用的手法

同時相比前面最爲理想化的 SSR 的效果存在一些差距:

  • 畢竟首屏是沒有數據的, 用戶看到的是框框, 甚至只是色塊

  • 首屏也對應加載動畫的頁面, 這相比以前須要更額外的一些代碼來完成

  • 對於 SEO 來講, 仍是差一些, 只有頭部和導航, 沒有具體數據

若是後面須要再填充數據以應對 SEO, 遷移方案仍然不樂觀,
從單純前端渲染, 到徹底 SSR, 這之間有很多的臺階須要邁過,
並且即使如今基於已有的方案來說, 仍是有一些不成熟的地方:

  • 前端路由和編譯階段路由要保持一致, 甚至對於 Nginx 也要作到一致.
    路由能不能徹底作到支持, 實際上是不肯定的

  • 代碼拆分, 整個站的路由分割, 實際場景當中會更復雜

固然, 我仍是以爲基於目前大量純前端渲染的單頁面, 這是一個加分項,
只要框架層面對 SSR API 作了實現, 用不大的成本就能完成這個優化,
而後對首屏加載的效果作一點點提高.

React 和 Respo 我前面已經作了演示, 感興趣往前面翻一翻,
這邊加上前面幾天用 Vue 2 仿製的一個 Demo, 幾乎一樣的功能:
Demo http://vue-coffee-workflow.co...
Repo https://github.com/coffee-js/...
大體有一些要點, 相似的項目能夠參考:

  • 代碼在到 Node 運行, 能夠用 Webpack 預處理, 或者想辦法直接載入

  • 用戶可能從多個路徑訪問, 因此須要編譯出多個 HTML 文件做爲入口

  • 路由建議使用 .html 做爲後綴, 方便 Nginx 直接命中文件

  • 組件當中不建議直接引入資源文件, 不方便處理, 建議在 CSS 當中進行打包

最主要的因素仍是框架自己對 SSR 的支持儘可能作到簡單,
如今 React, Respo, Vue 2 的 Virtual DOM 都較好地支持 SSR 渲染了,
至於 Angular, 前面收集到的資料不夠, 我等等看別人的進展.

我用 Vue 2 試驗的例子, 直接放在了 GitHub 上, 打包方式有註明.
中間爲了加載代碼的方便, 我直接用 js 來寫 vue render 方法,
實際開發當中幾乎沒人這麼作, 可是對於 Node 環境這樣更友好,
不過大概仍是 Vue 早起那種 template 的寫法更加實在一點...
核心的渲染代碼是 gulpfile 當中的這一段, 編譯路由, 生成 HTML 文件:
https://github.com/coffee-js/...

# this is the initial address
entries = [
  'index.html'
  'page/a.html'
  'page/b.html'
]
entries.forEach (address) ->
  app = new Vue
    router: router
    store: store
    components:
      container: Container
    render: (h) ->
      h 'container'
  router.push address
  renderer.renderToString app, (err, appHtml) ->
    if err?
      throw err
    else
      html = template.render appHtml, store.state, settings
      htmlPath = path.join 'build', address
      console.log 'render entry:', htmlPath
      mkpath.sync path.dirname(htmlPath)
      fs.writeFileSync htmlPath, html

細節不展開了, 畢竟是試驗的代碼, 實際項目並非我這樣寫的.若是有 SSR 相關的想法, 能夠一塊兒交流下, 我這邊方案還比較粗淺.

相關文章
相關標籤/搜索