UNPKG

mincer

Version:

Web assets processor. Native JavaScript port of Sprockets.

148 lines (120 loc) 3.91 kB
/** * class MacroProcessor * * `MacroProcessor` is simple and effective replacement for EJS, when user * needs just embed constants into file. Macros content is replaced with * evaluated value. You can use all mincer helpers (locals) in expression. * * ##### Example of use: * * ```javascript * var url = "'$$ asset_path('/vendor/jquery') $$'"; * var str = '"$$ function () { var foo = my_super_helpers(5) + 23; return foo; } $$"'; * var str = '$$ JSON.stringify(asset_path("/vendor/jquery")) $$'; * ``` * * MacroProcessor processor is always on, but not assigned to any file * extention. You can enable macros with `configure()` method. * * ##### Example of configure: * * ```javascript * var mincer = require('mincer'); * * // Enable for `.js` / `.css` / `.jade` * mincer.Macro.configure(['.js', '.css', '.jade']); * * // Enable for `.js` only * mincer.Macro.configure('.js', true); * ``` * * ##### SUBCLASS OF * * [[Template]] **/ 'use strict'; var format = require('util').format; // internal var Template = require('../template'); //////////////////////////////////////////////////////////////////////////////// // Class constructor var MacroProcessor = module.exports = function MacroProcessor() { Template.apply(this, arguments); }; require('util').inherits(MacroProcessor, Template); // Internal (private) config storage var config = {}; var DEFAULT_RULES = [ /'\$\$([^\r\n]+?)\$\$'/g, /"\$\$([^\r\n]+?)\$\$"/g ]; /** * MacroProcessor.configure(extention, enable) -> Void * - extention (String|Array): extention or array of extentions to process * - enable (Boolean): `false` - disable, `true` or undefined - enable * * on / off macroses for specified extentions. * * * ##### Example * * ```javascript * // Enable for `.js` / `.css` / `.jade` * MacroProcessor.configure(['.js', '.css', '.jade']); * MacroProcessor.configure(['.js', '.css', '.jade'], true); * * // Enable for `.js` only * MacroProcessor.configure('.js', true); * ``` **/ MacroProcessor.configure = function (extention, enable) { extention = Array.isArray(extention) ? extention : [ extention ]; // normalize extention format ('js' -> '.js') extention = extention.map(function (ext) { return ext[0] === '.' ? ext : '.' + ext; }); extention.forEach(function (ext) { if (enable === false) { config[ext] = null; return; } config[ext] = DEFAULT_RULES; }); }; // cache generated functions, to buid new one only if `locals` change. var evaluatorCache = {}; // Replace macros MacroProcessor.prototype.evaluate = function (context, locals) { var body = '', ext, key = Object.keys(locals).toString(); // search nearest extention, that has enabled macros rules ext = context.environment.attributesFor(context.pathname).extensions .reverse() .filter(function (extention) { return !!config[extention]; })[0]; if (!ext) { return; } // create evaluator wrapper if (!evaluatorCache.hasOwnProperty(key)) { // build function, that allow macro to access `local` keys by name directly. Object.keys(locals).forEach(function (key) { body += 'var ' + key + ' = __locals.' + key + ';\n'; }); body += 'return eval(data);\n'; /*eslint-disable no-new-func*/ evaluatorCache[key] = new Function('data', '__locals', body); } var result = this.data; config[ext].forEach(function (rule) { result = result.replace(rule, function (match, value, offset, orig) { try { return evaluatorCache[key](value, locals); } catch (e) { // Fill error message context.__LINE__ = orig.slice(0, offset).split(/\r?\n/).length; throw new Error(format('Failed to evaluate macro `%s` [%s]', value.trim(), e.message)); } }); }); this.data = result; };