BigPipeは、Webページの読み込み速度を最適化するためにFacebookが開発したテクノロジーです。インターネット上にnode.jsを使用して実装された記事はほとんどありません。実際、それはnode.jsだけではありません他の言語でのBigPipeの実装は、インターネットではまれです。このテクノロジーが登場してからずっと後、Webページフレームワーク全体が最初に送信された後、ページ内のモジュールをリクエストするために別のAJAXリクエストを使用したと思いました。 BigPipeの核となる概念が1つのHTTP要求のみを使用することであることを学びましたが、ページ要素は順番に送信されました。
このコアコンセプトを理解するのは簡単です。 node.jsの非同期機能のおかげで、node.jsを使用してBigPipeを簡単に実装できます。この記事では、例を段階的に使用して、BigPipeテクノロジーの原因とnode.jsに基づく簡単な実装を説明します。
Expressを使用してデモを行います。簡単にするために、Jadeをテンプレートエンジンとして選択します。エンジンのサブテンプレート(部分)機能を使用するのではなく、代わりに子テンプレートを使用して親テンプレートデータとしてHTMLをレンダリングします。
最初にnodejs-bigpipeフォルダーを作成し、次のようにpackage.jsonファイルを書き込みます。
コードコピーは次のとおりです。
{
「名前」:「BigPipe-Experiment」
、「バージョン」:「0.1.0」
、「プライベート」:本当
、「依存関係」:{
「エクスプレス」:「3.xx」
、「統合」:「最新」
、「ジェイド」:「最新」
}
}
NPMインストールを実行して、これら3つのライブラリをインストールします。統合は、ジェイドの呼び出しを促進するために使用されます。
最初に最も簡単な試みをしましょう。2つのファイル:
app.js:
コードコピーは次のとおりです。
var Express = require( 'Express')
、cons = require( 'conslidate')
、jade = require( 'Jade')
、パス= require( 'path')
var app = express()
App.Engine( 'Jade'、cons.jade)
app.set( 'views'、path.join(__ dirname、 'Views'))
app.set( 'View Engine'、 'Jade')
app.use(function(req、res){
Res.Render( 'Layout'、{
S1:「こんにちは、私は最初のセクションです。」
、S2:「こんにちは、私は2番目のセクションです。」
})
})
app.listen(3000)
ビュー/レイアウト
コードコピーは次のとおりです。
Doctype HTML
頭
タイトルこんにちは、世界!
スタイル
セクション {
マージン:20px Auto;
ボーダー:1px点線の灰色。
幅:80%;
高さ:150px;
}
セクション#s1!= s1
セクション#s2!= s2
効果は次のとおりです。
次に、2つのセクションテンプレートを2つの異なるテンプレートファイルに入れます。
ビュー/s1.jade:
コードコピーは次のとおりです。
H1部分1
.content!= content
ビュー/s2.jade:
コードコピーは次のとおりです。
H1部分2
.content!= content
レイアウトにいくつかのスタイルを追加します
コードコピーは次のとおりです。
セクションH1 {
フォントサイズ:1.5;
パディング:10px 20px;
マージン:0;
境界線:1px点線の灰色。
}
セクションdiv {
マージン:10px;
}
app.jsのapp.use()部分を次のように変更します。
コードコピーは次のとおりです。
var temp = {
S1:jade.compile(fs.readfilesync(path.join(__ dirname、 'views'、 's1.jade')))
、s2:jade.compile(fs.readfilesync(path.join(__ dirname、 'views'、 's2.jade'))))
}
app.use(function(req、res){
Res.Render( 'Layout'、{
S1:temp.s1({content: "こんにちは、私は最初のセクションです。"})
、s2:temp.s2({content: "こんにちは、2番目のセクションです。"})
})
})
前に、「子テンプレートのデータとして子テンプレートを使用してレンダリング後のHTMLが完了した」と述べました。つまり、2つのメソッドTEMP.S1とTEMP.S2は、2つのファイルS1.JADEおよびS2.JADEのHTMLコードを生成し、レイアウトの2つの変数S1とS2の値としてこれらの2つのコードを使用します。
今、ページは次のようになります:
一般的に、2つのセクションのデータは個別に取得されます - データベースをクエリするか、RESTFULリクエストをクエリするかどうかにかかわらず、2つの関数を使用して、そのような非同期操作をシミュレートします。
コードコピーは次のとおりです。
var getData = {
D1:function(fn){
setimeout(fn、3000、null、{content: "こんにちは、私は最初のセクションです。"})
}
、d2:function(fn){
setimeout(fn、5000、null、{content: "こんにちは、私は2番目のセクションです。"})
}
}
このように、app.use()のロジックはより複雑になり、それに対処する最も簡単な方法は次のとおりです。
コードコピーは次のとおりです。
app.use(function(req、res){
getData.d1(function(err、s1data){
getData.d2(function(err、s2data){
Res.Render( 'Layout'、{
S1:TEMP.S1(S1DATA)
、S2:TEMP.S2(S2DATA)
})
})
})
})
これにより、必要な結果も得られますが、この場合、戻るのに8秒間かかります。
実際、実装ロジックは、getData.D1の結果が返された後、GetData.D2が呼び出しを開始し、そのような依存関係がないことを示しています。 JavaScriptの非同期呼び出しを処理するAsyncなどのライブラリを使用して、この問題を解決することができますが、ここに書いてみましょう。
コードコピーは次のとおりです。
app.use(function(req、res){
var n = 2
、result = {}
getData.d1(function(err、s1data){
result.s1data = s1data
-n || writeresult()
})
getData.d2(function(err、s2data){
result.s2data = s2data
-n || writeresult()
})
function writeresult(){
Res.Render( 'Layout'、{
S1:Temp.S1(result.s1data)
、S2:TEMP.S2(result.S2Data)
})
}
})
これにはわずか5秒かかります。
次の最適化の前に、jQueryライブラリを追加し、CSSスタイルを外部ファイルに入れます。ちなみに、後で使用するJadeテンプレートを使用するために必要なRuntime.jsファイルを追加し、app.jsを含むディレクトリで実行します。
コードコピーは次のとおりです。
mkdir static
CD静的
curl http://code.jquery.com/jquery-1.8.3.min.js -o jquery.js
ln -s ../node_modules/jade/runtime.min.js jade.js
レイアウトのスタイルタグのコードを取り出して、static/style.cssに置き、ヘッドタグを次のように変更します。
コードコピーは次のとおりです。
頭
タイトルこんにちは、世界!
link(href = "/static/style.css"、rel = "styleSheet")
スクリプト(src = "/static/jquery.js")
スクリプト(src = "/static/jade.js")
app.jsでは、両方のダウンロード速度を2秒にシミュレートし、app.use(function(req、res){:
コードコピーは次のとおりです。
var static = express.static(path.join(__ dirname、 'static')))
app.use( '/static'、function(req、res、next){
setimeout(static、2000、req、res、next)
})
外部静的ファイルのため、ページの読み込み時間は約7秒です。
HTTPリクエストを受け取ったらすぐにヘッドパーツを返し、2つのセクションが非同期操作が完了するまで待機してから戻ると、HTTPのブロックされた伝送エンコードメカニズムを使用します。 node.jsでは、res.write()メソッドを使用する限り、転送エンコード:チャンクヘッダーが自動的に追加されます。このようにして、ブラウザが静的ファイルをロードしている間、ノードサーバーは非同期呼び出しの結果を待っています。最初にレイアウトでセクションの2行を削除しましょう。
コードコピーは次のとおりです。
セクション#s1!= s1
セクション#s2!= s2
したがって、このオブジェクトをres.render(){s1:…、s2:…}で指定する必要はありません。また、res.render()はデフォルトでres.end()を呼び出すため、レンダリングが完了した後にコールバック関数を手動で設定し、res.write()メソッドを使用する必要があります。 layout.jadeの内容は、writeresult()コールバック関数にある必要はありません。このリクエストを受け取ったときに返すことができます。コンテンツタイプのヘッダーを手動で追加したことに注意してください。
コードコピーは次のとおりです。
app.use(function(req、res){
Res.Render( 'Layout'、function(err、str){
if(err)return res.req.next(err)
res.setheader( 'content-type'、 'text/html; charset = utf-8')
res.write(str)
})
var n = 2
getData.d1(function(err、s1data){
res.write( '<section id = "s1">' + temp.s1(s1data) + '</section>')
-n || res.end()
})
getData.d2(function(err、s2data){
res.write( '<section id = "s2">' + temp.s2(s2data) + '</section>')
-n || res.end()
})
})
これで、最終的な読み込み速度は約5秒に戻ります。実際の操作では、ブラウザは最初にヘッドパートコードを受信し、3つの静的ファイルをロードします。これには2秒かかります。その後、3番目の2番目に、Partial 1が表示され、Partial 2が5秒間に表示され、Webページの読み込みが終了します。スクリーンショットはありません。スクリーンショット効果は、前の5秒のスクリーンショットと同じです。
ただし、GetData.D1はGetData.D2よりも速いため、この効果を達成できることに注意することが重要です。つまり、最初に返されるWebページのブロックは、その背後にあるインターフェイスの非同期コールの結果を誰が返すかによって異なります。 getData.d1を8秒で返すように変更すると、最初に部分的な2を返します。S1とS2の順序が逆転し、Webページの最終結果は期待と矛盾しています。
この問題は、最終的に私たちをBigPipeに導きます。これは、Webページの各部分の表示順序をデータの送信順序から切り離すことができるテクノロジーです。
基本的なアイデアは、最初にWebページ全体の一般的なフレームワークを送信することであり、後で送信する必要がある部分は、空のDiv(または他のタグ)で表されます。
コードコピーは次のとおりです。
Res.Render( 'Layout'、function(err、str){
if(err)return res.req.next(err)
res.setheader( 'content-type'、 'text/html; charset = utf-8')
res.write(str)
res.write( '<section id = "s1"> </section> <section id = "s2"> </section>')
})
次に、返されたデータをJavaScriptに書き込みます
コードコピーは次のとおりです。
getData.d1(function(err、s1data){
res.write( '<script> $( "#s1")。html( "' + temp.s1(s1data).replace(/"/g、 '// "') + '")</script>')
-n || res.end()
})
S2の処理はこれに似ています。この時点で、Webページをリクエストする2秒で、2つの空白の点線ボックスが表示され、5秒でPartial 2が表示され、8番目の秒でPartial 1が表示され、Webページリクエストが完了します。
この時点で、BigPipe Technologyによって実装された最もシンプルなWebページを完成させました。
書かれているWebページフラグメントには、s1.hadeの変更などのスクリプトタグがある場合、次のことに注意してください。
コードコピーは次のとおりです。
H1部分1
.content!= content
スクリプト
アラート( "s1.jadeからのアラート")
次に、Webページを更新すると、アラート文が実行されず、Webページにエラーがあることがわかります。ソースコードを確認し、<script>の文字列によって引き起こされるエラーであることを知ってください。 <//スクリプト>に置き換えてください
コードコピーは次のとおりです。
res.write( '<script> $( "#s1")。html( "' + temp.s1(s1data).replace(/"/g、 '//"').replace(/</script>/g、' <// script> ') +' ")</script> ')
上記では、bigpipeの原則とnode.jsを使用してBigPipeを実装する基本的な方法について説明します。そして、それは現実にどのように使用すべきですか?レンガとヒスイを投げる簡単な方法は次のとおりです。コードは次のとおりです。
コードコピーは次のとおりです。
var resproto = require( 'express/lib/response')
resproto.pipe = function(selector、html、cheplage){
this.write( '<script>' + '$( "' + selector + '")。' +
(置き換え=== true? 'cheplywith': 'html') +
'( "' + html.replace(/"/g、 '//"').replace(/<//script>/g、' <// script> ') +
'")</script>')
}
function pipename(res、name){
res.pipecount = res.pipecount || 0
res.pipemap = res.pipemap || {}
if(res.pipemap [name])return
Res.PipeCount ++
res.PipeMap [name] = this.id = ['pipe'、math.random()。toString()。substring(2)、(new date())。valueof()]。
this.res = res
this.name = name
}
resproto.pipename = function(name){
新しいPipename(これ、名前)を返す
}
resproto.pipelayout = function(view、options){
var res =これ
object.keys(options).foreach(function(key){
if(options [key] instanceof pipename)options [key] = '<span id = "' + options [key] .id + '"> </span>'
})
Res.Render(表示、オプション、関数(err、str){
if(err)return res.req.next(err)
res.setheader( 'content-type'、 'text/html; charset = utf-8')
res.write(str)
if(!res.pipecount)res.end()
})
}
resproto.pipepartial = function(name、view、options){
var res =これ
Res.Render(表示、オプション、関数(err、str){
if(err)return res.req.next(err)
res.pipe( '#'+res.pipemap [name]、str、true)
-res.pipecount || res.end()
})
}
app.get( '/'、function(req、res){
Res.PipeLayout( 'レイアウト'、{
S1:res.pipename( 's1name')
、s2:res.pipename( 's2name')
})
getData.d1(function(err、s1data){
Res.PipePartial( 'S1Name'、 'S1'、S1DATA)
})
getData.d2(function(err、s2data){
Res.PipePartial( 'S2Name'、 'S2'、S2Data)
})
})
また、レイアウトに2つのセクションを追加します。
コードコピーは次のとおりです。
セクション#s1!= s1
セクション#s2!= s2
ここでのアイデアは、パイプのコンテンツを最初にスパンタグで配置し、データを非同期に取得し、対応するHTMLコードをブラウザに出力し、プレースホルダースパン要素をjQueryの置換メソッドに置き換える必要があることです。
この記事のコードは、https://github.com/undozen/bigpipe-on-nodeにあります。私は各ステップをコミットにしました。実際にローカルで実行してハッキングできることを願っています。次のいくつかの手順には読み込み順序が含まれているため、ブラウザを実際に開いて体験する必要があり、スクリーンショットから見ることができません(実際、GIFアニメーションで実装する必要がありますが、やるのが面倒でした)。
BigPipeの練習について最適化する余地はまだたくさんあります。たとえば、パイプのコンテンツのトリガーされた時間値を設定することをお勧めします。呼び出されたデータが非同期に迅速に戻ってくる場合、BigPipeを使用する必要はありません。 Webページを直接生成して送信できます。 BigPipeを使用する前に、データリクエストが一定期間を超えるまで待つことができます。 Ajaxと比較して、BigPipeを使用すると、ブラウザからNode.jsサーバーにリクエストの数を保存するだけでなく、node.jsサーバーからデータソースにリクエスト数を保存します。ただし、Snowball NetworkがBigPipeを使用した後、特定の最適化と実践方法を共有しましょう。