diff --git a/README.md b/README.md index 59ce630..b963835 100644 --- a/README.md +++ b/README.md @@ -35,11 +35,11 @@ bem-xjst engines accesible via properties `bemhtml` and `bemtree`: var engine = require('gulp-bem-xjst')[engine]; ``` -### Plugin options +### Options -* *String* **exportName** — Engine handler's variable name. Default — `BEMHTML`. -* *String* **engine** — Engine's name. Default — `BEMHTML`. +* *String* **exportName** — Engine handler's variable name. * *String* **extension** — extension for file. Default — `.${engine}.js`. +* *Object* **engine** — XJST [options](https://github.com/bem/bem-xjst/blob/master/docs/en/3-api.md#settings). ### License diff --git a/error.js b/error.js index 6ae0b7a..4fc02c5 100644 --- a/error.js +++ b/error.js @@ -8,29 +8,27 @@ var format = util.format; /** * @param {{description: string, column: number, lineNumber: number}} err - Error data * @param {String} code - Code sample - * @param {String} filepath - Path to the file + * @param {String} filePath - Path to the file * @return {String|Object} */ -module.exports = function (err, code, filepath) { +module.exports = function (err, code, filePath) { // Assume that esprima parser failed if (err.description && err.column && err.lineNumber) { var source = code.split('\n'); - var addtionalLines = 3; + var additionalLines = 3; var errorLine = err.lineNumber; // extra line from length prop - var startLine = Math.max(errorLine - addtionalLines, 0); - var endLine = Math.min(errorLine + addtionalLines, source.length); + var startLine = Math.max(errorLine - additionalLines, 0); + var endLine = Math.min(errorLine + additionalLines, source.length); var fragment = source.slice(startLine, endLine); // Adding marker - fragment.splice(errorLine - startLine + 1, 0, Array(err.column).join(' ') + '^'); + fragment.splice(errorLine - startLine + 1, 0, new Array(err.column).join(' ') + '^'); - var message = format('%s at %s:\n%s', + return format('%s at %s:\n%s', err.description, - path.basename(filepath), + path.basename(filePath), fragment.join('\n')); - - return message; } return err; diff --git a/index.js b/index.js index 85e2859..33cfc7a 100644 --- a/index.js +++ b/index.js @@ -12,17 +12,22 @@ var isStream = require('is-stream'); var toArray = require('stream-to-array'); var formatError = require('./error'); +var bundle = require('./lib/templates/bundle'); var pluginName = path.basename(__dirname); /** * bem-xjst templates compiler. * - * @param {{extension: string}} options - Options for generator. + * @param {Object} [options] - Options for generator. + * @param {String} [options.extension] - File extension. Default: engine name. + * @param {String} [options.exportName] - Name for export. Please notice if you set this option generated code will + * @param {Object} [options.engine] - Engine compiler options. @see + * https://github.com/bem/bem-xjst/blob/master/docs/en/3-api.md#settings wrapped with CommonJS, YModules or Global * @param {String|Function} engine - 'bemhtml' either 'bemtree' or any xjst-like engine function. * @returns {Stream} */ -module.exports = function(options, engine) { +module.exports = function (options, engine) { options = options || {}; assert(typeof engine === 'string' || typeof (engine && engine.generate) === 'function', 'Invalid engine'); @@ -32,10 +37,10 @@ module.exports = function(options, engine) { engineName = engine; engine = bemxjst[engine]; } else { - engineName = (engine.engineName || engine.name || Object(engine.runtime).name).toLowerCase() || 'xjst'; + engineName = (engine.engineName || (engine.runtime && engine.runtime.name)).toLowerCase() || 'xjst'; } - return through.obj(function(file, encoding, callback) { + return through.obj(function (file, encoding, callback) { if (file.isNull()) { return callback(null, file); } @@ -45,13 +50,16 @@ module.exports = function(options, engine) { } var code = file.contents.toString(); - var res = tryCatch(function() { - return engine.generate(code, options); - }, function(e) { + var res = tryCatch(function () { + var compiledCode = engine.generate(code, options); + + return options.exportName ? bundle(compiledCode, options) : compiledCode; + }, function (e) { return new PluginError(pluginName, formatError(e, code, file.path), { fileName: file.path }); }); + if (res instanceof PluginError) { return callback(res); } @@ -64,28 +72,28 @@ module.exports = function(options, engine) { }); }; -module.exports.bemhtml = function(options) { +module.exports.bemhtml = function (options) { return module.exports(options, 'bemhtml'); }; -module.exports.bemtree = function(options) { +module.exports.bemtree = function (options) { return module.exports(options, 'bemtree'); }; /** * Wrapper for anything.apply with bemjson. * - * @param {Stream} templatesStream - Stream with bemhtmls - * @returns {TransformStream} - transform stream that applies templates to each incoming bemjson vinyl + * @param {Stream} templatesStream - Stream with bemhtmls + * @returns {stream.Transform} - transform stream that applies templates to each incoming bemjson vinyl */ -module.exports.toHtml = function(templatesStream) { +module.exports.toHtml = function (templatesStream) { if (!isStream(templatesStream)) { throw new PluginError(pluginName, 'Parameter should be a Stream'); } var templatesPromise = toArray(templatesStream); - return through.obj(function(bemjsonFile, _, callback) { + return through.obj(function (bemjsonFile, _, callback) { if (bemjsonFile.isNull()) { return callback(null, bemjsonFile); } @@ -111,7 +119,7 @@ module.exports.toHtml = function(templatesStream) { // Handle multiple templates case var n = 0; - templatesVinyls.forEach(function(file) { + templatesVinyls.forEach(function (file) { file.data || (file.data = _eval(String(file.contents))); var html = tryCatch(function () { diff --git a/lib/assets/bundle.jst b/lib/assets/bundle.jst new file mode 100644 index 0000000..a86ee17 --- /dev/null +++ b/lib/assets/bundle.jst @@ -0,0 +1,41 @@ +var ${ exportName }; + +(function(global) { + function buildBemXjst() { + var exports = {}; + + ${ bemxjst } + + return exports; + }; + + var defineAsGlobal = true; + + // Provide with CommonJS + if (typeof module === 'object' && typeof module.exports === 'object') { + exports['${ exportName }'] = buildBemXjst(); + defineAsGlobal = false; + } + + // Provide to YModules + if (typeof modules === 'object') { + modules.define( + '${ exportName }', + [<%_.each([], function(name) {%>'${ name }',<%});%>], + function( + provide<%if ([].length) {%>,<%}%> + ${ [].join(', ') } + ) { + provide(buildBemXjst()); + } + ); + + defineAsGlobal = false; + } + + // Provide to global scope + if (defineAsGlobal) { + ${ exportName } = buildBemXjst(); + global['${ exportName }'] = ${ exportName }; + } +})(typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : this); diff --git a/lib/templates/bundle.js b/lib/templates/bundle.js new file mode 100644 index 0000000..87b6657 --- /dev/null +++ b/lib/templates/bundle.js @@ -0,0 +1,31 @@ +var fs = require('fs'); +var path = require('path'); + +var _ = require('lodash'); + +var assetDir = path.join(__dirname, '..', 'assets'); +var templates = { + bundle: {path: path.join(assetDir, 'bundle.jst')} +}; + +// load templates +_.mapKeys(templates, function (template, name) { + templates[name] = _.template(fs.readFileSync(template.path, 'utf-8')); +}); + +/** + * Template for compile BEMHTML or BEMTREE to bundle. + * + * @param {String} code - Code compiled with the `bem-xjst` (BEMHTML or BEMTREE). + * @param {Object} options - Options. + * @param {String} [options.exportName=BEMHTML] - Name for exports. + * @returns {String} + */ +module.exports = function (code, options) { + options || (options = {}); + + return templates.bundle({ + exportName: options.exportName || 'BEMHTML', + bemxjst: code + }); +}; diff --git a/package.json b/package.json index e62bf69..25e0e99 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "dependencies": { "bem-xjst": "^8.4.0", "is-stream": "^1.1.0", + "lodash": "^4.17.4", "node-eval": "^1.1.0", "plugin-error": "^0.1.2", "stream-to-array": "^2.3.0", diff --git a/test/index.js b/test/index.js index 56388bb..b8af504 100644 --- a/test/index.js +++ b/test/index.js @@ -21,9 +21,9 @@ describe('gulp-bemhtml', function () { .on('end', next); intoStream.obj([new File({ - path: 'page.bemhtml', - contents: new Buffer('block(\'page\')(tag()(\'h1\'), content()(\'Hello, world!\'));') - })]) + path: 'page.bemhtml', + contents: new Buffer('block(\'page\')(tag()(\'h1\'), content()(\'Hello, world!\'));') + })]) .pipe(stream); }); @@ -41,7 +41,7 @@ describe('gulp-bemhtml', function () { var vinylFile; before(function (next) { - const stream = lib({}, bemxjst.bemhtml) + var stream = lib({}, bemxjst.bemhtml) .on('data', function (file) { vinylFile = file; }) @@ -59,4 +59,66 @@ describe('gulp-bemhtml', function () { expect(vinylFile.relative).to.be.equal('page.bemhtml.js'); }); }); + + describe('stream with exportName', function () { + var vinylFile; + + before(function (next) { + var stream = lib.bemhtml({exportName: 'BEMHTML'}) + .on('data', function (file) { + vinylFile = file; + }) + .on('error', next) + .on('end', next); + + intoStream.obj([new File({ + path: 'page.bemhtml', + contents: new Buffer('block(\'page\')(tag()(\'h1\'), content()(\'Hello, world!\'));') + })]) + .pipe(stream); + }); + + it('should export compiler to global', function () { + var engine = _eval(vinylFile.contents.toString()); + + expect(engine).to.have.property('BEMHTML'); + expect(engine.BEMHTML.apply).to.be.a('function'); + }); + + it('should export compiler to custom name', function (next) { + var testFile = ''; + var stream = lib.bemhtml({exportName: 'customProperty'}) + .on('data', function (file) { + testFile = file; + }) + .on('error', compileDone) + .on('end', compileDone); + + intoStream.obj([new File({ + path: 'page.bemhtml', + contents: new Buffer('block(\'page\')(tag()(\'h1\'), content()(\'Hello, world!\'));') + })]) + .pipe(stream); + + function compileDone() { + var engine = _eval(testFile.contents.toString()); + + expect(engine).to.have.property('customProperty'); + next(); + } + }); + + it('should export compiler to YModules', function () { + var vm = require('vm'); + var name = ''; + + vm.runInNewContext(vinylFile.contents.toString(), { + require: require, + console: console, + modules: {define: function (exportName) { name = exportName; }} + }); + + expect(name).to.be('BEMHTML'); + }); + }); });