UNPKG

bigpipe

Version:

Bigpipe a radical new web framework for Node.js that's inspired by Facebook's bigpipe concept.

203 lines (178 loc) 5.45 kB
'use strict'; var crypto = require('crypto') , fuse = require('fusing') , path = require('path'); /** * Simple representation of a single file. * * @constructor * @param {String} filepath Absolute path to file. * @param {Object} options Set of options * * Available options: * - aliases {String|Array} absolute (alternative) path(s) to the source. * - extname {String} extension of the file. * - dependency {Boolean} File is page level dependency. * - code {Buffer} The contents of the file. * - external {Boolean} File is hosted externally. * * @api public */ function File(filepath, options) { this.fuse(); options = options || {}; options.code = options.code || ''; options.extname = options.extname || '.js'; options.external = options.external || false; options.dependency = options.dependency || false; filepath = (filepath || '').replace(path.extname(filepath), ''); // // Normalize aliasses. File can have multiple aliases, for example due to // symlinked content. Aliases will be used by compiler.register // options.aliases = options.aliases || filepath; options.aliases = Array.isArray(options.aliases) ? options.aliases : [ options.aliases ]; this.readable('enumerable', File.predefine(this, { enumerable: true, writable: true })); this.writable('code'); // Actual code. this.writable('buffer'); // Buffer of code. this.writable('_events'); // EventEmitter 3. this.writable('aliases', options.aliases); // Absolute paths to source. this.enumerable('length'); // Buffer length. this.enumerable('hash', null); // Hashed code representation. this.enumerable('pagelets', []); // List of pagelets. this.enumerable('filepath', filepath); // Absolute path to file. this.enumerable('extname', options.extname); // File extension. this.enumerable('external', options.external); // File is hosted externally. this.readable('dependency', options.dependency); // File is page dependency. this.readable('type', this.mime[this.extname]); // The content-type. // // Process the content of the file if provided. // this.hash = this.encrypt(options.code); this.set(options.code); } fuse(File, require('eventemitter3')); /** * Expose distributed location of the File. * * @return {String} file location. * @api public */ File.readable('location', { enumerable: false, get: function get() { if (this.external) return this.filepath + this.extname; return '/' + this.hash + this.extname; } }, true); /** * Expose original location of the File. * * @return {String} original file location. * @api public */ File.readable('origin', { enumerable: false, get: function get() { if (!this.filepath) return; return '/' + this.filepath + this.extname; } }, true); /** * Update the content of the file, convert to Buffer if required and update length. * * @param {Buffer|String} content Content of the file. * @returns {File} fluent interface * @api private */ File.readable('set', function set(content) { this.code = content.toString('utf-8'); // // Append async loading class to CSS // if (this.is('css')) { content = this.append(content, this.hash); } this.buffer = Buffer.isBuffer(content) ? content : new Buffer(content); this.length = this.buffer.length; return this; }); /** * Get the orginal content, either the buffer or the code. * * @param {Boolean} readable true will return readable code, false the Buffer * @return {String} content * @api public */ File.readable('get', function get(readable) { return readable ? this.code : this.buffer; }); /** * Update the CSS with a selector that contains the filename which is * required for async loading of CSS. * * @param {String} content CSS * @param {String} hash Optional hashed representation of the file * @returns {String} CSS content with selector appended * @api public */ File.readable('append', function append(content, hash) { return [ content, '#_', hash || this.hash, ' { height: 42px }' ].join(''); }); /** * Create a hash of the code which can be used as filename. This allows us to * aggressively cache the data. * * @param {String} code The code that is send to the client. * @returns {String} hash representation of the code. * @api private */ File.readable('encrypt', function encrypt(code) { return crypto.createHash('sha1').update(code).digest('hex').toString('hex'); }); /** * Check if the file is of the provided type. * * @param {String} type part of the extension of the file. * @returns {Boolean} * @api private */ File.readable('is', function is(type) { if (type.charAt(0) !== '.') type = '.' + type; return this.extname === type; }); /** * Add file to the list of aliases. * * @param {String} filepath * @return {File} fluent interface * @api public */ File.readable('alias', function alias(filepath) { if (!~this.aliases.indexOf(filepath)) this.aliases.push(filepath); return this; }); /** * Small mime lookup table. * * @type {Object} * @private */ File.readable('mime', { '.js': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8' }); // // Expose the module interface. // module.exports = File;