UNPKG

templates

Version:

System for creating and managing template collections, and rendering templates with any node.js template engine. Can be used as the basis for creating a static site generator or blog framework.

258 lines (217 loc) 5.47 kB
'use strict'; var Base = require('base-methods'); var cloneStats = require('clone-stats'); var Stream = require('stream'); var Vinyl = require('vinyl'); var utils = require('./utils'); /** * Create an instance of `View`. Optionally pass a default object * to use. * * ```js * var view = new View({ * path: 'foo.html', * content: '...' * }); * ``` * @param {Object} `view` * @api public */ function View(view) { this.isView = true; view = view || {}; utils.syncContents(this, view.contents || view.content); this.options = view.options || {}; this.locals = view.locals || {}; this.data = view.data || {}; this.define('_contents', null); this.define('_content', null); this.define('contents', { configurable: true, enumerable: false, get: function () { return this._contents; }, set: function (val) { utils.syncContents(this, val); } }); Vinyl.call(this, view); Base.call(this, view); for (var key in view) { var val = view[key]; if (key === 'stat' && isObject(val) && val.mode) { this.set(key, cloneStats(val)); } else if (val) { this.set(key, val); } } } /** * Inherit `Base` and `Vinyl` */ Base.extend(View); Base.inherit(View, Vinyl); /** * Run a plugin on the `view` instance. * * ```js * var view = new View({path: 'abc', contents: '...'}) * .use(require('foo')) * .use(require('bar')) * .use(require('baz')) * ``` * @param {Function} `fn` * @return {Object} * @api public */ View.prototype.use = function(fn) { fn.call(this, this); this.emit('use'); return this; }; /** * Synchronously compile a view. * * ```js * var view = page.compile(); * view.fn({title: 'A'}); * view.fn({title: 'B'}); * view.fn({title: 'C'}); * ``` * * @param {Object} `locals` Optionally pass locals to the engine. * @return {Object} `View` instance, for chaining. * @api public */ View.prototype.compile = function (settings) { this.fn = utils.engine.compile(this.content, settings); return this; }; /** * Asynchronously render a view. * * ```js * view.render({title: 'Home'}, function(err, res) { * //=> view object with rendered `content` * }); * ``` * @param {Object} `locals` Optionally pass locals to the engine. * @return {Object} `View` instance, for chaining. * @api public */ View.prototype.render = function (locals, cb) { if (typeof locals === 'function') return this.render({}, locals); if (typeof this.fn !== 'function') this.compile(locals); this.locals = utils.merge({}, this.locals, locals); var context = utils.merge({}, this.locals, this.data); for (var key in this) { if (this.hasOwnProperty(key)) { context[key] = context[key] || this[key]; } } context.path = this.path; utils.engine.render(this.fn, context, function (err, res) { if (err) return cb(err); this.contents = new Buffer(res); cb(null, this); }.bind(this)); return this; }; /** * Re-decorate View methods after calling * vinyl's `.clone()` method. * * ```js * view.clone({deep: true}); // false by default * ``` * @param {Object} `options` * @return {Object} `view` Cloned instance * @api public */ View.prototype.clone = function (opts) { opts = opts || {}; if (typeof opts === 'boolean') { opts = {deep: true}; } opts.deep = opts.deep === true; opts.contents = opts.contents !== false; // clone the instance's view contents var contents = this.contents; if (this.isStream()) { contents = this.contents.pipe(new Stream.PassThrough()); this.contents = this.contents.pipe(new Stream.PassThrough()); } else if (this.isBuffer()) { contents = opts.contents ? cloneBuffer(this.contents) : this.contents; } var view = new View({ cwd: this.cwd, base: this.base, stat: (this.stat ? cloneStats(this.stat) : null), history: this.history.slice(), contents: contents }); var ignored = ['_contents', 'stat', 'history', 'path', 'base', 'cwd']; for (var key in this) { if (ignored.indexOf(key) < 0) { utils.define(view, key, opts.deep ? utils.clone(this[key], true) : this[key]); } } return view; }; /** * Override the vinyl `inspect` method. */ View.prototype.inspect = function () { var inspect = []; // use relative path if possible var filepath = (this.base && this.path) ? this.relative : this.path; if (filepath) { inspect.push('"' + filepath + '"'); } if (this.isBuffer()) { inspect.push(this.contents.inspect()); } if (this.isStream()) { inspect.push(inspectStream(this.contents)); } return '<View ' + inspect.join(' ') + '>'; }; /** * */ utils.define(View.prototype, 'content', { set: function(val) { utils.syncContents(this, val); }, get: function() { return this._content; } }); /** * Ensure that the `layout` property is set on a view. */ utils.define(View.prototype, 'layout', { set: function (val) { this.define('_layout', val); }, get: function () { return this._layout || this.data.layout || this.locals.layout || this.options.layout; } }); function inspectStream(stream) { var type = stream.constructor.name; return '<' + (type !== 'Stream' ? type + 'Stream>' : ''); } function cloneBuffer(buffer) { var res = new Buffer(buffer.length); buffer.copy(res); return res; } function isObject(val) { return val && typeof val === 'object'; } /** * Expose `View` */ module.exports = View;