Home >Web Front-end >JS Tutorial >Node.js implements BigPipe in detail_node.js
BigPipe is a technology developed by Facebook to optimize web page loading speed. There are almost no articles implemented using node.js on the Internet. In fact, not only node.js, BigPipe implementations in other languages are rare on the Internet. So long after this technology appeared, I thought that after the entire web page frame was sent first, another or several ajax requests were used to request the modules in the page. Until not long ago, I learned that the core concept of BigPipe is to use only one HTTP request, but the page elements are sent out of order.
It will be easier once you understand this core concept. Thanks to the asynchronous features of node.js, it is easy to implement BigPipe with node.js. This article will use examples step by step to explain the origin of BigPipe technology and a simple implementation based on node.js.
I will use express to demonstrate. For simplicity, we choose jade as the template engine, and we do not use the sub-template (partial) feature of the engine. Instead, we use the HTML after the sub-template is rendered as the data of the parent template.
First create a nodejs-bigpipe folder and write a package.json file as follows:
Run npm install to install these three libraries. consolidate is used to facilitate calling jade.
Let’s try the simplest first, two files:
app.js:
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: "Hello, I'm the first section."
, s2: "Hello, I'm the second section."
})
})
app.listen(3000)
views/layout.jade
head
title Hello, World!
style
Section {
margin: 20px auto;
Border: 1px dotted gray;
width: 80%;
Height: 150px;
}
section#s1!=s1
section#s2!=s2
The effect is as follows:
Next we put the two section templates into two different template files:
views/s1.jade:
views/s2.jade:
Add some styles to the style of layout.jade
Change the app.use() part of app.js to:
Before we said "use the HTML after the sub-template is rendered as the data of the parent template", that's what it means. The two methods temp.s1 and temp.s2 will generate two files, s1.jade and s2.jade. HTML code, and then use these two pieces of code as the values of the two variables s1 and s2 in layout.jade.
The page now looks like this:
Generally speaking, the data of the two sections are obtained separately - whether by querying the database or RESTful request, we use two functions to simulate such asynchronous operations.
In this way, the logic in app.use() will be more complicated. The simplest way to deal with it is:
This can also get the result we want, but in this case, it will take a full 8 seconds to return.
In fact, the implementation logic shows that getData.d2 is called only after the result of getData.d1 is returned, and there is no such dependency between the two. We can use libraries such as async that handle JavaScript asynchronous calls to solve this problem, but let’s simply write it by hand here:
This will only take 5 seconds.
Before the next optimization, we added the jquery library and put the css style into an external file. By the way, we also added the runtime.js file needed to use the jade template on the browser side that we will use later. Run in the directory containing app.js:
And take out the code in the style tag in layout.jade and put it into static/style.css, and then change the head tag to:
In app.js, we simulate the download speed of both of them to two seconds, and add:
before app.use(function (req, res) {
Our page now loads in around 7 seconds due to external static files.
If we return the head part as soon as we receive the HTTP request, and then the two sections wait until the asynchronous operation is completed before returning, this is using the HTTP chunked transfer encoding mechanism. In node.js, as long as you use the res.write() method, the Transfer-Encoding: chunked header will be automatically added. In this way, while the browser loads the static file, the node server is waiting for the result of the asynchronous call. Let's first delete these two lines in layout.jade:
So we don’t need to give the object { s1: …, s2: … } in res.render(), and because res.render() will call res.end() by default, we need to manually set render after completion The callback function uses the res.write() method in it. The content of layout.jade does not need to be in the writeResult() callback function. We can return when receiving this request. Note that we manually added the content-type header:
Final loading speed is now back to around 5 seconds. In actual operation, the browser first receives the head part of the code, and then loads three static files, which takes two seconds. Then in the third second, the Partial 1 part appears, and the Partial 2 part appears in the 5th second, and the web page is loaded. I won’t take a screenshot, the screenshot effect is the same as the previous 5 seconds screenshot.
But please note that this effect can be achieved because getData.d1 is faster than getData.d2. In other words, which block in the web page is returned first depends on the asynchronous call result of the interface behind which returns first. If we put getData. If d1 is changed to 8 seconds to return, Partial 2 will be returned first, and the order of s1 and s2 will be reversed. The final result of the web page will not match our expectations.
This question eventually leads us to BigPipe. BigPipe is a technology that can decouple the display order of each part of the web page from the data transmission order.
The basic idea is to first transfer the general frame of the entire web page, and the parts that need to be transferred later are represented by empty divs (or other tags):
Then write the returned data using JavaScript
s2 is handled similarly. At this time, you will see that in the second second of requesting the web page, two blank dotted boxes appear, in the fifth second, the Partial 2 part appears, in the eighth second, the Partial 1 part appears, and the web page request is completed.
At this point, we have completed a web page implemented with the simplest BigPipe technology.
It should be noted that the web page fragment to be written has a script tag. For example, change s1.jade to:
Then refresh the webpage and you will find that the alert is not executed and there will be errors on the webpage. Check the source code and know that the error is caused by appearing in the string inside <script>. Just replace it with </script>
Above we have explained the principle of BigPipe and the basic method of implementing BigPipe using node.js. And how should it be used in practice? A simple method is provided below, just for reference. The code is as follows:
还要在 layout.jade 把两个 section 添加回来:
这里的思路是,需要 pipe 的内容先用一个 span 标签占位,异步获取数据并渲染完成相应的 HTML 代码后再输出给浏览器,用 jQuery 的 replaceWith 方法把占位的 span 元素替换掉。
本文的代码在 https://github.com/undozen/bigpipe-on-node ,我把每一步做成一个 commit 了,希望你 clone 到本地实际运行并 hack 一下看看。因为后面几步涉及到加载顺序了,确实要自己打开浏览器才能体验到而无法从截图上看到(其实应该可以用 gif 动画实现,但是我懒得做了)。
关于 BigPipe 的实践还有很大的优化空间,比如说,要 pipe 的内容最好设置一个触发的时间值,如果异步调用的数据很快返回,就不需要用 BigPipe,直接生成网页送出即可,可以等到数据请求超过一定时间才用 BigPipe。使用 BigPipe 相比 ajax 既节省了浏览器到 node.js 服务器的请求数,又节省了 node.js 服务器到数据源的请求数。不过具体的优化和实践方法,等到雪球网用上 BigPipe 以后再分享吧。