如何在Koa整合Bigpipe首屏渲染服務

2021-09-11 14:32:00 字數 4034 閱讀 3156

本文章使用的**

為什麼前後端分離的時代還需要服務端渲染?

就是為了快啊!還能做seo啊!下面我們來簡單分析下這兩種方式的渲染過程

前後端分離頁面渲染模式:

1、瀏覽器發起頁面請求

2、解析html

3、發起請求獲取頁面對應的js、css

4、解析css、js

5、發起ajax請求獲取資料後將資料渲染到dom中

服務端渲染:

1、瀏覽器發起請求

2、服務端發起請求獲取對應的頁面資料後將資料拼接到讀取的html中

3、返回拼接後的html給瀏覽器

4、瀏覽器解析html

5、獲取資源、解析資源

通過上面的對比,可以看出為什麼服務端渲染更快?因為前端通過ajax渲染,需要等到獲取js後,再發起http請求獲取到資料後才完成渲染,而服務端免去了多次http請求的過程(http請求耗時),直接讓服務端返回渲染好的html頁面。

那類似首屏這種對速度有要求的就可以使用服務端渲染了。

這裡提出乙個問題,如果乙個頁面,在服務端渲染中,資料來源比較多的情況下,我們需要等待所有的請求都返回資料才進行html拼接並返回,這樣我們頁面最終渲染的速度就限制在最遲返回資料的請求上了。

那針對上述資料來源較多的情況,還有優化的方案嗎?答案就是bigpipe。

bigpipe是一種採用流的方式對頁面進行渲染的機制,在瀏覽器請求頁面時,開啟管道後持續對頁面的塊進行輸出。

如下圖,塊a、b、c拼裝好塊之後直接通過開始建立的管道輸出到頁面中,這樣頁面的最終輸出就不需要依賴最後乙個塊的拼裝時間了。

下面來抽象乙個簡單的bigpipe中介軟體。

以中介軟體的形式載入bigpipe服務,並指定模板與靜態資源的跟目錄

templatepath = resolve(__dirname, './template'), // 模板資料夾

publicpath = resolve(__dirname, './view') // 靜態資源目錄

));使用bigpipe,我們一般需要讀取乙個html-layout,接下來就是定義每乙個塊的模板路徑和資料來源,執行乙個render方法後,開始返回html並持續輸出我們定義的塊。

let bigpipe = ctx.body = ctx.createbigpipe();

// 定義輸出的html layout

bigpipe.definelayout('/bigpipe.html');

// 定義片段,這裡我們使用promise的方式模擬http請求

bigpipe.definepagelets([

, 3000)})}

},, 2000)})}

},, 0)})}

}]);

bigpipe.render();

})複製**

bigpipe.definepagelets傳入的物件陣列中,每乙個物件中的id為每乙個塊對應需要插入的dom節點的id屬性值,tpl為該模板在模板根目錄下的路徑,getdata只是乙個模擬http請求的函式,可以設定在x秒後返回輸出資料並進行拼接返回。html-layout如下。

"utf-8">

test bigpipe

"a">

"b">

"c">

複製**

下面的createbigpipemiddleware中介軟體的實現

const  = require('path')

const bigpipe = require('./lib/bigpipe')

module.exports = createbigpipereadable

function createbigpipereadable (

templatepath = resolve(__dirname, '../../template'), // 模板根目錄(預設)

publicpath = resolve(__dirname, '../../../../public') // html根目錄(預設)

) ) }

return next()

}}複製**

上面是koa中介軟體的寫法,不太理解的可以google查一查。這個中介軟體會在ctx中掛載方法createbigpipe用於初始化bigpipe服務,那在後續的業務檔案中就可以直接通過呼叫ctx.createbigpipe來呼叫bigpipe服務了

下面就是具體bigpipe物件的類實現了。

首先,我們先讓bigpipe物件繼承readable(因為koa不支援直接呼叫底層res進行響應處理)

const readable = require('stream').readable;

class bigpipe extends readable

_read

() {}

...}複製**

接下來實現乙個definelayout函式,把layout轉成字串(也就是上文貼出來的html)

const  = require('path');

class bigpipe extends readable

...}複製**

下面的definepagelets用於傳入塊的配置,可傳入乙個物件多次呼叫或者直接傳入乙個陣列

class bigpipe extends readable  else 

}this.pageletsnum = this.pagelets.length;

} ...

}複製**

接下來是就是render函式了,呼叫後直接開始輸出layout還有對塊進行拼接傳輸

class bigpipe extends readable 

...}複製**

上面,因為bigpipe繼承了readable,所以可以用push的方式推入資料,接著await後則是乙個promise.all方法,等到所有的塊輸出完成後,才執行done方法閉合html結束資料傳輸。

下面是最重要的方法,wrap方法,用於將傳入的塊陣列包裝成promise(這裡我們使用handlebars作為模板引擎,當然還有很多其他選擇)

const handlebars = require('handlebars');

class bigpipe extends readable ,

tplhtml = '';

// 呼叫個個塊的getdata方法,等待資料獲取

data = await pagelet.getdata()

// 獲取hbs模板

tpl = this.gethtmltemplate(pagelet.tpl);

// 將資料拼接好後返回模板字串,並清除換行符

tplhtml = this.clearenter(tpl(data));

// 以script輸出到頁面中

this.push(``)

this.pageletsnum--;

resolve()

})()

})})

} // 獲取骨架並轉成字串

gethtmltemplate(realpath)

// 清除模板字串的換行符

clearenter(html)

...}複製**

每乙個promise中,在data返回後,都會呼叫this.push方法推入一串指令碼,執行的就是如下的在html-layout中的函式,傳入的是id屬性值與拼接好的html塊,執行renderflush就會將塊輸出到html中。

var renderflush = function (selector, html) ;

複製**

上面我們傳入了getdata方法,相應的你也可以使用request等模組去封裝乙個函式去獲取對應資料,這裡只是作為演示,直接使用乙個promise返回資料。

1、先輸出html與塊c

2、2秒後,輸出塊b

3、3秒後,輸出完畢,管道關閉(注意,瀏覽器重新整理按鈕從叉變成了箭頭)

如何在Koa整合Bigpipe首屏渲染服務

本文章使用的 為什麼前後端分離的時代還需要服務端渲染?就是為了快啊!還能做seo啊!下面我們來簡單分析下這兩種方式的渲染過程 前後端分離頁面渲染模式 1 瀏覽器發起頁面請求 2 解析html 3 發起請求獲取頁面對應的js css 4 解析css js 5 發起ajax請求獲取資料後將資料渲染到do...

如何在C 中整合Lua指令碼

如何在c 中整合lua指令碼 logger virtual logger logger int n logger logger logger int setvalue int val int getvalue public int v 匯入到lua指令碼 luaclass state create ...

koa2 mysql 事務 koa2整合mysql

引入mysql包 npm install mysql 封裝mysql 建立mysql.js檔案放在utils 工具包 中 使用pool連線池 mysql.js 封裝mysql const mysql require mysql let pools 連線池 let query sql,callback...