agate icon indicating copy to clipboard operation
agate copied to clipboard

BigPipe与安卓布局标签

Open RubyLouvre opened this issue 9 years ago • 8 comments

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>

image http://www.cubrid.org/blog/dev-platform/faster-web-page-loading-with-facebook-bigpipe/

RubyLouvre avatar Apr 24 '15 03:04 RubyLouvre

使用安卓布局标签 实现如下:

<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

RubyLouvre avatar Apr 24 '15 03:04 RubyLouvre

如何定义两个pagelet的位置

  1. beforeBegin: 插入到标签开始前
    
  2. afterBegin:插入到标签开始标记之后
    
  3. beforeEnd:插入到标签结束标记前
    
  4. afterEnd:插入到标签结束标记后
    

这个比较麻烦,可以尝并jquery的replace, prepend, append, before, after插入体系

最后决定使用pin了

RubyLouvre avatar Apr 24 '15 12:04 RubyLouvre

https://www.npmjs.com/package/koa-write

RubyLouvre avatar Apr 28 '15 14:04 RubyLouvre

使用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);  

RubyLouvre avatar Apr 29 '15 03:04 RubyLouvre

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>

RubyLouvre avatar Apr 29 '15 03:04 RubyLouvre

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

RubyLouvre avatar May 04 '15 07:05 RubyLouvre

现在的浏览器,显示网页时需要经历连续的几个步骤,分别是请求网页 -> 服务器端的页面生成 -> 返回全部内容 -> 浏览器渲染,在这一过程中,“服务器的页面生成”到“返回全部内容”阶段,浏览器什么也不做,大部分浏览器就直接显示空白。可想而知,如果页面庞大,那么等待的时间就很长,这很可能导致大量的用户丢失。Facebook 提出的 BigPipe 技术就是为了解决这个问题,它是基于多线程实现,原理大致可以分为以下两点。 将一个页面分为多个的 PageLet,每个的 PageLet 实际上就是一个 HTML 片段,每个 PageLet 的页面内容由单独的线程生成与处理。 由于使用了多线程,PageLet 内容的返回顺序无法确定,因此如果将内容直接写回 HTML 文档内,它的位置是无法确定的,因此需要借助 JavaScript 将内容插入到正确的位置,因为脚本代码的位置无关紧要。 实现了以上两点,最终的效果将是网页中首先出现网页结构和基本的、简单的信息,然后才会在网页的各个 PageLet 位置出现具体内容,这些 PageLet 没有按流模型从上到下从左到右出现,而是“并行出现”,加载页面速度加快。从以上的分析,这种技术至少有两种好处。 首先出现的结构和基本信息,告诉用户页面正在加载,是有希望的。 并行加载的机制使得某个 PageLet 的缓慢不会影响到别的 PageLet 的加载。 所有的 PageLet 在同一个 HTTP 请求内处理。

RubyLouvre avatar May 04 '15 12:05 RubyLouvre

https://github.com/peerigon/phridge

RubyLouvre avatar May 04 '15 13:05 RubyLouvre