ablock
Advanced tools
Comparing version 0.0.1 to 0.0.2
188
index.js
@@ -1,1 +0,187 @@ | ||
module.exports = require('./lib') | ||
var Stream = require('stream') | ||
var cat = require('cat-stream') | ||
var fs = require('fs') | ||
var PassThrough = Stream.PassThrough | ||
var splitBlock = /(\{\{\s*\w+\s*\}\})/ | ||
var matchBlock = /\{\{\s*(\w+)\s*\}\}/ | ||
module.exports = Block | ||
Block.read = function (filename, minify) { | ||
var string = fs.readFileSync(filename, 'utf8') | ||
return new Block(minify ? string.replace(/\n\s*/g, '') : string) | ||
} | ||
/* | ||
blocks = string[] | ||
names = { | ||
key: index[] | ||
} | ||
*/ | ||
function Block(string) { | ||
if (!(this instanceof Block)) | ||
return new Block(string) | ||
var blocks = this.blocks = string.split(splitBlock) | ||
var names = this.names = Object.create(null) | ||
this._locals = Object.create(null) | ||
for (var i = 0, l = blocks.length; i < l; i++) { | ||
var block = blocks[i] | ||
var match = block.match(matchBlock) | ||
var name = match && match[1] | ||
if (name) | ||
(names[name] = names[name] || []).push(i) | ||
} | ||
} | ||
// Permanently writes a local to the block. | ||
// Useful for building blocks. | ||
Block.prototype.local = | ||
Block.prototype.locals = function (name, value) { | ||
// locals({}) support | ||
if (typeof name === 'object') { | ||
Object.keys(name).forEach(function (key) { | ||
this.local(key, name[key]) | ||
}, this) | ||
return this | ||
} else if (typeof name !== 'string') { | ||
throw new Error('Local key must be a string.') | ||
} | ||
var indices = this.names[name] | ||
if (!indices || !indices.length) | ||
throw new Error('The block name "' + name + '" is not defined.') | ||
value = value || '' | ||
if (typeof value === 'function' || typeof value === 'string' || Buffer.isBuffer(value)) | ||
this._locals[name] = value | ||
else if (value instanceof Stream) | ||
throw new TypeError('Streams are not allowed as permanent locals as they can only be consumed once.') | ||
else | ||
throw new TypeError('Value must either be falsey, a string, a buffer, or a function.') | ||
return this | ||
} | ||
Block.prototype.render = function (locals, callback) { | ||
if (typeof locals === 'function') { | ||
callback = locals | ||
locals = {} | ||
} else if (!locals) { | ||
locals = {} | ||
} | ||
extend(locals, this._locals) | ||
var names = this.names | ||
var blocks = this.blocks.slice(0) | ||
// Execute the functions and streams in parallel. | ||
Object.keys(locals).forEach(function (key) { | ||
var indices = names[key] | ||
var value = locals[key] || '' | ||
// In this case, we convert the stream into a function | ||
// so we can replace all the blocks | ||
if (value instanceof Stream && indices.length > 1) { | ||
var stream = value | ||
value = function (done) { | ||
stream.pipe(cat(done)) | ||
} | ||
} | ||
// When its done, we replace all the blocks. | ||
// Otherwise, we replace the blocks with a function | ||
// that callbacks when the function is done. | ||
if (typeof value === 'function') { | ||
var fn = value | ||
value = function (done) { | ||
fn(function (err, string) { | ||
if (err) | ||
return done(err) | ||
string = string || '' | ||
indices.forEach(function (i) { | ||
blocks[i] = string | ||
}) | ||
done(null, string) | ||
}) | ||
} | ||
} | ||
// Replace all the blocks with the local value | ||
indices.forEach(function (i) { | ||
blocks[i] = value | ||
}) | ||
}) | ||
// Now we iterate through the blocks. | ||
var stream = new PassThrough() | ||
var i = 0 | ||
var l = blocks.length | ||
;(function next(err) { | ||
if (err) | ||
return stream.emit('error', err) | ||
if (i >= l) | ||
return stream.end() | ||
var block = blocks[i++] | ||
if (!block) { | ||
next() | ||
} else if (typeof block === 'string') { | ||
stream.write(block) | ||
next() | ||
} else if (Buffer.isBuffer(block) && block.length) { | ||
stream.write(block) | ||
next() | ||
} else if (typeof block === 'function') { | ||
block(function (err, buf) { | ||
if (buf && buf.length) | ||
stream.write(buf) | ||
next(err) | ||
}) | ||
} else if (block instanceof Stream) { | ||
var cleanup = function cleanup() { | ||
block.removeListener('error', next) | ||
block.removeListener('error', cleanup) | ||
block.removeListener('end', next) | ||
block.removeListener('end', cleanup) | ||
block.removeListener('close', next) | ||
block.removeListener('close', cleanup) | ||
} | ||
block | ||
.once('error', next) | ||
.once('error', cleanup) | ||
.once('end', next) | ||
.once('end', cleanup) | ||
.once('close', next) | ||
.once('close', cleanup) | ||
.pipe(stream, { | ||
end: false | ||
}) | ||
} | ||
})() | ||
if (callback) | ||
stream.pipe(cat(callback)) | ||
return stream | ||
} | ||
function extend(dest, src) { | ||
Object.keys(src).forEach(function (key) { | ||
if (!(key in dest)) | ||
dest[key] = src[key] | ||
}) | ||
} |
{ | ||
"name": "ablock", | ||
"description": "Asynchronous block-based templating", | ||
"version": "0.0.1", | ||
"version": "0.0.2", | ||
"dependencies": { | ||
"cat-stream": "~0.0.1" | ||
}, | ||
"devDependencies": { | ||
"should": "*", | ||
"mocha": "*", | ||
"concat-stream": "*", | ||
"express": "*", | ||
"supertest": "*", | ||
"jade": "*" | ||
"mocha": "*" | ||
}, | ||
@@ -13,0 +12,0 @@ "scripts": { |
# ABlock - Asynchronous Block Templating [![Build Status](https://travis-ci.org/funraiseme/ablock.png)](https://travis-ci.org/funraiseme/ablock) | ||
Split your templates into asynchronous blocks, | ||
then asynchronously render the template, | ||
with a streaming option. | ||
then asynchronously render the template. | ||
This is a asynchronous, server-side extension of [block](https://github.com/funraiseme/block). | ||
This is similar to [hyperstream](https://github.com/substack/hyperstream) except: | ||
- More than just streams are allowed | ||
- No CSS selector support - `ablock` is template type agnostic. You can stream JSON if you really want. | ||
## API | ||
### new Block(string) | ||
Creates a block instance from an input string. | ||
Each block can only be a word and must be surrounded by `{{}}`. | ||
Example, `{{header}}` is okay, but not `{{body.header}}`. | ||
### block.local({} || [key'', value]), block.locals() | ||
Permanently replaces a block with a string or an asynchronous function. | ||
`block.local()` and `block.locals()` are aliases of each other. | ||
For example: | ||
```js | ||
var template = new Block('<html>{{head}}{{body}}</html>') | ||
// This would permanently replace {{head}} with the following string | ||
template.local('head', '<title>My Site</title>') | ||
// This would permanently replace {{body}} with an asynchronous template | ||
template.local('body', function (done) { | ||
res.render('homepage', done) | ||
}) | ||
// This would set both locals at the same time: | ||
template.local({ | ||
head: '<title>My Site</title>', | ||
body: function (done) { | ||
res.render('homepage', done) | ||
} | ||
}) | ||
``` | ||
This is useful when building templates from pieces. | ||
### block.render([locals], [callback]) | ||
This returns a readable stream. | ||
`locals` are optional, temporary locals for the template. | ||
Unlike `block.local()`, these locals are not permanent. | ||
`locals` must be an object. | ||
`callback` is an optional callback that returns the resulting template as a `Buffer` instance. For example: | ||
```js | ||
function (req, res, next) { | ||
block.render(res.locals, function (err, buf) { | ||
if (err) | ||
return next(err) | ||
if (!buf) | ||
// One of the underlying streams was destroyed. | ||
return next(new Error('Something wrong happened.')) | ||
res.setHeader('Content-Type', 'text/html; charset=utf-8') | ||
res.send(buf) | ||
}) | ||
} | ||
``` | ||
If you want the result in a single string, just call `buf.toString('utf8')`. | ||
However, this is pretty much unnecessary. | ||
If no `callback` is set, you are expected to `block.render().pipe()` into a writable stream. For example: | ||
```js | ||
function (req, res) { | ||
res.setHeader('Content-Type', 'text/html; charset=utf-8') | ||
block.render(res.locals).pipe(res) | ||
} | ||
``` | ||
If you want to use conditional GETs (ie send 304 status codes when possible), | ||
you should use a callback. | ||
Otherwise, you should stream it. | ||
### Valid block types | ||
Each block can be: | ||
- A string | ||
- A buffer (ASCII or UTF-8) | ||
- A readable stream | ||
- A thunk (function that only takes a callback) | ||
## License | ||
@@ -9,0 +99,0 @@ |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
2
120
13981
1
7
296
1
+ Addedcat-stream@~0.0.1
+ Addedcat-stream@0.0.4(transitive)