/bigview

Primary LanguageJavaScriptMIT LicenseMIT

BigView (Node.js 4.x +)

Build Status codecov.io js-standard-style

一款开源的的 Node.js Bigpipe 框架。支持 koaexpress

如何使用

koa / Egg.js 为例;

$npm install bigkoa --save 

使用 bigview-cli 脚手架创建模块;

$ npm install -g bigview-cli

进入项目 app 目录;我们创建第一个模块 bphello;我们新建一个文件夹 bphello;然后我们进入该目录输入:

$ bpm a b c

这个时候程序会自动创建三个 pagelet 模块;


generate ~/a/MyPagelet.js
generate ~/a/index.html
generate ~/a/index.js
generate ~/a/req.js
generate ~/b/MyPagelet.js
generate ~/b/index.html
generate ~/b/index.js
generate ~/b/req.js
generate ~/c/MyPagelet.js
generate ~/c/index.html
generate ~/c/index.js
generate ~/c/req.js

接下来我们需要创建具体的 bigview 将三个 pagelet 串起来; 我们在 bp-hello-world 目录下创建 index.js

const BigView = require('bigview')
const a = require('./a')
const b = require('./b')
const c = require('./c')

module.exports = async (ctx, next) => {
  const bigpipe = new BigView(ctx, {
    layout: a,
  })
  // bigpipe.mode = 'render'
  bigpipe.timeout = 5000
  bigpipe.add(b)
  bigpipe.add(c)

  await bigpipe.start()
}

目前 bigview 不提供模板渲染能力,如果需要支持模板渲染,你们需要在 context 中引入 render 方法 比如引入了 nunjucks 我们需要在 app/extend/context.js 加上 render

const nunjucks = require('nunjucks')

...

  render (tpl, data, cb) {
    const env = nunjucks.configure({
      // your template config
    })
    if (/\.nj$/.test(tpl) || /\.html$/.test(tpl)) {
      env.render(tpl, data, (err, html) => {
        err && debug(err)
        cb(err, html)
      })
    } else {
      env.renderString(tpl, data, (err, html) => {
        err && debug(err)
        cb(err, html)
      })
    }
  }

  afterRenderLayout () {
    let self = this

    if (self.showPagelet === '1') {
      self.run('pagelet1')
    } else {
      self.run('pagelet2')
    }

    // console.log('afterRenderLayout')
    return Promise.resolve(true)
  } 

在bigview

'use strict'

const debug = require('debug')('bigview')
const fs = require('fs')
const MyBigView = require('./MyBigView')

module.exports = function (req, res) {
  var bigpipe = new MyBigView(req, res, 'if/index', { title: "条件选择pagelet" })

  bigpipe.add(require('./p1'))
  bigpipe.add(require('./p2'))

  bigpipe.start()
}

出错模块

  • bigview出错,即在所有pagelets渲染之前,显示错误模块,中断其他模块渲染
  • 如果是pagelets里的某一个出错,可以自己根据模板去,模块内的错误就模块自己处理就好了
    var bigpipe = new MyBigView(req, res, 'error/index', { title: "测试" })
    // bigpipe.mode = 'render'
    bigpipe.add(require('./p1'))
    bigpipe.addErrorPagelet(require('./error'))

显示ErrorPagelet,可以在bigview的生命周期,执行子Pagelets之前。reject一个error即可。

比如在afterRenderLayout里,reject

	afterRenderLayout() {
		let self = this
		// console.log('afterRenderLayout')
		return new Promise(function(resolve, reject) {
			setTimeout(function() {
				reject(new Error('xxxxxx'))
				// resolve()
			}, 0)
		})
	}

通过addErrorPagelet设置Error时要显示的模块,如果要包含多个,请使用pagelet子模块。

另外,如果设置了ErrorPagelet,布局的时候可以使用errorPagelet来控制错误显示

<!doctype html>
<html class="no-js">
<head>
    <title><%= title %></title>
    <link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body>
    <div id="<%= errorPagelet.location %>" class="<%= errorPagelet.selector %>">
        <ul>
        <% pagelets.forEach(function(p){ %>
            <li><%= p.name %> | <%= p.selector %>
        <% }) %>
        </ul>

        <% pagelets.forEach(function(p){ %>
        <div id="<%= p.location %>" class="<%= p.selector %>">loading...<%= p.name %>...</div>
        <% }) %>
    </div>

    <script src="/js/jquery.min.js"></script>
    <script src="/js/bigpipe.js"></script>
    <script>
        var bigpipe=new Bigpipe();

        <% pagelets.forEach(function(p){ %>
        
        bigpipe.ready('<%= p.name %>',function(data){
            $("#<%= p.location %>").html(data);
        })
        <% }) %>
        
        bigpipe.ready('<%= errorPagelet.name %>',function(data){
            $("#<%= errorPagelet.location %>").html(data);
        })
    </script>
    <script src="/bigconsole.min.js"></script> 
