当前位置: 首页 > 文档资料 > yog2 官方文档 >

进阶功能

优质
小牛编辑
190浏览
2023-12-01

在 YOG2 下,通过 widget 的划分,可以以 widget 为粒度,以多种模式加载,依靠这种技术我们可以优化大型网站性能或者轻松的实现一个单页应用。

BigPipe

Facebook 的 BigPipe 技术,是通过将站点分解为多个 pagelet 小块,每个pagelet 获取数据与渲染均是独立的,当传统的后端模板渲染模式受限于后端响应速度最慢的接口时,BigPipe 模式可以实现 pagelet 的数据一旦返回,就可以无阻塞的在浏览器端进行渲染,以此来实现大型复杂页面的性能加速。

而在 YOG2 中,实际上一个 pagelet 就对应着一个 widget。我们可以通过简单的改造就实现 BigPipe 模式的加载。

首先,我们需要调整一下 widget 的引用写法,值得注意的是,我们并不需要调整 widget 的实现,这也意味着同一个 widget ,既可以使用传统模式加载,也可以使用 BigPipe模式加载。

其次我们还需要引用一个前端脚本来实现 bigpipe 功能,bigpipe.js

我们通过在 widget 标签后添加 mode='async' 标记指定的 widget 使用 BigPipe 模式加载,并且显式的为 widget 添加 id 属性方便控制器获取 widget

<!-- /client/page/index.tpl -->
<!doctype html>
{% html framework="home:static/js/mod.js" %}
    {% head %}
        <title>Hello World</title>
        {% require "home:static/js/bigpipe.js %}
    {% endhead %}
    {% body %}
        {% widget "home:widget/search/search.tpl" mode="async" %}
    {% endbody %}
{% endhtml %}

接着按照我们前文所说,如果使用 BigPipe 模式加载 widget ,widget 需要拥有独立的数据获取方式。那么我们就需要在控制器中通过 res.bigpipe.bind 函数为 locationSearch 这个组件绑定其独立的数据获取方式。

var lbsModel = require("../models/lbs.js");

module.exports.get = function (req, res, next) {
    res.bigpipe.bind('locationSearch', function () {
        return lbsModel.search(req.query.name, req.query.region);
    });
    res.render('home/page/index.tpl');
}

在经过 BigPipe改造后,再次请求时,查看源代码我们就会发现原先 locationSearch 组件是以 HTML 的形式返回的,而在 BigPipe 模式下,则是从脚本中通过 onPageletArrive 函数返回。如果使用 fiddler 等抓包工具查看的话,还可以发现页面框架是在第一时间返回,而 locationSearch 的内容则是在后端请求响应后以 chunk 的形式输出至页面。

关于 BigPipe 的更多原理介绍,可以参考 Facebook网站的Ajax化、缓存和流水线

需要注意的是并不是所有场景都适合使用BigPipe,只有当一个页面需要向多个系统请求数据,并且后端系统无法提供一致的返回时间保证时,使用BigPipe才会有较大的性能提升。

Quickling

除了 BigPipe 模式外,我们还可以将 pagelet 用于 Quickling 模式。所谓 Quickling 模式是将 widget 整体通过 Ajax 请求返回。也就是将传统的 Ajax 请求数据,前端模板渲染数据的模式变化为 Ajax 请求渲染好的页面以及 widget 执行的必要代码和样式。这两种方式并非互相取代的关系,而是应该根据使用场景灵活判断。

这个脚本的功能就是在前端发起一个 widget 的 Quickling 请求。

首先我们需要将页面中 widget 的加载模式由 async 调整为 quickling

<!-- /client/page/index.tpl -->
<!doctype html>
{% html framework="home:static/js/mod.js" %}
    {% head %}
        <title>Hello World</title>
        {% require "home:static/js/bigpipe.js %}
    {% endhead %}
    {% body %}
        {% widget "home:widget/search/search.tpl" mode="quickling" %}
    {% endbody %}
{% endhtml %}

这样修改后,页面首次加载时并不会加载 locationSearch ,需要前端代码中通过调用 bigpipe.js 加载。

BigPipe.load('locationSearch');

更多 BigPipe 的接口可以查看 文档

Quickling 的嵌套使用

Quickling 支持嵌套调用,嵌套调用是指

在页面 index.tpl 中我们引用了 PageletA

// index.tpl
{% widget "home:widget/pagelets/A.tpl" mode="async" %}

同时在 A.tpl 中,我们又引用了 PageletB

// A.tpl
{% widget "home:widget/pagelets/B.tpl" mode="async" %}

此时当我们在前端使用 BigPipe.load 加载 PageletA 时,会合并返回 PageletA 与 PageletB 的内容。

如果希望在加载 PageletA 时不返回 PageletB 的内容,则可以为 B 的引用添加 lazy 属性

// A.tpl
{% widget "home:widget/pagelets/B.tpl" mode="async" lazy="true" %}

如果我们希望在前端仅更新 PageletB 的内容,则可以在前端调用时使用 @ 语法声明 B 所在的父 Pagelet 的 ID

BigPipe.load('B@A')

内联式Pagelet

当我们为了实现页面局部 Pagelet 化时,通常都会需要将局部内容抽离为一个独立的 widget 来实现异步加载等功能,为了简化实现局部 Pagelet 化的复杂度,YOG2 在yog2-kernel的1.2.0版本起,开始提供了一种新的模板标签pagelet来实现内联式的 Pagelet。其使用方式与现有的 Widget 十分类似。

<!-- /client/page/index.tpl -->
<!doctype html>
{% html framework="home:static/js/mod.js" %}
    {% head %}
        <title>Hello World</title>
        {% require "home:static/js/bigpipe.js %}
    {% endhead %}
    {% body %}
        {% pagelet mode="async"%}
            // 此处为 home:widget/search/search.tpl 的内容
            <h5>查询结果</h5>
            <ul>
            {% for poi in results %}
                <li>{{poi.name}}</li>
            {% endfor %}
            </ul>
        {% endpagelet %}
        {% widget "home:widget/search/search.tpl" mode="quickling" %}
    {% endbody %}
{% endhtml %}
var lbsModel = require("../models/lbs.js");

module.exports.get = function (req, res, next) {
    res.bigpipe.bind('locationSearch', function () {
        return lbsModel.search(req.query.name, req.query.region)
    });
    res.render('home/page/index.tpl');
}

其中被 {% pagelet mode="async"%} 包裹的 Pagelet 部分就会使用 BigPipe 技术进行加载,并且可以通过前端触发 Quickling 请求刷新局部内容。