【VueSSR系列二】clientManifest與bundle的處理流程解讀

上一節討論了VueSSR的構建流程,構建出來的clientManifest和serverBundle最終會被轉換成html,這一節咱們深刻vue-server-renderer的核心內容,看看它們都通過了哪些的處理。這一節的內容包括:javascript

  • 使用BundleRenderer的緣由
  • 服務端渲染的大致原理
  • 輸出html正文過程
  • 預加載與預取資源

閱讀源碼先總體查看寫下代碼文件結構和入口文件暴露的接口,而後運行一段demo斷點來跟蹤代碼處理數據的細節,下面將以這段demo做爲閱讀代碼的入口:css

image

使用BundleRenderer的緣由

vue-server-renderer提供了兩個API,createRenderer和createBundleRender。它們的用法同樣,若是你閱讀了源碼你會知道createBundleRenderer實際上是在createRenderer上作了擴展,以提供官網上所說的下面幾種特性:html

  • 支持source map
  • 熱重載
  • 關鍵css注入
  • 資源注入 使用基本SSR(createRenderer)有一個問題,當源代碼更新後須要重啓服務器以獲取最新代碼,因此須要引入熱重載。官方的作法就是經過webpack自定義插件將server bundle生成可傳入renderer處理的特殊JSON文件,createBundleRender須要具有處理JSON文件能力以便支持熱重載。當這一章不打算闡述SSr如何支持熱重載,咱們先來看看SSR最終是怎樣輸出html的。

服務端渲染的大致原理

源碼看到最後,我發現了一段能夠能夠大體的理解它的脈絡的代碼:vue

// templateRenderer.render方法內
if (this.inject) {
    return (
        template.head(context) +
        (context.head || '') +
        this.renderResourceHints(context) +
        this.renderStyles(context) +
        template.neck(context) +
        content +
        this.renderState(context) +
        this.renderScripts(context) +
        template.tail(context)
    )
} else {
    return (
        template.head(context) +
        template.neck(context) +
        content +
        template.tail(context)
    )
}

複製代碼

轉換成圖來展現:java

html分段圖

原來bundleRenderer以字符串拼接的方式將html的片斷組合成整個html文檔,整個html文檔會劃分爲幾個部分,這裏只列出主要的部分,分別是頭部、資源預加載和預取資源、inlineStyle、正文、state、script。這幾個主要字符片斷跟輔助的片斷結合就結合成了輸出的服務端渲染的html內容。webpack

前面咱們已經知道clientManifest的做用是記錄文檔資源文件的信息,bundleRenderer利用clientManifest的信息,推理出須要預加載和預取的資源和首屏加載的js資源文件,而後拼接成資源預加載和預取片斷和script片斷。而server bundle中記錄中編譯後的源代碼,這正是html文檔中正文的內容來源。web

輸出html正文過程

那麼server bundle是如何被處理成正文的呢?我將bundle的處理過程當中的關鍵步驟畫成了流程圖,以下: 數組

vueSSR處理bundle生成正文流程

當執行createBundleRunner()時,在內部會執行compileModule(),生成一個處理編譯後源碼的函數evaluate。evaluate函數會將編譯後文件源碼包裝成module對象,而後返回module.exports.defualt,它就是封裝了文件源碼的函數,執行這個函數就就至關於執行文件源碼。當這個文件是入口文件時,返回的就是entry入口文件源碼的封裝函數,也就是runner,那麼執行runner(context)至關於執行entry-server.js導出的函數,以下。服務器

export default context => {
  return new Promise((resolve, reject) => {
    const { app, router, store } = createApp(context.url)
    ...
    resolve(app)
    ...
  })
}
複製代碼

app就是執行該函數後,Promise狀態爲fulfilled時往下傳遞的單頁應用的vue根實例。以後app會傳入renderToString方法,該方法內會調用render函數,遞歸根實例中的每一個子組件對象,渲染每一個子組件的template而後組裝template,最終生成html中的正文片斷content。app

render函數內實際上是執行了vue內部的render函數,執行組件的生命鉤子函數,生成虛擬dom節點,只不過最後轉化成了template字符串返回。最後templateRenderer.render方法將正文和其餘文檔片斷組件成整個html文檔返回。

預加載與預取資源

bundle Renderer如何推斷出預加載和預取資源的呢?

咱們如今已經知道html是在templateRenderer.render方法中組合的,在它裏面有這麼一句this.renderResourceHints(context),預加載和預取片斷就是由它來生成的。如下是renderResourceHints方法的流程圖:

vueSSR預加載和預取片斷生成生成流程圖

從圖中咱們很清晰得知道renderPreloadLinks方法和renderPrefetchLinks方法都調用了getUsedAsyncFiles方法,來拿到組件實例中所依賴到的代碼chunk文件列表usedAsyncFiles,它是經過context._registeredComponents獲得組件實例依賴的moduleIds,再根據clientManifest中的modules對象(記錄modules之間的依賴關係)和all對象(記錄着全部編譯後的文件),找出moduleIds所對應的文件,這些文件就是初始渲染組件所依賴到的代碼 chunk文件,也就是usedAsyncFiles。

資源加載文件

usedAsyncFiles與preloadFiles(clientManifest中的initial文件數組)合併就是須要預加載的資源列表,usedAsyncFiles與prefetchFiles(clientManifest中的async文件數組)的差集就是預取的資源列表。

小結

  • 服務端渲染思路:將html劃分片斷,頭部、資源預加載和預取資源、inlineStyle、正文、state、script和其餘輔助銜接片斷,推斷生成這些片斷而後組裝成整一個html文檔。
  • 生成正文:將bundle中的編譯後代碼字符串包裝成一個可執行的模塊,運行模塊獲得應用實例app(vue根實例),遞歸渲染app中的子組件,將data與template組合,最後組裝成正文部分。
  • clientManifest中記錄着資源加載信息,經過運行app獲得context對象中_registedComponents拿到moduleIds,而後獲得usedAsyncFiles(組件依賴的文件)。其與preloadFiles(clientManifest中的initial文件數組)的並集就是初始渲染的預加載的資源列表,與prefetchFiles(clientManifest中的async文件數組)的差集就是預取的資源列表。
相關文章
相關標籤/搜索