Maison  >  Article  >  interface Web  >  Node.js implémente BigPipe dans detail_node.js

Node.js implémente BigPipe dans detail_node.js

WBOY
WBOYoriginal
2016-05-16 16:29:031557parcourir

BigPipe est une technologie développée par Facebook pour optimiser la vitesse de chargement des pages Web. Il n'y a presque aucun article implémenté à l'aide de node.js sur Internet. En fait, non seulement node.js, les implémentations de BigPipe dans d'autres langages sont rares sur Internet. Bien après l'apparition de cette technologie, j'ai pensé qu'après l'envoi de l'intégralité du cadre de la page Web, une ou plusieurs requêtes ajax étaient utilisées pour demander les modules de la page. Il n'y a pas si longtemps, j'ai appris que le concept de base de BigPipe était d'utiliser une seule requête HTTP, mais que les éléments de la page étaient envoyés dans le désordre.

Ce sera plus facile une fois que vous aurez compris ce concept de base. Grâce aux fonctionnalités asynchrones de node.js, il est facile d'implémenter BigPipe avec node.js. Cet article utilisera des exemples étape par étape pour expliquer l'origine de la technologie BigPipe et une implémentation simple basée sur node.js.

J'utiliserai express pour démontrer. Pour plus de simplicité, nous choisissons jade comme moteur de modèle, et nous n'utilisons pas la fonctionnalité de sous-modèle (partiel) du moteur, mais nous utilisons le HTML une fois le sous-modèle créé. rendus en tant que données du modèle parent.

Créez d'abord un dossier nodejs-bigpipe et écrivez un fichier package.json comme suit :

Copier le code Le code est le suivant :

