agate
agate copied to clipboard
BigPipe与安卓布局标签
bigpipe需要对后端查询进行规划
根据查询SQL对页面进行划分
然后pipe出一个只有少有脚本与样式的空页面(脚本是bigpipe的前端处理脚本, 样式是为了不显示太零乱)
然后每查到一份数据, 就将对应的版块(官话称之为pagelets), 及调整pagelets 位置的脚本pipe到前端
这个 pipe是通过 报头Transfer-Encoding: chunked
实现的, 在nodejs里面就是一个简单的res.write
调整pagelets 位置的脚本
也是bigpipe实现的核心, 我们现在是零乱盲目地将一个个DIV乱放到body上,需要在前端用JS将它们重新放置,哪个是位置哪个前面,哪个内部,每个bigpipe都有一段脚本,当浏览器解析HTML页面时,自然会调用这些脚本,当解析完毕,页面就会还原成设计师预期的外观。
因此bigpipe与我所说的安卓布局标签
技术应该是一对好兄弟,可以无缝地结合起来。
最后是res.end()
传统的BigPipe(可诸于facebook与新浪微博),它们是将pagelet放到一个script标签内,HTML全部序列化为 一个很大的字符串,然后再插入页面,这对SEO不友好。
我的改进建议是, 标签直接放在页面是
<!--- pagelet112 -->
<div>
</div>
<table>
</table>
<script id=paglet112>
adjustPageletPositon()
</script>
var pagelets = []
function getPagelet(id){
var el = document.getElementById(id) //script#id为一个bigpipe的结束标记
var fragment = document.createDocumentFragment()
var array = [el]
var comment
while(comment = el.previousSibling){
array.unshift(comment)
//<!--id--> 注释节点为一个bigpipe的开始标记
if(comment.nodeType === 8 && comment.nodeValue === id){
break
}
}
while(el = array.shift()){
fragment.appendChild(el)
}
return fragment
}
function adjustPageletPositon(){
for(var i = 0, el; el = pagelets[i++];){
}
}
下面是一个较复杂的页面,结构上分为7块,但视觉上分 5个区域
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>bigpipe</title>
<script type="text/javascript" src='avalon.js'></script>
<style type="text/css">
*{
margin: 0;
padding: 0;
}
html, body{
width:100%;
height:100%;
}
body {
position: relative;
}
#header{
height:40px;
background: red;
}
#middle{
height: calc(100% - 40px - 40px);
height:-moz-calc(100% - 40px - 40px);
background: #c00
}
#left{
display: inline-block;
width:200px;
height:100%;
background:#a9ea00;
}
#right{
position: absolute;
top: 40px;
right:0px;
width:200px;
height: calc(100% - 40px - 40px);
height:-moz-calc(100% - 40px - 40px);
background:darkturquoise;
}
#center{
display: inline-block;
width: calc(100% - 400px );
width:-moz-calc(100% - 400px );
height: 100%;
background:darkkhaki;
}
#footer{
height:40px;
width:100%;
position: absolute;
z-index:10;
bottom:0px;
background:yellow;
}
#inner{
background: blue;
height: 300px;
}
</style>
</head>
<body>
<div id="header">
1
</div>
<div id="middle">
<div id="left">
3
</div>
<div id="center">
<div id="inner">5这里垂直居中还没有实现</div>
</div>
<div id="right">
6
</div>
</div>
<div id="footer">
7
</div>
</body>
</html>
http://www.cubrid.org/blog/dev-platform/faster-web-page-loading-with-facebook-bigpipe/
使用安卓布局标签 实现如下:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>bigpipe</title>
<script type="text/javascript" src='avalon.js'></script>
<style type="text/css">
*{
margin: 0;
padding: 0;
}
html, body{
width:100%;
height:100%;
}
</style>
</head>
<body>
<LinearLayout style="orientation:vertical;">
<Layout style="weight: 0;height:40px;background: red"></Layout>
<LinearLayout style="orientation:horizontal;">
<Layout style="weight: 0;width:200px;background:#a9ea00"></Layout>
<Layout style="layout_gravity:center;background:darkkhaki">
<Layout style="height:300px;background:blue"></Layout>
</Layout>
<Layout style="weight: 0;width:200px;background:darkturquoise"></Layout>
</linearlayout>
<Layout style="weight: 0;height:40px;background: red"></Layout>
</linearlayout>
</body>
</html>
http://stackoverflow.com/questions/3482742/gravity-and-layout-gravity-on-android http://liangruijun.blog.51cto.com/3061169/632532
如何定义两个pagelet的位置
-
beforeBegin: 插入到标签开始前
-
afterBegin:插入到标签开始标记之后
-
beforeEnd:插入到标签结束标记前
-
afterEnd:插入到标签结束标记后
这个比较麻烦,可以尝并jquery的replace, prepend, append, before, after插入体系
最后决定使用pin了
https://www.npmjs.com/package/koa-write
使用Stream实现的bigpipe
var http = require("http");
http.createServer(function (request, response) {
response.writeHead(200, {"Content-Type": "text/html"});
var Readable = require("stream").Readable;
var stream = this.body = new Readable();
stream._read = function () {
}
stream.pipe(response)
stream.push('First line. ' + Date.now() + "<br/>");
setTimeout(function () {
stream.push('second line. ' + Date.now() + "<br/>");
}, 1000)
// yield delay(2000);
setTimeout(function () {
stream.push('Last line. ' + Date.now());
stream.push(null);
console.log("done");
}, 2000)
// response.write("Hello node.js");
// response.end();
}).listen(8888);
//
var koa = require('koa');
var Readable = require('stream').Readable
var app = koa();
app.use(function *(){
this.type = 'text/html'
var stream = this.body = new Readable()
stream._read = function () {}
// isn't piped
console.log(stream._readableState.pipes != null)
stream.push("==================" + '<br>');
yield function (callback) { setTimeout(callback, 2000) }
stream.push("------------" + '<br>');
// not yet
console.log(stream._readableState.pipes != null)
var i = 0
setTimeout(function f(){
if (i == 0)
// now it is
console.log(stream._readableState.pipes != null)
stream.push(Date() + '<br>');
if (++i < 10)
setTimeout(f, 50)
else
stream.push(null)
}, 200)
});
app.listen(3000);
var http = require("http");
var fs = require("fs");
function heredoc(fn) {
return fn.toString().replace(/^[^\/]+\/\*!?\s?/, '').replace(/\*\/[^\/]+$/, '')
}
function format(str, object) {
var array = Array.prototype.slice.call(arguments, 1);
return str.replace(/\\?\#{([^{}]+)\}/gm, function (match, name) {
if (match.charAt(0) == '\\')
return match.slice(1);
var index = Number(name)
if (index >= 0)
return array[index];
if (object && object[name] !== void 0)
return object[name];
return '';
});
}
function BigPipe(n) {
this.n = n
var Readable = require("stream").Readable
var stream = new Readable()
stream._read = function () {
}
this.stream = stream
}
function ClientBigPipe() {
var ids = []
function getPagelet(node) {
var fragment = document.createDocumentFragment()
var id = node.id
var nodes = [node]
while (node = node.previousSibling) {
nodes.unshift(node)
if (node.nodeType === 8 && trim(node.nodeValue) === id) {
break
}
}
for (var i = 0; node = nodes[i++]; ) {
fragment.appendChild(node)
}
fragment.removeChild(fragment.firstChild)
fragment.removeChild(fragment.lastChild)
return fragment
}
function trim(str) {
return str.replace(/^\s+|\s+$/g, '');
}
return {
pin: function (id) {
ids.push(id)
for (var i = ids.length, cur; cur = ids[--i]; ) {
var placehold = document.getElementById("_" + cur)
if (placehold) {
var pin = document.getElementById(cur)
var pagelet = getPagelet(pin)
placehold.parentNode.replaceChild(pagelet, placehold)
ids.splice(i, 1)
}
}
}
}
}
BigPipe.prototype.begin = function (str) {
var id = "_id" + Date.now()
var chunk = format(heredoc(function () {/*
#{str}
<script id="#{id}">window.BigPipe = new #{bigpipe};
(function(){
var el = document.getElementById("#{id}")
el.parentNode.removeChild(el)
})();
</script>
*/
}), {id: id, str: str, bigpipe: ClientBigPipe.toString()})
this.stream.push(chunk)
}
BigPipe.prototype.end = function (str) {
this.tail = str
}
BigPipe.prototype.finish = function () {
this.stream.push(this.tail)
this.stream.push(null)
}
BigPipe.prototype.push = function (str, id) {
id = "pagelet-" + id
var chunk = format(heredoc(function () {/*
<!--#{id}-->#{str}
<script id="#{id}">BigPipe.pin("#{id}")</script>
*/
}), {id: id, str: str})
this.stream.push(chunk)
if (--this.n === 0) {
this.finish()
}
return id
}
http.createServer(function (request, response) {
var b = new BigPipe(3)
response.writeHead(200, {"Content-Type": "text/html"});
b.stream.pipe(response)
b.begin(fs.readFileSync("./start.html", "utf8"))
b.end(fs.readFileSync("./end.html", "utf8"))
setTimeout(function () {
b.push('<p>这个先插入</p>', 1);
}, 1000)
setTimeout(function () {
b.push('<p>这个后插入<span id="_pagelet-2"></span></p>', 0);
}, 2000)
setTimeout(function () {
b.push('<strong>这是插在中间的</strong>', 2);
}, 1500)
}).listen(8888);
这是模板,里面有一些占位符
<!DOCTYPE html>
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<h1>这是最前面</h1>
<div id="_pagelet-0"></div>
<div id="_pagelet-1"></div>
</body>
</html>
koa+bigpipe
html
exports.index = function * (next) {
var Readable = require("stream").Readable
var stream = new Readable()
stream._read = function () {
}
this.type = "html"
this.body = stream
stream.push('<html><head><body>');
stream.push("<p>第一行</p>")
stream.push('</body></html>');//浏览器会自动修正
setTimeout(function () {
stream.push("<p>第二行" + Date.now() + "</p>")
setTimeout(function () {
stream.push("<p>第三行" + Date.now() + "</p>")
setTimeout(function () {
stream.push("<p>第4行" + Date.now() + "</p>")
stream.push(null)
}, 1000)
}, 1000)
}, 1000)
}
现在的浏览器,显示网页时需要经历连续的几个步骤,分别是请求网页 -> 服务器端的页面生成 -> 返回全部内容 -> 浏览器渲染,在这一过程中,“服务器的页面生成”到“返回全部内容”阶段,浏览器什么也不做,大部分浏览器就直接显示空白。可想而知,如果页面庞大,那么等待的时间就很长,这很可能导致大量的用户丢失。Facebook 提出的 BigPipe 技术就是为了解决这个问题,它是基于多线程实现,原理大致可以分为以下两点。 将一个页面分为多个的 PageLet,每个的 PageLet 实际上就是一个 HTML 片段,每个 PageLet 的页面内容由单独的线程生成与处理。 由于使用了多线程,PageLet 内容的返回顺序无法确定,因此如果将内容直接写回 HTML 文档内,它的位置是无法确定的,因此需要借助 JavaScript 将内容插入到正确的位置,因为脚本代码的位置无关紧要。 实现了以上两点,最终的效果将是网页中首先出现网页结构和基本的、简单的信息,然后才会在网页的各个 PageLet 位置出现具体内容,这些 PageLet 没有按流模型从上到下从左到右出现,而是“并行出现”,加载页面速度加快。从以上的分析,这种技术至少有两种好处。 首先出现的结构和基本信息,告诉用户页面正在加载,是有希望的。 并行加载的机制使得某个 PageLet 的缓慢不会影响到别的 PageLet 的加载。 所有的 PageLet 在同一个 HTTP 请求内处理。
https://github.com/peerigon/phridge