BigPipe adalah teknologi yang dikembangkan oleh Facebook untuk mengoptimalkan kecepatan pemuatan halaman web. Hampir tidak ada artikel yang diimplementasikan dengan Node.js di Internet. Bahkan, ini bukan hanya node.js. Implementasi bigpipe dalam bahasa lain jarang terjadi di internet. Begitu lama setelah teknologi ini muncul, saya berpikir bahwa setelah seluruh kerangka kerja halaman web dikirim terlebih dahulu, saya menggunakan permintaan AJAX lain atau beberapa untuk meminta modul di halaman. Baru belum lama ini saya mengetahui bahwa konsep inti bigpipe adalah hanya menggunakan satu permintaan HTTP, tetapi elemen halaman dikirim secara berurutan.
Akan mudah untuk memahami konsep inti ini. Berkat fitur asinkron Node.js, mudah untuk mengimplementasikan bigpipe dengan node.js. Artikel ini akan menggunakan contoh langkah demi langkah untuk menggambarkan penyebab teknologi bigpipe dan implementasi sederhana berdasarkan node.js.
Saya akan menggunakan Express untuk menunjukkan. Untuk kesederhanaan, kami memilih Jade sebagai mesin template, dan kami tidak menggunakan fitur sub-template mesin (parsial), melainkan menggunakan template anak untuk membuat HTML sebagai data templat induk.
Pertama-tama buat folder NodeJs-Bigpipe dan tulis file package.json sebagai berikut:
Salinan kode adalah sebagai berikut:
{
"Nama": "Eksperimen Bigpipe"
, "Versi": "0.1.0"
, "Pribadi": Benar
, "dependensi": {
"Express": "3.xx"
, "Konsolidasi": "Terbaru"
, "Jade": "Terbaru"
}
}
Jalankan Instal NPM untuk menginstal ketiga perpustakaan ini. Konsolidasi digunakan untuk memfasilitasi memanggil Jade.
Mari kita lakukan upaya paling sederhana terlebih dahulu, dua file:
app.js:
Salinan kode adalah sebagai berikut:
var express = membutuhkan ('ekspres')
, kontra = membutuhkan ('konsolidasi')
, Jade = membutuhkan ('Jade')
, path = membutuhkan ('path')
var app = express ()
app.engine ('Jade', Cons.Jade)
app.set ('view', path.join (__ dirname, 'views'))
app.set ('view engine', 'jade')
app.use (function (req, res) {
res.render ('tata letak', {
S1: "Halo, saya bagian pertama."
, S2: "Halo, saya bagian kedua."
})
})
app.listen (3000)
views/tata letak.jade
Salinan kode adalah sebagai berikut:
HTML DOCTYPE
kepala
Judul Halo, Dunia!
gaya
bagian {
Margin: 20px Auto;
Perbatasan: 1px bertitik abu -abu;
Lebar: 80%;
Tinggi: 150px;
}
Bagian#S1! = S1
Bagian#S2! = S2
Efeknya adalah sebagai berikut:
Selanjutnya, kami menempatkan dua bagian templat ke dalam dua file template yang berbeda:
views/s1.jade:
Salinan kode adalah sebagai berikut:
H1 parsial 1
.content! = konten
views/s2.jade:
Salinan kode adalah sebagai berikut:
H1 parsial 2
.content! = konten
Tambahkan beberapa gaya ke tata letak.
Salinan kode adalah sebagai berikut:
Bagian H1 {
Ukuran font: 1.5;
padding: 10px 20px;
Margin: 0;
Border-Bottom: 1px bertitik abu-abu;
}
Bagian Div {
margin: 10px;
}
Ubah bagian app.use () dari app.js menjadi:
Salinan kode adalah sebagai berikut:
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 ('tata letak', {
s1: temp.s1 ({konten: "Halo, saya bagian pertama."})
, s2: temp.s2 ({konten: "Halo, saya bagian kedua."})
})
})
Kami mengatakan sebelumnya, "HTML setelah rendering diselesaikan dengan templat anak sebagai data templat induk", yang berarti bahwa dua metode Temp.S1 dan Temp.S2 akan menghasilkan kode HTML untuk dua file S1.Jade dan S2.Jade, dan kemudian menggunakan dua bagian kode ini sebagai nilai -nilai dari kedua variabel S1 dan S2.
Sekarang halaman terlihat seperti ini:
Secara umum, data dari dua bagian diperoleh secara terpisah - apakah itu dengan menanyakan database atau permintaan yang tenang, kami menggunakan dua fungsi untuk mensimulasikan operasi yang tidak sinkron.
Salinan kode adalah sebagai berikut:
var getData = {
d1: function (fn) {
setTimeout (fn, 3000, null, {content: "Halo, saya bagian pertama."})
}
, d2: function (fn) {
setTimeout (fn, 5000, null, {content: "Halo, saya bagian kedua."})
}
}
Dengan cara ini, logika di app.use () akan lebih rumit, dan cara paling sederhana untuk menghadapinya adalah:
Salinan kode adalah sebagai berikut:
app.use (function (req, res) {
getData.d1 (function (err, s1data) {
getData.d2 (function (err, s2data) {
res.render ('tata letak', {
S1: Temp.S1 (S1Data)
, S2: Temp.S2 (S2Data)
})
})
})
})
Ini juga akan mendapatkan hasil yang kami inginkan, tetapi dalam hal ini, akan membutuhkan waktu 8 detik penuh untuk kembali.
Faktanya, logika implementasi menunjukkan bahwa getData.d2 mulai menelepon setelah hasil getData.d1 dikembalikan, dan mereka tidak memiliki ketergantungan seperti itu. Kita dapat menggunakan perpustakaan seperti async yang menangani panggilan asinkron javascript untuk menyelesaikan masalah ini, tetapi mari kita tulis di sini:
Salinan kode adalah sebagai berikut:
app.use (function (req, res) {
var n = 2
, hasil = {}
getData.d1 (function (err, s1data) {
result.s1data = s1data
--N || writteresult ()
})
getData.d2 (function (err, s2data) {
result.s2data = s2data
--N || writteresult ()
})
function writeresult () {
res.render ('tata letak', {
S1: Temp.S1 (hasilnya
, s2: temp.s2 (result.s2data)
})
}
})
Ini hanya membutuhkan waktu 5 detik.
Sebelum optimasi berikutnya, kami menambahkan perpustakaan jQuery dan memasukkan gaya CSS ke dalam file eksternal. Ngomong -ngomong, kami akan menambahkan file runtime.js yang diperlukan untuk menggunakan templat giok yang akan kami gunakan nanti, dan menjalankannya di direktori yang berisi app.js:
Salinan kode adalah sebagai berikut:
statis mkdir
CD statis
Curl http://code.jquery.com/jquery-1.8.3.min.js -o jQuery.js
ln -s ../node_modules/jade/runtime.min.js jade.js
Dan keluarkan kode dalam tag gaya di tata letak.
Salinan kode adalah sebagai berikut:
kepala
Judul Halo, Dunia!
link (href = "/static/style.css", rel = "stylesheet")
skrip (src = "/static/jQuery.js")
skrip (src = "/static/jade.js")
Di app.js, kami mensimulasikan kedua kecepatan unduhan hingga dua detik, dan ditambahkan sebelum app.use (function (req, res) {:
Salinan kode adalah sebagai berikut:
var static = express.static (path.join (__ dirname, 'static'))
app.use ('/static', function (req, res, next) {
SetTimeout (Static, 2000, Req, Res, Next)
})
Karena file statis eksternal, halaman kami sekarang memiliki waktu pemuatan sekitar 7 detik.
Jika kami mengembalikan bagian kepala segera setelah kami menerima permintaan HTTP, dan kemudian dua bagian menunggu sampai operasi asinkron selesai sebelum kembali, ini menggunakan mekanisme penyandian transmisi yang diblokir HTTP. Di node.js, selama Anda menggunakan metode res.write (), header transfer-encoding: chunked akan ditambahkan secara otomatis. Dengan cara ini, sementara browser memuat file statis, server simpul menunggu hasil panggilan asinkron. Pertama -tama mari kita hapus dua baris bagian dalam tata letak.
Salinan kode adalah sebagai berikut:
Bagian#S1! = S1
Bagian#S2! = S2
Oleh karena itu, kita tidak perlu memberikan objek ini di res.render () {s1:…, s2:…}, dan karena res.render () akan memanggil res.end () secara default, kita perlu mengatur fungsi callback secara manual setelah render selesai, dan menggunakan metode res.write () di dalamnya. Isi tata letak. Kami dapat mengembalikannya saat kami menerima permintaan ini. Perhatikan bahwa kami secara manual menambahkan header tipe konten:
Salinan kode adalah sebagai berikut:
app.use (function (req, res) {
res.render ('tata letak', fungsi (err, str) {
if (err) return res.req.next (err)
res.setHeader ('tipe konten', 'teks/html; charset = utf-8')
res.write (str)
})
var n = 2
getData.d1 (function (err, s1data) {
res.write ('<bagian id = "s1">' + temp.s1 (s1data) + '</siction>')
--N || res.end ()
})
getData.d2 (function (err, s2data) {
res.write ('<bagian id = "s2">' + temp.s2 (s2data) + '</siction>')
--N || res.end ()
})
})
Sekarang kecepatan pemuatan terakhir kembali ke sekitar 5 detik. Dalam operasi yang sebenarnya, browser pertama menerima kode bagian kepala dan memuat tiga file statis. Ini membutuhkan dua detik. Kemudian pada detik ketiga, parsial 1 muncul, parsial 2 muncul di detik kelima, dan pemuatan halaman web berakhir. Saya tidak akan memberikan tangkapan layar, efek tangkapan layar sama dengan tangkapan layar dalam 5 detik sebelumnya.
Namun, penting untuk dicatat bahwa efek ini dapat dicapai karena getData.d1 lebih cepat dari getData.d2. Dengan kata lain, blok mana di halaman web dikembalikan terlebih dahulu tergantung pada siapa yang mengembalikan hasil panggilan antarmuka yang tidak sinkron di belakangnya. Jika kita mengubah getData.D1 untuk kembali dalam 8 detik, pertama -tama kita akan mengembalikan parsial 2. Urutan S1 dan S2 terbalik, dan hasil akhir dari halaman web tidak konsisten dengan harapan kita.
Masalah ini pada akhirnya membawa kita ke bigpipe, yang merupakan teknologi yang dapat memisahkan urutan tampilan dari setiap bagian dari halaman web dari urutan data transmisi.
Ide dasarnya adalah untuk terlebih dahulu mengirimkan kerangka umum dari seluruh halaman web, dan bagian -bagian yang perlu dikirim nanti diwakili oleh div kosong (atau tag lainnya):
Salinan kode adalah sebagai berikut:
res.render ('tata letak', fungsi (err, str) {
if (err) return res.req.next (err)
res.setHeader ('tipe konten', 'teks/html; charset = utf-8')
res.write (str)
res.write ('<bagian id = "s1"> </siction> <bagian id = "s2"> </siction>')
})
Kemudian tulis data yang dikembalikan ke JavaScript
Salinan kode adalah sebagai berikut:
getData.d1 (function (err, s1data) {
res.write ('<script> $ ("#s1"). html ("' + temp.s1 (s1data) .replace (/"/g, '// "') + '") </cript>')
--N || res.end ()
})
Pemrosesan S2 mirip dengan ini. Pada saat ini, Anda akan melihat bahwa pada kedua kedua dari meminta halaman web, dua kotak bertitik kosong muncul, pada detik kelima, parsial 2 muncul, dan pada detik kedelapan, parsial 1 muncul, dan permintaan halaman web selesai.
Pada titik ini, kami telah menyelesaikan halaman web paling sederhana yang diimplementasikan oleh BigPipe Technology.
Perlu dicatat bahwa jika fragmen halaman web yang akan ditulis memiliki tag skrip, seperti mengubah s1.jade menjadi:
Salinan kode adalah sebagai berikut:
H1 parsial 1
.content! = konten
naskah
Peringatan ("Peringatan dari S1.Jade")
Kemudian segarkan halaman web dan Anda akan menemukan bahwa kalimat peringatan tidak dijalankan, dan halaman web akan memiliki kesalahan. Periksa kode sumber dan ketahuilah bahwa itu adalah kesalahan yang disebabkan oleh string di <script>. Ganti saja dengan <// script>
Salinan kode adalah sebagai berikut:
res.write ('<script> $ ("#s1"). html ("' + temp.s1 (s1data) .replace (/"/g, '//"')).
Di atas kami menjelaskan prinsip -prinsip bigpipe dan metode dasar untuk menerapkan bigpipe dengan node.js. Dan bagaimana seharusnya digunakan dalam kenyataan? Berikut adalah metode sederhana untuk melempar batu bata dan batu giok, kodenya adalah sebagai berikut:
Salinan kode adalah sebagai berikut:
var resproto = membutuhkan ('ekspres/lib/respons')
resproto.pipe = fungsi (selector, html, ganti) {
this.write ('<script>' + '$ ("' + selector + '").' +
(ganti === true? 'ReplaceWith': 'html') +
'("' + html.replace (/"/g, '//"').replace(/<//script>/g,' <// script> ') +
'") </script>')
}
fungsi pipename (res, name) {
res.pipecount = res.pipecount || 0
res.pipemap = res.pipemap || {}
if (res.pipemap [name]) kembali
res.pipecount ++
res.pipeMap [name] = this.id = ['pipa', math.random (). tostring (). substring (2), (tanggal baru ()). valueOf ()]. gabung ('_')
this.res = res
this.name = name
}
resproto.pipeName = function (name) {
Return New Pipename (ini, nama)
}
resproto.pipelayout = function (view, options) {
var res = ini
Object.keys (opsi) .foreach (function (key) {
if (option [key] instance dari Pipename) opsi [key] = '<span id = "' + option [key] .id + '"> </span>'
})
res.render (tampilan, opsi, fungsi (err, str) {
if (err) return res.req.next (err)
res.setHeader ('tipe konten', 'teks/html; charset = utf-8')
res.write (str)
if (! res.pipecount) res.end ()
})
}
resproto.pipepartial = function (name, view, options) {
var res = ini
res.render (tampilan, opsi, fungsi (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 ('tata letak', {
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)
})
})
Tambahkan juga dua bagian dalam tata letak.
Salinan kode adalah sebagai berikut:
Bagian#S1! = S1
Bagian#S2! = S2
Idenya di sini adalah bahwa konten pipa perlu ditempatkan dengan tag rentang terlebih dahulu, dapatkan data secara tidak sinkron dan render kode HTML yang sesuai sebelum mengeluarkannya ke browser, dan ganti elemen span placeholder dengan metode pengganti JQuery.
Kode artikel ini ada di https://github.com/undozen/bigpipe-on-node. Saya telah membuat setiap langkah menjadi komit. Saya harap Anda benar -benar dapat menjalankannya secara lokal dan meretasnya. Karena beberapa langkah berikutnya melibatkan pesanan pemuatan, Anda benar -benar harus membuka browser sendiri untuk mengalaminya dan tidak dapat melihatnya dari tangkapan layar (sebenarnya, itu harus diimplementasikan dengan animasi GIF, tetapi saya terlalu malas untuk melakukannya).
Masih ada banyak ruang untuk optimasi tentang latihan bigpipe. Misalnya, yang terbaik adalah menetapkan nilai waktu yang dipicu untuk konten pipa. Jika data yang disebut secara tidak sinkron kembali dengan cepat, Anda tidak perlu menggunakan bigpipe. Anda dapat secara langsung menghasilkan halaman web dan mengirimkannya. Anda dapat menunggu sampai permintaan data telah melampaui periode waktu tertentu sebelum menggunakan bigpipe. Dibandingkan dengan AJAX, menggunakan BigPipe tidak hanya menyimpan jumlah permintaan dari browser ke server Node.js, tetapi juga menyimpan jumlah permintaan dari server Node.js ke sumber data. Namun, mari kita bagikan metode optimasi dan latihan spesifik setelah Snowball Network menggunakan bigpipe.