Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

bigkoa

Package Overview
Dependencies
Maintainers
3
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

bigkoa

> BigKoa = BigView + Koa

  • 0.1.3
  • npm
  • Socket score

Version published
Weekly downloads
1
Maintainers
3
Weekly downloads
 
Created
Source

BigKoa (Node.js 8.x +)

BigKoa = BigView + Koa

Build Status codecov.io js-standard-style

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

特性

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

功能点

  • 支持静态布局和动态布局
  • 支持5种bigpipe渲染模式
    • parallel.js 并行模式, 先写布局,并行请求,但在获得所有请求的结果后再渲染
    • pipeline.js (默认) 管线模式:即并行模式, 先写布局,并行请求,并即时渲染
    • reduce.js 顺序模式: 先写布局,按照pagelet加入顺序,依次执行,写入
    • reducerender.js 先写布局,然后顺序执行,在获得所有请求的结果后再渲染
    • render.js 一次渲染模式:即普通模式,不写入布局,所有pagelet执行完成,一次写入到浏览器。支持搜索引擎,用来支持那些不支持JS的客户端。
  • 支持子pagelet,无限级嵌套
  • 支持根据条件渲染模板,延时输出布局
  • bigview支持错误模块显示,仅限于布局之前

生命周期

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

Scaffold

Install

$ npm i -g bigview-cli

Usages

$ bpm a b c
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

安装

$ npm i -S bigview

mode 1 并行渲染

const MyBigView = require('./MyBigView')

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.start()
});

mode 2 支持嵌套子布局

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

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

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

  pagelet1.addChild(pagelet2)

  bigpipe.add(pagelet1)

  bigpipe.start()
});

a) 静态布局

views/nest/index.html是bp的布局文件

<!doctype html>
<html class="no-js">
<head>
    <title><%= title %></title>
    <link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body>
    <div id="pagelet2" class="pagelet2">load,,,,</div>

    <ul>
    <% pagelets.forEach(function(p){ %>
        <li><%= p.name %> | <%= p.selector %>
        <% if (p.children.length) { %>  
            <ul>  
            <% p.children.forEach(function(sub){ %>  
                <li> subPagelet = <%= sub.name %> | <%= sub.selector %>
            <% }) %>  
            </ul>  
        <% } %>
    <% }) %>
    </ul>

    <% pagelets.forEach(function(p){ %>
       <div id="<%= p.location %>" class="<%= p.selector %>">loading...<%= p.name %>...</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('pagelet2',function(data){
            $("#pagelet2").html(data);
        })
    
    </script>
</body>
</html>

遍历pagelets来生成各种页面需要的即可。

b) 延时输出布局

views/nest2/index.html是bp的布局文件

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

    <% pagelets.forEach(function(p){ %>
       <div id="<%= p.location %>" class="<%= p.selector %>">loading...<%= p.name %>...</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);
        })
        <% }) %>
    </script>
</body>
</html>

此时无任何pagelet2的布局

在bpmodules/nest2/p1/p1.html里,输出pagelet2的布局。

  <script>bigpipe.set("pagelet1", '<%= is %>');</script>

  <div id="pagelet2" class="pagelet2">load,,,,</div>

  <script>
      bigpipe.ready('pagelet2',function(data){
          $("#pagelet2").html(data);
      })
  </script>

mode 3:根据条件渲染模板

自定义p1和p2,设置this.immediately = false

'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.selector = 'pagelet1'
      this.location = 'pagelet1'
      this.tpl = 'p1.html'
      this.delay = 4000
      this.immediately = false
  }

  fetch () {
		let pagelet = this
		return require('./req')(pagelet)
	}
}

自定义BigView基类

'use strict'

const BigView = require('../../../packages/bigview')