</body>
</html>

Pagelet里触发其他模块

提供trigger方法,可以触发1个多个多个其他模块,无序并行。结果返回的是Promise

'use strict'

const Pagelet = require('../../../../packages/biglet')
const somePagelet1 = require('./somePagelet1')
const somePagelet2 = require('./somePagelet2')
const somePagelet = require('./somePagelet')

module.exports = class MyPagelet extends Pagelet {
	constructor () {
		super()

		this.root = __dirname
		this.name = 'pagelet1'
	}

	fetch () {
    // 触发一个模块
    this.trigger(new somePagelet())
    // 触发一个模块
    this.trigger([new somePagelet1(), new somePagelet2()])
	}
}

不允许,直接

return this.trigger([require('./somePagelet1'), require('./somePagelet2')])

这样会有缓存,不会根据业务请求来进行不同处理。

也可以强制的fetch里完成

'use strict'

const Pagelet = require('../../../../packages/biglet')
const somePagelet1 = require('./somePagelet1')
const somePagelet2 = require('./somePagelet2')
const somePagelet = require('./somePagelet')

module.exports = class MyPagelet extends Pagelet {
	constructor () {
		super()

		this.root = __dirname
		this.name = 'pagelet1'
	}

	fetch () {
    // 触发多个模块
    return this.trigger([new somePagelet1, new somePagelet2()])
	}
}

生成预览数据

app.get('/', function (req, res) {
  var bigpipe = new MyBigView(req, res, 'basic/index', { title: "测试" })

  var Pagelet1 = require('./bpmodules/basic/p1')
  var pagelet1 = new Pagelet1()

  var Pagelet2 = require('./bpmodules/basic/p2')
  var pagelet2 = new Pagelet2()

  bigpipe.add(pagelet1)
  bigpipe.add(pagelet2)

  // bigpipe.preview('aaaa.html')
  bigpipe.previewFile = 'aaaa.html'
  bigpipe.start()
});

方法

  • 设置previewFile
  • bigpipe.preview('aaaa.html')

获取数据

'use strict'

const Pagelet = require('../../../../packages/biglet')

module.exports = class MyPagelet extends Pagelet {
	constructor () {
		super()

		this.root = __dirname
		this.name = 'pagelet1'
		this.data = { is: "pagelet1测试" }
		this.location = 'pagelet1'
		this.tpl = 'p1.html'
		this.selector = 'pagelet1'
		this.delay = 2000
	}

  fetch () {        
    return new Promise(function(resolve, reject){
      setTimeout(function() {
        // self.owner.end()
        resolve(self.data)
      }, 4000);
    })
  }
}

这个时候 bp-hello-world 创建的差不多了。这个时候我们需要在 app/router.js 中定义路由:

const bpHelloWorld = require('./bp-hello-world')

...

app.router.get('/hello', bpHelloWorld)

这个时候启动程序,然后输入路由后就可以看到我们刚刚完成的一个 bigpipe 页面;

特性

  • 模块化
  • 具有测试性
  • 支持mock数据
  • 生成html片段(便于对比)
  • 提供Scaffold(bigview-cli)
  • 提供调试UI(bigconsole)

目前支持五种渲染模式:

  • pipeline: (默认) 管线模式:即并行模式, 先写布局,并行请求,并即时渲染;
  • parallel: 并行模式, 先写布局,并行请求,但在获得所有请求的结果后再渲染;
  • reduce: 顺序模式: 先写布局,按照pagelet加入顺序,依次执行,写入;
  • reducerender: 先写布局,然后顺序执行,在获得所有请求的结果后再渲染;
  • render: 一次渲染模式:即普通模式,不写入布局,所有pagelet执行完成,一次写入到浏览器。支持搜索引擎,用来支持那些不支持JS的客户端;

关于 bigview 支持的 mode 可以进入 bigview-mode 查看具体文档。

生命周期

bigview的生命周期

  • before
  • .then(this.beforeRenderLayout.bind(this))
  • .then(this.renderLayout.bind(this))
  • .then(this.afterRenderLayout.bind(this))
  • .then(this.beforeRenderPagelets.bind(this))
  • .then(this.renderPagelets.bind(this))
  • .then(this.afterRenderPagelets.bind(this)
  • end

bigview的生命周期精简

  • before
  • renderLayout
  • renderPagelets
  • end

biglet的生命周期

  • before
  • .then(self.fetch.bind(self))
  • .then(self.parse.bind(self))
  • .then(self.render.bind(self))
  • end

Packages

  • bigview NPM version
  • biglet NPM version
  • bigview-cli NPM version
  • bigconsole NPM version
  • bigview-runtime NPM version