yog-bigpipe
Advanced tools
Comparing version 0.0.7 to 0.0.8
var Pagelet = require('./pagelet.js'); | ||
var status = Pagelet.status; | ||
var mode = Pagelet.mode; | ||
var mixin = require('./util.js').mixin; | ||
var Readable = require('stream').Readable; | ||
var util = require('util'); | ||
function mixin(a, b) { | ||
if (a && b) { | ||
for (var key in b) { | ||
a[key] = b[key]; | ||
} | ||
} | ||
return a; | ||
} | ||
var BigPipe = module.exports = function BigPipe(options) { | ||
this.options = mixin({}, BigPipe.options, options); | ||
this.quicklings = []; | ||
this.options = mixin(mixin({}, BigPipe.options), options); | ||
this.pagelets = []; | ||
@@ -22,5 +15,8 @@ this.pipelines = []; | ||
this.state = status.pending; | ||
this.isquicking = false; | ||
this.quicklings = []; | ||
Readable.call(this, null); | ||
}; | ||
// default options | ||
BigPipe.options = { | ||
@@ -35,2 +31,11 @@ | ||
util.inherits(BigPipe, Readable); | ||
// don't call this directly. | ||
BigPipe.prototype._read = function(n) { | ||
if (this.state === status.pending) { | ||
this.render(); | ||
} | ||
}; | ||
// bind pagelet data source. | ||
@@ -43,2 +48,4 @@ // accept fucntion and model name. | ||
// 当为 quickling 模式的时候调用。 | ||
// 设置后,只有在此列表中的 pagelet 才会渲染。 | ||
BigPipe.prototype.addQuicklingPagelets = function(pagelets) { | ||
@@ -49,2 +56,3 @@ var arr = this.quicklings; | ||
// 判断是否是 quickling 模式。 | ||
BigPipe.prototype.isQuickingMode = function() { | ||
@@ -54,4 +62,6 @@ return !!(this.quicklings && this.quicklings.length); | ||
// 添加 pagelet. | ||
BigPipe.prototype.addPagelet = function(obj) { | ||
// 已经晚了了,不处理。 | ||
if (this.state === status.fulfilled) { | ||
@@ -61,3 +71,3 @@ return false; | ||
// todo 如果 quickling 的 widget 藏在某些异步 widget 里面,岂不是找不到? | ||
// 注意: 如果 quickling 的 widget 藏在某些异步 widget 里面,岂不是找不到? | ||
if (!this.isQuickingMode() && obj.mode !== mode.pipeline && obj.mode !== mode.async) { | ||
@@ -75,41 +85,54 @@ | ||
var pagelet = new Pagelet(obj); | ||
var sources = this.sources; | ||
var source = sources[pagelet.id]; | ||
if (!source && sources.all) { | ||
source = sources.all.bind(null, pagelet.id); | ||
} | ||
source && pagelet.setSource(source); | ||
var self = this; | ||
this.pagelets.push(pagelet); | ||
pagelet.mode === mode.pipeline && this.pipelines.push(pagelet); | ||
pagelet.once('ready', this._onPageletReady.bind(this)); | ||
pagelet.once('done', this._onPageletDone.bind(this)); | ||
pagelet.on('before-render', function() { | ||
var args = [].slice.call(arguments); | ||
return self.emit.apply(self, ['before-pagelet-render'].concat(args)); | ||
}); | ||
pagelet.on('after-render', function() { | ||
var args = [].slice.call(arguments); | ||
return self.emit.apply(self, ['after-pagelet-render'].concat(args)); | ||
}); | ||
if (this.state === status.rendering) { | ||
pagelet.render(); | ||
this.renderPagelet(pagelet); | ||
} | ||
}; | ||
BigPipe.prototype.render = function(stream, cb) { | ||
BigPipe.prototype.render = function() { | ||
var pagelets = this.pagelets.concat(); | ||
var sources = this.sources; | ||
var self = this; | ||
this.stream = stream; | ||
this.cb = cb; | ||
this.state = status.rendering; | ||
if (!pagelets.length) { | ||
process.nextTick(cb); | ||
pagelets.forEach(this.renderPagelet.bind(this)); | ||
this._checkFinish(); | ||
}; | ||
BigPipe.prototype.renderPagelet = function(pagelet) { | ||
var sources = this.sources; | ||
var source = sources[pagelet.id]; | ||
if (!source && typeof sources.all === 'function') { | ||
source = sources.all.bind(null, pagelet.id); | ||
} | ||
pagelets.forEach(function(pagelet) { | ||
pagelet.render(); | ||
// hook | ||
this.emit('prepare-pagelet-source', pagelet.id, function(_source) { | ||
source = _source; | ||
}); | ||
pagelet.start(source); | ||
}; | ||
BigPipe.prototype.destroy = function() { | ||
this.stream = this.cb = this.sources = null; | ||
this.sources = null; | ||
this.removeAllListeners(); | ||
this.rendered.concat(this.pagelets).forEach(function(pagelet) { | ||
@@ -122,6 +145,6 @@ pagelet.destroy(); | ||
BigPipe.prototype._onPageletReady = function(pagelet) { | ||
BigPipe.prototype._onPageletDone = function(pagelet) { | ||
var cb, idx, item; | ||
if (pagelet.mode ===mode.pipeline) { | ||
if (pagelet.mode === mode.pipeline) { | ||
// 必须按顺序 | ||
@@ -150,8 +173,5 @@ // idx = this.pipelines.indexOf(pagelet); | ||
if (!this.pagelets.length && this.state === status.rendering) { | ||
cb = this.cb; | ||
// 标记已经完成。 | ||
this.state = status.fulfilled; | ||
cb(null, ''); | ||
cb = this.cb = null; | ||
this.push(null); | ||
} | ||
@@ -162,3 +182,3 @@ }; | ||
var content = this.format(pagelet); | ||
content && this.stream.write(content); | ||
content && this.push(content); | ||
this._markPageletRendered(pagelet); | ||
@@ -165,0 +185,0 @@ }; |
var util = require("util"); | ||
var EventEmitter = require("events").EventEmitter; | ||
var _ = require('./util.js'); | ||
function mixin(a, b) { | ||
if (a && b) { | ||
for (var key in b) { | ||
a[key] = b[key]; | ||
} | ||
} | ||
return a; | ||
} | ||
function ucfirst(str) { | ||
return str.substring(0, 1).toUpperCase() + str.substring(1); | ||
} | ||
var Pagelet = module.exports = function Pagelet(obj) { | ||
this.model = obj.model; | ||
this.container = obj['for']; | ||
this.container = obj['container'] || obj['for']; | ||
this.mode = obj.mode; | ||
@@ -24,9 +12,9 @@ this.id = obj.id; | ||
this.compiled = obj.compiled; | ||
this.state = status.pending; | ||
this.scripts = []; | ||
this.styles = []; | ||
this.js = []; | ||
this.css = []; | ||
this.styles = []; | ||
this.html = ''; | ||
// this.errorMsg = ''; | ||
}; | ||
@@ -36,2 +24,8 @@ | ||
// Status Spec. | ||
// | ||
// - `pending` just initialized. | ||
// - `rendering` start rendered but not yet finished. | ||
// - `fulfilled` render finish. | ||
// - `failed` render failed. | ||
var status = Pagelet.status = { | ||
@@ -44,36 +38,58 @@ pending: 'pending', | ||
// Mode Spec | ||
// | ||
// - `async` 忽略顺序,谁先准备好,谁先输出 | ||
// - `pipeline` 按顺序输出,以入库的顺序为准。 | ||
// - `quickling` 此类 pagelet 并不是 chunk 输出,而是第二次请求的时候输出。 | ||
Pagelet.mode = { | ||
async: 'async', | ||
pipeline: 'pipeline', | ||
async: 'async', | ||
quickling: 'quickling' | ||
}; | ||
Pagelet.prototype.setSource = function(source) { | ||
this.source = source; | ||
}; | ||
Pagelet.prototype.render = function() { | ||
var fn = this.source; | ||
/** | ||
* 开始渲染 pagelet, 可以指定一个 provider 即数据准备回调。 | ||
* | ||
* 回调中,第一个参数为状态 error,第二个参数才为数据 data。 | ||
* @example | ||
* | ||
* ```javascript | ||
* pagelet.start(function(done) { | ||
* var model = new RemoteModel(); | ||
* | ||
* model.fetch(function(err, val) { | ||
* done(err, val); | ||
* }); | ||
* }); | ||
* ``` | ||
* | ||
* @method start | ||
* @param {Function} provider 数据提供者 | ||
* @return {Undefined} | ||
*/ | ||
Pagelet.prototype.start = function(provider) { | ||
var self = this; | ||
var eventname; | ||
var self = this; | ||
if (!fn && (eventname = 'onPagelet' + ucfirst(this.id)) && | ||
typeof this.locals[eventname] === 'function' ) { | ||
if (this.state !== status.pending) { | ||
return this.emit('error', new Error('Alreay rendered.')); | ||
} | ||
fn = this.locals[eventname]; | ||
// mark the state. | ||
this.state = status.rendering; | ||
if (!provider && (eventname = 'onPagelet' + _.ucfirst(this.id)) && | ||
typeof this.locals[eventname] === 'function') { | ||
provider = this.locals[eventname]; | ||
} | ||
if (!fn && typeof this.locals['onPagelet'] === 'function') { | ||
fn = this.locals['onPagelet'].bind(null, this.id); | ||
if (!provider && typeof this.locals['onPagelet'] === 'function') { | ||
provider = this.locals['onPagelet'].bind(null, this.id); | ||
} | ||
this.state = status.rendering; | ||
// todo 支持 pagelet.model 自动关联。 | ||
if (fn) { | ||
fn(function(err, val) { | ||
if (provider) { | ||
provider(function(err, val) { | ||
if (err) { | ||
// todo | ||
// self.state = status.failed; | ||
// self.errorMsg = err; | ||
self.emit('error', err); | ||
return; | ||
@@ -89,4 +105,5 @@ } | ||
// don't call this directly. | ||
Pagelet.prototype._render = function(model) { | ||
var locals = this.locals; | ||
var locals = this.locals || {}; | ||
var fn = this.compiled; | ||
@@ -96,62 +113,100 @@ var self = this; | ||
model && (mixin(locals, model), locals.model = model); | ||
model && (_.mixin(locals, model), locals.model = model); | ||
this._yog = locals._yog = locals._yog.fork(); | ||
// hook sub pagelet render. | ||
// make that render after this pagelet render. | ||
var origin = locals._yog.addPagelet; | ||
var subpagelets = []; | ||
self.onReady = function() { | ||
subpagelets.forEach(function(args) { | ||
origin.apply(locals._yog, args); | ||
}); | ||
locals._yog.addPagelet = origin; | ||
locals = self = origin = null; | ||
}; | ||
locals._yog.addPagelet = function() { | ||
subpagelets.push(arguments); | ||
}; | ||
self.emit('before-render', self, locals, fn); | ||
output = fn(locals); | ||
this.analyse(output); | ||
self.emit('after-render', self, locals, fn); | ||
this._analyse(output); | ||
this.state = status.fulfilled; | ||
this.emit('done', this); | ||
}; | ||
Pagelet.prototype.analyse = function(output) { | ||
var yog = this._yog; | ||
Pagelet.prototype._analyse = function(output) { | ||
var scripts = this.scripts; | ||
var styles = this.styles; | ||
var js = this.js; | ||
var css = this.css; | ||
var styles = this.styles; | ||
var yogScripts = yog.getScripts(); | ||
var yogStyles = yog.getStyles(); | ||
var p; | ||
// 收集js, css, html | ||
if(yog.getResourceMap()) { | ||
scripts.push('require.resourceMap(' + JSON.stringify(this.getResourceMap()) + ');'); | ||
} | ||
this.html = output | ||
// 收集内联或者外链 js | ||
.replace(/<script([^>]*?)>([\s\S]+?)<\/script>/ig, function(_, attr, content) { | ||
var m = /src=('|")(.*?)\1/i.exec(attr); | ||
styles.push.apply(styles, yogStyles.embed); | ||
if (m) { | ||
js.push(m[2]); | ||
} else { | ||
scripts.push(content); | ||
} | ||
output = output.replace(/<script[^>]*?>([\s\S]+?)<\/script>/ig, function(_, content) { | ||
scripts.push(content); | ||
return ''; | ||
}).replace(/<style[^>]*?>([\s\S]+?)<\/style>/ig, function(_, content) { | ||
styles.push(content); | ||
return ''; | ||
}); | ||
scripts.push.apply(scripts, yogScripts.embed); | ||
yogScripts.urls && (js = this.js = yogScripts.urls.concat()); | ||
yogStyles.urls && (css = this.css = yogStyles.urls.concat()); | ||
return ''; | ||
}) | ||
this.html = output; | ||
this.state = status.fulfilled; | ||
this.emit('ready', this); | ||
this.onReady && this.onReady(); | ||
// 收集内联样式 | ||
.replace(/<style[^>]*?>([\s\S]+?)<\/style>/ig, function(_, content) { | ||
styles.push(content); | ||
return ''; | ||
}) | ||
// 收集外链样式。 | ||
.replace(/<link(.*?)\/>/ig, function(_, attr) { | ||
var m = /href=('|")(.*?)\1/i.exec(attr); | ||
if (m) { | ||
css.push(m[2]); | ||
} | ||
return _; | ||
}); | ||
}; | ||
/** | ||
* 添加内联样式。 | ||
* @method addStyle | ||
* @param {String | Array of String} style 样式内容 | ||
*/ | ||
Pagelet.prototype.addStyle = Pagelet.prototype.addStyles = function(style) { | ||
style = Array.isArray(style) ? style : [style]; | ||
this.styles.push.apply(this.styles, style); | ||
}; | ||
/** | ||
* 添加内联脚本。 | ||
* @method addScript | ||
* @param {String | Array of String} script 脚本内容。 | ||
*/ | ||
Pagelet.prototype.addScript = Pagelet.prototype.addScripts = function(script) { | ||
script = Array.isArray(script) ? script : [script]; | ||
this.scripts.push.apply(this.scripts, script); | ||
}; | ||
/** | ||
* 添加脚本。 | ||
* @method addJs | ||
* @param {String | Array of String} js 脚本地址。 | ||
*/ | ||
Pagelet.prototype.addJs = Pagelet.prototype.addJses = function(js) { | ||
js = Array.isArray(js) ? js : [js]; | ||
this.js.push.apply(this.js, js); | ||
}; | ||
/** | ||
* 添加样式。 | ||
* @method addCss | ||
* @param {String | Array of String} js 样式地址。 | ||
*/ | ||
Pagelet.prototype.addCss = Pagelet.prototype.addCsses = function(css) { | ||
css = Array.isArray(css) ? css : [css]; | ||
this.css.push.apply(this.css, css); | ||
}; | ||
/** | ||
* 获取 pagelet 渲染后的信息。 | ||
* @method toJson | ||
* @return {Object} | ||
*/ | ||
Pagelet.prototype.toJson = function() { | ||
@@ -167,4 +222,9 @@ return { | ||
}; | ||
} | ||
}; | ||
/** | ||
* 销毁。 | ||
* @destroy | ||
*/ | ||
Pagelet.prototype.destroy = function() { | ||
@@ -175,2 +235,2 @@ this.destroyed = true; | ||
this.scripts = this.js = this.css = this.styles = null; | ||
} | ||
}; |
{ | ||
"name": "yog-bigpipe", | ||
"version": "0.0.7", | ||
"version": "0.0.8", | ||
"description": "An express.js middleware for fis widget pipline output.", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
"test": "mocha --reporter dot --check-leaks test/", | ||
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", | ||
"test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" | ||
}, | ||
@@ -23,3 +25,10 @@ "repository": { | ||
}, | ||
"homepage": "https://github.com/fex-team/yog-bigpipe" | ||
"homepage": "https://github.com/fex-team/yog-bigpipe", | ||
"devDependencies": { | ||
"mocha": "~1.20.1", | ||
"istanbul": "~0.2.14", | ||
"express": "~4.4.5", | ||
"supertest": "~0.13.0", | ||
"should": "~4.0.4" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
25172
9
656
1
5