module.exports = class MyBigView extends BigView {
  before () {
    let self = this
     return new Promise(function(resolve, reject) {
       self.showPagelet = self.query.a
       resolve(true)
    })
  }

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

只需要重写fetch方法,并且返回Promise对象即可。如果想多个,就利用Promise的链式写法解决即可

http支持

无论http也好,还是其他方式(rpc)也好,都是需要参数的

  • bigview为单页应用入口
  • bigview只定义布局,以及各个pagelet位置(当然也可以在before里完成http请求)
  • bigview入口是express路由,可以获取querystring
  • bigview里包含多个pagelet
  • pagelet里需要发起接口请求,获取数据后,想浏览器写html片段

所以要做的,依然是上面的fetch方法,由于pagelet是独立的,所以无法直接获取bigview页面的参数。

但是pagelet里有一个owner对象,其实就是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, 'basic/index', { title: "测试" })

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

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

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

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

很明显这就是一个express中间件。

app.get('/', require('./bpmodules/basic'));

所以获取QueryString就很简单了,从req.query里获得就可以了。然后赋值给bigpipe对象。

实际上,bigview已经做了这件事,它自身已经绑定了3个获取参数的属性

  • this.query // ?a=1&b=2
  • this.params // /a/:id this.params.id
  • this.body 仅限于POST等类型的请求,估计用的不会很多

所以在req.js里可以这样使用

  • pagelet.owner.query
  • pagelet.owner.params
  • pagelet.owner.body

例子如下

'use strict'

module.exports = function (pagelet) {
    console.log(pagelet.owner.query)
    
    pagelet.delay = 1000
    return new Promise(function(resolve, reject){
      setTimeout(function() {
        // self.owner.end()
        resolve(pagelet.data)
      }, pagelet.delay)
    }) 
}

定义实现render方法,支持更多模板引擎

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

    render (tpl, data) {
        const ejs = require('ejs')
        let self = this

        return new Promise(function(resolve, reject){
            ejs.renderFile(tpl, data, self.options, function(err, str){
                // str => Rendered HTML string
                if (err) {
                    console.log(err)
                    reject(err)
                }
                
                resolve(str)
            })
        })
    }
}

重写render()方法,如果不重写则采用默认的模板引擎ejs编译。

render方法的参数

  • tpl,即pagelet对应的模板
  • data,是pagelet对应的模板编译时需要的数据

模块目录的思考

.
├── MyBigView.js(实现类,继承自bigview)
├── index.js (返回MyBigView以及p1和p2等pagelet模块的组织)
├── p1(pagelet模块)
│   ├── index.js
│   ├── p1.html
│   └── req.js
└── p2(pagelet模块)
    ├── index.js
    ├── p2.html
    └── req.js

pagelet模块

pagelet的本章是返回模板引擎编译后的html片段。

模板引擎编译(模板 + 数据) = html

唯一比较麻烦的是数据的来源,可能是静态数据,也可能是api请求的数据,所以在设计pagelet的时候,通过集成fetch方法来实现自定义数据。为了进一步

├── p1(pagelet模块)
│   ├── index.js
│   ├── p1.html
│   └── req.js

说明

  • index.js (实现类,继承自biglet)可以完成各种配置
  • p1.html 是模板
  • req.js 是获取api数据的,提供给模板引擎data的请求文件,返回Promise对象

支持mock数据

bigpipe

  bigpipe.isMock = true
  bigpipe.previewFile = 'aaaa.html'

pagelet带定

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

pagelet1.mock = true

pagelet1.data = {
  xxx: yyy
} 

pagelet1.test()

or

$ pt bpmoduless/p1 url
$ pt bpmoduless/p1 aaaa.json

自动跑测试,并给出测试结果

More

pagelet能复用么?

直接请求,也未尝不可

  • pagelet独立模块复用
  • http方式调用pagelet(需要深入思考一下)

性能改进

  • req.js,有http改成rpc
  • 缓存模板
  • 缓存编译结果

