BigPipe es una tecnología desarrollada por Facebook para optimizar la velocidad de carga de la página web. Casi no hay artículos implementados con Node.js en Internet. De hecho, no es solo Node.js. Las implementaciones de BigPipe en otros idiomas son raras en Internet. Mientras mucho tiempo después de que apareciera esta tecnología, pensé que después de que todo el marco de la página web se envió primero, utilicé otras o varias solicitudes AJAX para solicitar los módulos en la página. No fue hasta hace mucho tiempo que aprendí que el concepto central de BigPipe es usar solo una solicitud HTTP, pero los elementos de la página se envían en orden.
Será fácil entender este concepto central. Gracias a la característica asíncrona de Node.js, es fácil implementar BigPipe con Node.js. Este artículo utilizará ejemplos paso a paso para ilustrar las causas de la tecnología BigPipe y una implementación simple basada en Node.js.
Usaré Express para demostrar. Para simplificar, elegimos a Jade como motor de plantilla, y no utilizamos la función Sub-Plplate (parcial) del motor, sino que usamos la plantilla infantil para representar HTML como datos de plantilla principal.
Primero cree una carpeta NodeJS-BigPipe y escriba un archivo Packle.json de la siguiente manera:
La copia del código es la siguiente:
{
"Nombre": "BigPipe-Experiment"
, "Versión": "0.1.0"
, "privado": verdadero
, "Dependencias": {
"Express": "3.xx"
, "Consolidar": "Último"
, "Jade": "Último"
}
}
Ejecute la instalación de NPM para instalar estas tres bibliotecas. Consolidate se utiliza para facilitar la llamada de Jade.
Hagamos primero el intento más simple, dos archivos:
App.js:
La copia del código es la siguiente:
var express = require ('express')
, contras = require ('consolidar')
, jade = require ('jade')
, ruta = requerir ('ruta')
VAR App = Express ()
App.Engine ('Jade', Conv.Jade)
App.set ('Vistas', Path.Join (__ Dirname, 'Vistas')))
App.set ('Ver motor', 'Jade')
app.use (function (req, res) {
res.render ('Layout', {
S1: "Hola, soy la primera sección".
, S2: "Hola, soy la segunda sección".
})
})
App.listen (3000)
vistas/diseño.jade
La copia del código es la siguiente:
doctype html
cabeza
¡Título Hola, mundo!
estilo
sección {
Margen: 20px Auto;
borde: 1px gris punteado;
Ancho: 80%;
Altura: 150px;
}
Sección#S1! = S1
Sección#S2! = S2
Los efectos son los siguientes:
A continuación, colocamos dos plantillas de sección en dos archivos de plantilla diferentes:
Vistas/S1.Jade:
La copia del código es la siguiente:
H1 parcial 1
.Content! = Contenido
Vistas/S2.Jade:
La copia del código es la siguiente:
H1 parcial 2
.Content! = Contenido
Agregue algunos estilos al diseño.
La copia del código es la siguiente:
Sección H1 {
tamaño de fuente: 1.5;
relleno: 10px 20px;
margen: 0;
Border-Bottom: 1px Grey Grey;
}
sección div {
margen: 10px;
}
Cambie la parte App.use () de App.js a:
La copia del código es la siguiente:
var temp = {
S1: jade.compile (fs.readfilesync (path.join (__ dirname, 'vistas', 's1.jade')))
, S2: jade.compile (fs.readfilesync (path.join (__ dirname, 'vistas', 's2.jade')))
}
app.use (function (req, res) {
res.render ('Layout', {
S1: temp.s1 ({contenido: "Hola, soy la primera sección"})
, S2: temp.s2 ({contenido: "Hola, soy la segunda sección"})
})
})
Dijimos antes, "el HTML después de la representación se completa con la plantilla infantil como datos de la plantilla principal", lo que significa que los dos métodos temp.s1 y temp.s2 generarán código HTML para los dos archivos S1.Jade y S2.Jade, y luego usar estas dos piezas de código como los valores de las dos variables S1 y S2 en el diseño. Jade.
Ahora la página se ve así:
En términos generales, los datos de las dos secciones se obtienen por separado, ya sea consultando la base de datos o la solicitud RESTful, utilizamos dos funciones para simular como operaciones asíncronas.
La copia del código es la siguiente:
var getData = {
d1: function (fn) {
setTimeout (fn, 3000, nulo, {contenido: "Hola, soy la primera sección"})
}
, d2: function (fn) {
setTimeOut (fn, 5000, nulo, {contenido: "Hola, soy la segunda sección"})
}
}
De esta manera, la lógica en App.use () será más complicada, y la forma más sencilla de lidiar con ella es:
La copia del código es la siguiente:
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)
})
})
})
})
Esto también obtendrá los resultados que queremos, pero en este caso, tomará 8 segundos completos para regresar.
De hecho, la lógica de implementación muestra que GetData.d2 comienza a llamar después de que se devuelve el resultado de GetData.d1, y no tienen tal dependencia. Podemos usar bibliotecas como Async que maneja las llamadas asíncronas de JavaScript para resolver este problema, pero simplemente escribamos aquí:
La copia del código es la siguiente:
app.use (function (req, res) {
var n = 2
, resultado = {}
getData.d1 (function (err, s1data) {
resultado.s1data = s1data
--n || WritereSult ()
})
getData.d2 (function (err, s2data) {
resultado.s2data = s2data
--n || WritereSult ()
})
function writerSult () {
res.render ('Layout', {
S1: temp.s1 (resultado.s1data)
, S2: temp.s2 (resultado.s2data)
})
}
})
Esto toma solo 5 segundos.
Antes de la próxima optimización, agregamos la biblioteca jQuery y colocamos el estilo CSS en archivos externos. Por cierto, agregaremos el archivo Runtime.js necesario para usar la plantilla Jade que usaremos más adelante, y lo ejecutaremos en el directorio que contiene App.js:
La copia del código es la siguiente:
mkdir estático
CD estático
curl http://code.jquery.com/jquery-1.8.3.min.js -o jQuery.js
ln -s ../node_modules/jade/runtime.min.js jade.js
Y saque el código en la etiqueta de estilo en el diseño.
La copia del código es la siguiente:
cabeza
¡Título Hola, mundo!
enlace (href = "/static/style.css", rel = "Stylesheet")
script (src = "/static/jquery.js")
script (src = "/static/jade.js")
En App.js, simulamos ambas velocidades de descarga a dos segundos, y agregamos antes de App.use (function (req, res) {:
La copia del código es la siguiente:
var static = express.static (rath.Join (__ dirname, 'static'))
app.use ('/static', function (req, res, next) {
SetTimeout (Static, 2000, Req, Res, Next)
})
Debido a archivos estáticos externos, nuestra página ahora tiene un tiempo de carga de aproximadamente 7 segundos.
Si devolvemos la parte del cabecera tan pronto como recibimos una solicitud HTTP, y luego dos secciones esperan hasta que se complete la operación asincrónica antes de regresar, esto utiliza el mecanismo de codificación de transmisión bloqueado de HTTP. En Node.js, siempre que use el método res.write (), el encabezado de codificación de transferencia: se agregará automáticamente. De esta manera, mientras el navegador carga el archivo estático, el servidor de nodos está esperando el resultado de la llamada asíncrona. Primero eliminemos las dos líneas de la sección en diseño. Jade:
La copia del código es la siguiente:
Sección#S1! = S1
Sección#S2! = S2
Por lo tanto, no necesitamos dar este objeto en res.render () {S1: ..., S2: ...}, y porque res.render () llamará a Res.end () de forma predeterminada, necesitamos establecer manualmente la función de devolución de llamada después de que se complete el renderizado, y usar el método res.write () en él. El contenido de LEYOUT.JADE no necesita estar en la función de devolución de llamada WritereSult (). Podemos devolverlo cuando recibamos esta solicitud. Tenga en cuenta que agregamos manualmente el encabezado de tipo de contenido:
La copia del código es la siguiente:
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 ()
})
})
Ahora la velocidad de carga final ha vuelto a unos 5 segundos. En la operación real, el navegador primero recibe el código de pieza principal y carga tres archivos estáticos. Esto toma dos segundos. Luego, en el tercer segundo, aparece parcial 1, parcial 2 aparece en el quinto segundo, y la carga de la página web termina. No daré una captura de pantalla, el efecto de captura de pantalla es el mismo que las capturas de pantalla en los 5 segundos anteriores.
Sin embargo, es importante tener en cuenta que este efecto se puede lograr porque getData.d1 es más rápido que getData.d2. Es decir, qué bloque en la página web se devuelve primero depende de quién devuelve el resultado de la llamada asíncrona de la interfaz detrás de ella. Si cambiamos getData.d1 para que regrese en 8 segundos, primero devolveremos parcial 2. El orden de S1 y S2 se invierte, y el resultado final de la página web es inconsistente con nuestras expectativas.
Este problema finalmente nos lleva a BigPipe, que es una tecnología que puede desacoplarse el orden de visualización de cada parte de la página web desde el orden de transmisión de datos.
La idea básica es transmitir primero el marco general de toda la página web, y las partes que deben transmitirse más tarde están representadas por divs vacíos (u otras etiquetas):
La copia del código es la siguiente:
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"> </sect> <section id = "s2"> </section>'))
})
Luego escriba los datos devueltos en JavaScript
La copia del código es la siguiente:
getData.d1 (function (err, s1data) {
res.write ('<script> $ ("#s1"). html ("' + temp.s1 (s1data) .replace (/"/g, '// "') + '") </script>'))
--n || res.end ()
})
El procesamiento de S2 es similar al de esto. En este momento, verá que en el segundo de solicitar la página web, aparecen dos cuadros punteados en blanco, en el quinto segundo, aparece 2 parcial, y en el octavo segundo aparece parcial 1, y la solicitud de la página web se completa.
En este punto, hemos completado la página web más simple implementada por BigPipe Technology.
Cabe señalar que si el fragmento de la página web a escribir tiene etiquetas de script, como cambiar S1.Jade a:
La copia del código es la siguiente:
H1 parcial 1
.Content! = Contenido
guion
Alerta ("Alerta de S1.Jade")
Luego, actualice la página web y encontrará que la oración de alerta no se ejecuta, y la página web tendrá errores. Verifique el código fuente y sepa que es un error causado por la cadena en <Script>. Simplemente reemplácelo con <// script>
La copia del código es la siguiente:
res.write ('<script> $ ("#S1"). Html ("' + temp.s1 (s1data) .replace (/"/g, '//"').replace(/</script>/g,' <// script> ') +' ") </script> ')
Arriba explicamos los principios de BigPipe y el método básico para implementar BigPipe con Node.js. ¿Y cómo debe usarse en la realidad? Aquí hay un método simple para tirar ladrillos y jade, el código es el siguiente:
La copia del código es la siguiente:
var reproto = require ('express/lib/respuesta')
resproto.pipe = function (selector, html, reemplazar) {
this.write ('<script>' + '$ ("' + selector + '").' +
(reemplazar === verdadero? 'reemplazar': 'html') +
'("' + html.replace (/"/g, '//"').replace(/<//script>/g,' <// script> ') +
'") </script>'))
}
function pipename (res, nombre) {
res.pipecount = res.pipecount || 0
res.pipEmap = res.pipEmap || {}
if (res.pipemap [nombre]) return
res.pipecount ++
res.pipEmap [name] = this.id = ['pipe', math.random (). toString (). sustring (2), (nueva fecha ()). valueOf ()]. Join ('_')
this.res = res
this.name = nombre
}
resproto.pipename = function (nombre) {
Devuelve el nuevo Pipename (este, nombre)
}
resproto.pipelayout = function (ver, opciones) {
var res = esto
Object.Keys (Opciones) .ForEach (function (Key) {
if (options [key] instanceOf Pipename) Opciones [clave] = '<span id = "' + opciones [key] .id + '"> </span>'
})
res.render (ver, opciones, funciones (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 (nombre, vista, opciones) {
var res = esto
res.render (ver, opciones, funciones (err, str) {
if (err) return res.req.next (err)
res.pipe ('#'+res.pipEmap [nombre], str, verdadero)
--res.pipecount || res.end ()
})
}
app.get ('/', function (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)
})
})
También agregue dos secciones en el diseño. Jade:
La copia del código es la siguiente:
Sección#S1! = S1
Sección#S2! = S2
La idea aquí es que el contenido de la tubería debe colocarse primero con una etiqueta de tramo, obtener los datos de forma asincrónica y representar el código HTML correspondiente antes de emitirlo al navegador, y reemplace el elemento de la capacidad del marcador de posición con el método de reemplazo de jQuery con el método.
El código de este artículo está en https://github.com/undozen/bigpipe-on-node. He hecho cada paso en un compromiso. Espero que puedas ejecutarlo localmente y hackearlo. Debido a que los siguientes pasos implican el orden de carga, realmente tiene que abrir el navegador usted mismo para experimentarlo y no puede verlo desde la captura de pantalla (en realidad, debe implementarse con animación GIF, pero era demasiado vago para hacerlo).
Todavía hay mucho espacio para la optimización sobre la práctica de BigPipe. Por ejemplo, es mejor establecer un valor de tiempo activado para el contenido de la tubería. Si los datos llamados devuelven asíncronamente rápidamente, no necesita usar BigPipe. Puede generar directamente una página web y enviarla. Puede esperar hasta que la solicitud de datos haya excedido un cierto período de tiempo antes de usar BigPipe. En comparación con AJAX, el uso de BigPipe no solo guarda el número de solicitudes del navegador al servidor Node.js, sino que también guarda el número de solicitudes del servidor Node.js a la fuente de datos. Sin embargo, compartimos los métodos específicos de optimización y práctica después de que Snowball Network usa BigPipe.