{
"name": "expérience-bigpipe"
, "version": "0.1.0"
, "privé" : vrai
, "dépendances": {
"express": "3.x.x"
, "consolidate": "dernier"
, "jade": "dernière"
>
>

Exécutez npm install pour installer ces trois bibliothèques, consolidate est utilisé pour faciliter l'appel de jade.

Essayons d'abord le plus simple, deux fichiers :

app.js :

Copier le code Le code est le suivant :

var express = require('express')
, contre = require('consolidate')
, jade = require('jade')
, chemin = require('chemin')

var app = express()

app.engine('jade', cons.jade)
app.set('views', path.join(__dirname, 'views'))
app.set('moteur d'affichage', 'jade')

app.use(function (req, res) {
res.render('layout', {
s1 : "Bonjour, je suis la première section."
, s2 : "Bonjour, je suis la deuxième section."
})
})

app.listen(3000)

views/layout.jade

Copier le code Le code est le suivant :

doctype html

tête
titre Bonjour tout le monde !
style
Rubrique {
        marge : 20 px automatique ;
Bordure : 1px gris pointillé ;
       largeur : 80 % ;
Hauteur : 150 px ;
>

section#s1!=s1
section#s2!=s2

L'effet est le suivant :

Ensuite, nous mettons les deux modèles de section dans deux fichiers de modèles différents :

views/s1.jade :

Copier le code Le code est le suivant :

h1 Partiel 1
.content!=content

views/s2.jade :

Copier le code Le code est le suivant :

h1 Partiel 2
.content!=content

Ajoutez quelques styles au style de layout.jade

Copier le code Le code est le suivant :

rubrique h1 {
taille de police : 1,5 ;
rembourrage : 10px 20px ;
marge : 0;
border-bottom : 1px gris pointillé ;
>
section div {
marge : 10px ;
>

Changez la partie app.use() de app.js par :

Copier le code Le code est le suivant :

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 (fonction (req, res) {
res.render('layout', {
s1 : temp.s1({ content : "Bonjour, je suis la première section." })
, s2 : temp.s2({ content : "Bonjour, je suis la deuxième section." })
})
})

Avant de dire "utiliser le HTML une fois le sous-modèle rendu comme données du modèle parent", c'est ce que cela signifie. Les deux méthodes temp.s1 et temp.s2 généreront deux fichiers, s1.jade et. s2.jade. Code HTML, puis utilisez ces deux morceaux de code comme valeurs des deux variables s1 et s2 dans layout.jade.

La page ressemble désormais à ceci :

De manière générale, les données des deux sections sont obtenues séparément - que ce soit par interrogation de la base de données ou par requête RESTful, nous utilisons deux fonctions pour simuler de telles opérations asynchrones.

Copier le code Le code est le suivant :

var getData = {
d1 : fonction (fn) {
​ ​ ​ setTimeout(fn, 3000, null, { content: "Bonjour, je suis la première section." })
>
, d2 : fonction (fn) {
​ ​ ​ setTimeout(fn, 5000, null, { content: "Bonjour, je suis la deuxième section." })
>
>

De cette façon, la logique dans app.use() sera plus compliquée. La façon la plus simple de la gérer est :

Copier le code Le code est le suivant :

app.use (fonction (req, res) {
getData.d1(function (err, s1data) {
GetData.d2(fonction (erreur, s2data) {
res.render('layout', {
          s1 : temp.s1(s1data)
, , s2 : temp.s2(s2data)
})
})
})
})

Cela peut également obtenir le résultat souhaité, mais dans ce cas, il faudra 8 secondes complètes pour revenir.

En fait, la logique d'implémentation montre que getData.d2 n'est appelé qu'après le retour du résultat de getData.d1, et il n'y a pas de telle dépendance entre les deux. Nous pouvons utiliser des bibliothèques telles que async qui gèrent les appels asynchrones JavaScript pour résoudre ce problème, mais écrivons-le simplement à la main ici :

Copier le code Le code est le suivant :

app.use (fonction (req, res) {
var n = 2
, résultat = {}
getData.d1(function (err, s1data) {
​ résultat.s1data = s1data
--n || écrireRésultat()
})
getData.d2(function (err, s2data) {
​ résultat.s2data = s2data
--n || écrireRésultat()
})
fonction writeResult() {
res.render('layout', {
        s1 : temp.s1(result.s1data)
, s2 : temp.s2(result.s2data)
})
>
})

Cela ne prendra que 5 secondes.

Avant la prochaine optimisation, nous avons ajouté la bibliothèque jquery et mis le style css dans un fichier externe. D'ailleurs, nous avons également ajouté le fichier runtime.js nécessaire pour utiliser le modèle jade côté navigateur que nous utiliserons plus tard. . Exécutez dans le répertoire contenant app.js :

.

Copier le code Le code est le suivant :

mkdir statique
cd statique
curl http://code.jquery.com/jquery-1.8.3.min.js -o jquery.js
ln -s ../node_modules/jade/runtime.min.js jade.js

Et retirez le code dans la balise style dans layout.jade et mettez-le dans static/style.css, puis remplacez la balise head par :

Copier le code Le code est le suivant :

tête
titre Bonjour tout le monde !
lien(href="/static/style.css", rel="stylesheet")
script(src="/static/jquery.js")
script(src="/static/jade.js")

Dans app.js, nous simulons la vitesse de téléchargement des deux à deux secondes, et ajoutons :
avant app.use(function (req, res) {

Copier le code Le code est le suivant :

var static = express.static(path.join(__dirname, 'static'))
app.use('/static', fonction (req, res, next) {
setTimeout(static, 2000, req, res, next)
})

Notre page se charge désormais en 7 secondes environ en raison de fichiers statiques externes.

Si nous renvoyons la partie head dès que nous recevons la requête HTTP, puis que les deux sections attendent que l'opération asynchrone soit terminée avant de revenir, cela utilise le mécanisme de codage de transfert HTTP chunked. Dans node.js, tant que vous utilisez la méthode res.write(), l'en-tête Transfer-Encoding: chunked sera automatiquement ajouté. De cette façon, pendant que le navigateur charge le fichier statique, le serveur de nœud attend le résultat de l'appel asynchrone Supprimons d'abord ces deux lignes dans layout.jade :
.

Copier le code Le code est le suivant :

section#s1!=s1
section#s2!=s2

Nous n'avons donc pas besoin de donner l'objet { s1 : …, s2 : … } dans res.render(), et comme res.render() appellera res.end() par défaut, nous devons le faire manuellement définir le rendu une fois terminé La fonction de rappel utilise la méthode res.write(). Le contenu de layout.jade n'a pas besoin d'être dans la fonction de rappel writeResult(). Nous pouvons revenir lors de la réception de cette requête. Notez que nous avons ajouté manuellement l'en-tête content-type :

.

Copier le code Le code est le suivant :

app.use (fonction (req, res) {
res.render('layout', function (err, str) {
Si (err) renvoie 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('
' temp.s1(s1data) '
')
--n || res.end()
})
getData.d2(function (err, s2data) {
res.write('
' temp.s2(s2data) '
')
--n || res.end()
})
})

La vitesse de chargement finale est désormais revenue à environ 5 secondes. En fonctionnement réel, le navigateur reçoit d'abord la partie principale du code, puis charge trois fichiers statiques, ce qui prend deux secondes. Puis dans la troisième seconde, la partie Partial 1 apparaît, et la partie Partial 2 apparaît dans la 5ème seconde. et la page Web est chargée. Je ne prendrai pas de capture d'écran, l'effet de la capture d'écran est le même que celui de la capture d'écran précédente de 5 secondes.

Mais veuillez noter que cet effet peut être obtenu car getData.d1 est plus rapide que getData.d2. En d'autres termes, le bloc de la page Web qui est renvoyé en premier dépend du résultat de l'appel asynchrone de l'interface derrière laquelle revient en premier. Si nous mettons getData. Si d1 est modifié en 8 secondes pour revenir, Partial 2 sera renvoyé en premier et l'ordre de s1 et s2 sera inversé, le résultat final de la page Web ne correspondra pas à nos attentes.

Cette question nous amène finalement à BigPipe. BigPipe est une technologie qui permet de découpler l'ordre d'affichage de chaque partie de la page Web de l'ordre de transmission des données.

L'idée de base est de transférer d'abord le cadre général de la page Web entière, et les parties qui doivent être transférées ultérieurement sont représentées par des divs vides (ou d'autres balises) :

Copier le code Le code est le suivant :

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('
')
})

Ensuite, écrivez les données renvoyées en utilisant JavaScript

Copier le code Le code est le suivant :

getData.d1(function (err, s1data) {
res.write('<script>$("#s1").html("' temp.s1(s1data).replace(/"/g, '\"') '")</script>')
--n || res.end()
})

s2 est géré de la même manière. À ce moment-là, vous verrez que dans la deuxième seconde de demande de la page Web, deux cases vides en pointillés apparaissent, dans la cinquième seconde, la partie Partielle 2 apparaît, dans la huitième seconde, la partie Partielle 1 apparaît et la page Web. la demande est complétée.

À ce stade, nous avons terminé une page Web implémentée avec la technologie BigPipe la plus simple.

Il convient de noter que le fragment de page Web à écrire possède une balise de script. Par exemple, remplacez s1.jade par :

Copier le code Le code est le suivant :

h1 Partiel 1
.content!=content
scénario
alert("alerte de s1.jade")

Actualisez ensuite la page Web et vous constaterez que l'alerte n'est pas exécutée et qu'il y aura des erreurs sur la page Web. Vérifiez le code source et sachez que l'erreur est causée par l'apparition de dans la chaîne à l'intérieur de <script>. <br></p> <div class="codetitle"><span><a style="CURSOR: pointer" data="48323" class="copybut" id="copybut48323" onclick="doCopy('code48323')">Copier le code<u></u></a> Le code est le suivant :</span></div> <div class="codebody" id="code48323"> res.write('<script>$("#s1").html("' temp.s1(s1data).replace(/"/g, '\"').replace(/</script>/ g, '<
\/script>') '")')

Ci-dessus, nous avons expliqué le principe de BigPipe et la méthode de base d'implémentation de BigPipe à l'aide de node.js. Et comment doit-il être utilisé en pratique ? Une méthode simple est fournie ci-dessous, juste à titre de référence. Le code est le suivant :

Copier le code Le code est le suivant :

var resProto = require('express/lib/response')
resProto.pipe = fonction (sélecteur, html, remplacer) {
  this.write('<script>' '$("' sélecteur '").' <br>     (replace === true ? 'replaceWith' : 'html') <br>     '("' html.replace(/"/g, '\"').replace(/</script>/g, '<\/script>')
    '")')
>
function PipeName (res, nom) {
  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()].join('_')
  this.res = res
  this.name = nom
>
resProto.pipeName = fonction (nom) {
  renvoyer un nouveau PipeName(this, name)
>
resProto.pipeLayout = fonction (vue, options) {
  var res = ceci
  Object.keys(options).forEach(function (key) {
    if (options[key] instanceof PipeName) options[key] = ''
  })
  res.render(vue, options, fonction (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 = fonction (nom, vue, options) {
  var res = ceci
  res.render(vue, options, fonction (err, str) {
    if (err) return res.req.next(err)
    res.pipe('#' res.pipeMap[name], str, true)
    --res.pipeCount || res.end()
  })
>
app.get('/', fonction (req, res) {
  res.pipeLayout('layout', {
      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)
  })
})

还要在 layout.jade 把两个 section 添加回来:

复制代码 代码如下 :

section#s1!=s1
section#s2!=s2

这里的思路是,需要 pipe 的内容先用一个 span 标签占位,异步获取数据并渲染完成相应的 HTML 代码后Il s'agit d'un jQuery et d'un replaceWith . 。

本文的代码在 https://github.com/undozen/bigpipe-on-node ,我把每一步做成一个 commit 了,希望你 clone 到本地实际运行并 hack 一下看看. (其实应该可以用 gif 动画实现,但是我懒得做了)。

关于 BigPipe 的实践还有很大的优化空间,比如说,要 pipe 的内容最好设置一个触发的时间值,如果异Il s'agit de BigPipe. Il s'agit de BigPipe et de BigPipe ajax et de node.js. Il s'agit d'une application node.js basée sur node.js.优化和实践方法,等到雪球网用上 BigPipe 以后再分享吧。

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Article précédent:Explication détaillée des compétences de méthode_javascript d'analyse d'URL JavascriptArticle suivant:Explication détaillée des compétences de méthode_javascript d'analyse d'URL Javascript

Articles Liés

Voir plus