与传统Ajax比较

  • 减少HTTP请求数:多个模块更新合成一个请求
  • 请求数减少:多个chunk合成一个请求
  • 减少开发成本:前端无需多写JavaScript代码
  • 降低管理成本:模块更新由后端程序控制
  • URL优雅降级:页面链接使用真实地址
  • 代码一致性:页面加载不劢态刷新模块代码相同

前端优化,参考微博的方式

异步加载显示模块的方式:BigPipe方式降低模块开发成本、管理成本

var FM=function(a,b,c){function bN(b,c){a.clear&&(bN=a.clear)(b,c)}function bM(b,c,d){a.start&&(bM=a.start)(b,c,d)}function bL(a){return a===null?"":Object.prototype.toString.call(a).slice(8,-1).toLowerCase()}function bK(){bv(function(){bH();for(var a in J){if(I[a]){bB(P,I[a]);delete 


<script>FM.view({"ns":"pl.common.webim","domid":"pl_common_webim","css":["style/css/module/list/comb_webim.css?version=f25a07f3fbb17183"],"js":["webim_prime/js/common/all.js?version=8fde40d2c1ecd58b"]})</script>
<script>FM.view({"ns":"pl.top.index","domid":"plc_top","css":[],"js":["home/js/pl/top/index.js?version=8fde40d2c1ecd58b"],"html":""})</script>

<script>
pl.trigger('pagelet1',{
  css: ['pl1.css'],
  js: ['pl1.js'],
  data: {},
  html:'<span>Here is pagelet1</span>'
});
</script>
  • 同一个pagelet可能触发多次

模块的css可以采用各种预处理编写,在提供bigpipe打包功能,合并到一起或者单独引入(可以再考虑)。

  • 目前是模板引擎里嵌入js和css
  • 显示的方式,应该可以完成更多优化功能,对模板化更好
  • 优化开发方式

4种模式

  • sync 默认就是此模式,直接输出。
  • quicking 此类 widget 在输出时,只会输出个壳子,内容由用户自行决定通过 js,另起请求完成填充,包括静态资源加载。
  • async 此类 widget 在输出时,也只会输出个壳子,但是内容在 body 输出完后,chunk 输出 js 自动填充。widget 将忽略顺序,谁先准备好,谁先输出。
  • pipeline 与 async 基本相同,只是它会严格按顺序输出。

BigPipe的三种模式:

  • 一次渲染模式:即普通模式,支持搜索引擎,用来支持那些不支持JS的客户端。
  • 管线模式:即并行模式,并行请求,并即时渲染。(已实现)
  • 并行模式:并行请求,但在获得所有请求的结果后再渲染。

参考

http://velocity.oreilly.com.cn/2011/ppts/WK_velocity.pdf

review

  • 测试独立

  • render统一(?)

  • 初始化参数

  • fetch()就够用了,不必before(精简生命周期,fetch后增加parse)

  • layout:先返回布局(压测)(ok)

  • out模式:同步

  • 日志

  • 开关

  • 性能

  • 共享内存

  • 约定,所有的数据,只能绑定到data上,改成parse

  • pagelet(传model)

  • 继承自event,外接日志(基本实现)

  • 静态布局和动态布局说明

  • bigview指定模板demo

  • pagelet指定外部模板demo

  • ng proxy给个头compress,expire-buffer

  • 分享

  • 封装前端bigpipe库

TODO

  • 增加事件
  • 支持bigview向浏览器写入方法
  • 支持error模块指定
  • 支持条件

感谢

  • 冯地木
  • 张凯
  • 张代应
  • 陈愉镔

Timeout

主流程bigview上5秒超时,biglet里fetch、parse等操作默认1秒超时,如果不满足,请设置timeout(单位是毫秒)

模块是否渲染

当获取数据后,某些模块不必要展示的时候,打破biglet的渲染promise链即可

biglet

  • before (reject)
  • fetch (reject)
  • parse (reject)
  • render

移除了show属性,没有意义

FAQs

Package last updated on 25 Jan 2018

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc