diff --git a/bin/bem-xjst b/bin/bem-xjst index 74b027ae..2d2b8533 100755 --- a/bin/bem-xjst +++ b/bin/bem-xjst @@ -35,6 +35,12 @@ require('coa').Cmd() .short('e').long('engine') .def('bemhtml') .end() + .opt() + .name('sourcemap').title('Generate source map (default: false)') + .short('s').long('sourcemap') + .def(false) + .flag() + .end() .act(function(options) { var input = [], deferred = q.defer(); @@ -51,7 +57,10 @@ require('coa').Cmd() function finish(source) { var out = bem_xjst[options.engine].generate(source, { - 'no-opt': options['no-opt'] + 'no-opt': options['no-opt'], + from: options.input.path, + to: options.output.path, + sourceMap: options.sourceMap }); options.output.write(out); diff --git a/docs/en/3-api.md b/docs/en/3-api.md index aeca2145..da97bbba 100644 --- a/docs/en/3-api.md +++ b/docs/en/3-api.md @@ -15,6 +15,7 @@ * [Using thirdparty libraries](#using-thirdparty-libraries) * [Extending BEMContext](#extending-bemcontext) * [Bundling](#bundling) +* [Source maps](#source-maps) ## Choosing an engine, compiling and applying templates @@ -552,4 +553,32 @@ var bundle = bemxjst.bemhtml.generate(function() { ``` Now `bundle` has a string containing the JavaScript code of the BEMHTML core and the user-defined templates. +## Source maps + +There is options in `generate` method to use source maps. + +* `to` {String} — output bundle file name +* `sourceMap.from` {String} — file name +* `sourceMap.prev` {String|Function|SourceMapConsumer|SourceMapGenerator} — +* previous source maps in any format + +Example of generating bundle with source maps: + +```js +var fs = require('fs'), + bemxjst = require('bem-xjst').bemhtml, + tmpl = 'my-block-1.bemhtml.js', + bundle = 'bundle.bemhtml.js'; + +var result = bemxjst.generate(fs.readFileSync(tmpl, 'utf8'), { + to: bundle, + sourceMap: { from: tmpl } +}); + +fs.writeFileSync(bundle, result); +``` + +Also [see examples](../../examples/source-maps/) about source maps and +bem-xjst. + Read next: [Input data](4-data.md) diff --git a/docs/ru/3-api.md b/docs/ru/3-api.md index aedbf00b..f3d66ab7 100644 --- a/docs/ru/3-api.md +++ b/docs/ru/3-api.md @@ -14,6 +14,7 @@ * [Подключение сторонних библиотек](#Подключение-сторонних-библиотек) * [Расширение BEMContext](#Расширение-bemcontext) * [Создание бандла](#Создание-бандла) +* [Source maps](#source-maps) ## Выбор движка, компиляция и применение шаблонов @@ -556,4 +557,30 @@ fs.writeFile('bundle.js', bundle, function(err) { полностью готов к исполению в браузерах, node.js или любой виртуальной машине JS. +## Source maps + +В опциях шаблонизатора у вас есть возможность указать данные про карты кода + +* `to` {String} — имя выходного бандла-файла +* `sourceMap.from` {String} — имя файла +* `sourceMap.prev` {String|Function|SourceMapConsumer|SourceMapGenerator} — предыдущие карты кода в любом из перечисленных форматов + +Пример генерирования бандла с шаблонами и ядром bem-xjst с использованием карт кода: + +```js +var fs = require('fs'), + bemxjst = require('bem-xjst').bemhtml, + tmpl = 'my-block-1.bemhtml.js', + bundle = 'bundle.bemhtml.js'; + +var result = bemxjst.generate(fs.readFileSync(tmpl, 'utf8'), { + to: bundle, + sourceMap: { from: tmpl } +}); + +fs.writeFileSync(bundle, result); +``` + +Так же [смотрите примеры](../../examples/source-maps/) по использованию карт кода вместе с шаблонизатором. + Читать далее: [входные данные](4-data.md) diff --git a/examples/source-maps/demo1/b1.bemhtml.js b/examples/source-maps/demo1/b1.bemhtml.js new file mode 100644 index 00000000..d799ecaf --- /dev/null +++ b/examples/source-maps/demo1/b1.bemhtml.js @@ -0,0 +1 @@ +block('b1').tag()('blah'); diff --git a/examples/source-maps/demo1/demo1.html b/examples/source-maps/demo1/demo1.html new file mode 100644 index 00000000..1e1bb500 --- /dev/null +++ b/examples/source-maps/demo1/demo1.html @@ -0,0 +1 @@ + diff --git a/examples/source-maps/demo1/demo1.js b/examples/source-maps/demo1/demo1.js new file mode 100644 index 00000000..4e4f1f06 --- /dev/null +++ b/examples/source-maps/demo1/demo1.js @@ -0,0 +1,11 @@ +var fs = require('fs'), + bemxjst = require('../../../').bemhtml, + tmpl = 'b1.bemhtml.js', + bundle = 'bundle.bemhtml.js'; + +var result = bemxjst.generate(fs.readFileSync(tmpl, 'utf8'), { + to: bundle, + sourceMap: { from: tmpl } +}); + +fs.writeFileSync(bundle, result); diff --git a/examples/source-maps/demo2/demo2.html b/examples/source-maps/demo2/demo2.html new file mode 100644 index 00000000..1e1bb500 --- /dev/null +++ b/examples/source-maps/demo2/demo2.html @@ -0,0 +1 @@ + diff --git a/examples/source-maps/demo2/demo2.js b/examples/source-maps/demo2/demo2.js new file mode 100644 index 00000000..6f1eb39f --- /dev/null +++ b/examples/source-maps/demo2/demo2.js @@ -0,0 +1,11 @@ +var fs = require('fs'), + bemxjst = require('../../../').bemhtml, + tmpl = 'tmpls-with-sourcemap.bemhtml.js', + bundle = 'bundle.bemhtml.js'; + +var result = bemxjst.generate(fs.readFileSync(tmpl, 'utf8'), { + to: bundle, + sourceMap: { from: tmpl } +}); + +fs.writeFileSync(bundle, result); diff --git a/examples/source-maps/demo2/tmpls-with-sourcemap.bemhtml.js b/examples/source-maps/demo2/tmpls-with-sourcemap.bemhtml.js new file mode 100644 index 00000000..0e254de2 --- /dev/null +++ b/examples/source-maps/demo2/tmpls-with-sourcemap.bemhtml.js @@ -0,0 +1,72 @@ +block('button')( + def()(function() { + var tag = apply('tag'), + isRealButton = (tag === 'button') && (!this.mods.type || this.mods.type === 'submit'); + + return applyNext({ _isRealButton : isRealButton }); + }), + + tag()(function() { + return this.ctx.tag || 'button'; + }), + + js()(true), + + // NOTE: mix below is to satisfy interface of `control` + mix()({ elem : 'control' }), + + attrs()( + // Common attributes + function() { + var ctx = this.ctx, + attrs = { + role : 'button', + tabindex : ctx.tabIndex, + id : ctx.id, + title : ctx.title + }; + + this.mods.disabled && + !this._isRealButton && (attrs['aria-disabled'] = true); + + return attrs; + }, + + // Attributes for button variant + match(function() { return this._isRealButton; })(function() { + var ctx = this.ctx, + attrs = { + type : this.mods.type || 'button', + name : ctx.name, + value : ctx.val + }; + + this.mods.disabled && (attrs.disabled = 'disabled'); + + return this.extend(applyNext(), attrs); + }) + ), + + content()( + function() { + var ctx = this.ctx, + content = [ctx.icon]; + // NOTE: wasn't moved to separate template for optimization + 'text' in ctx && content.push({ elem : 'text', content : ctx.text }); + return content; + }, + match(function() { return typeof this.ctx.content !== 'undefined'; })(function() { + return this.ctx.content; + }) + ) +); + +block('input')( + tag()('span'), + js()(true), + def()(function() { + return applyNext({ _input : this.ctx }); + }), + content()({ elem : 'box', content : { elem : 'control' } }) +); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImxpYnMvYmVtLWNvbXBvbmVudHMvY29tbW9uLmJsb2Nrcy9idXR0b24vYnV0dG9uLmJlbWh0bWwiLCJsaWJzL2JlbS1jb21wb25lbnRzL2NvbW1vbi5ibG9ja3MvaW5wdXQvaW5wdXQuYmVtaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUM5REE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6InRtcGwtd2l0aC1zb3VyY2VtYXAuanMifQ== \ No newline at end of file diff --git a/examples/source-maps/demo3/b3.bemhtml.js b/examples/source-maps/demo3/b3.bemhtml.js new file mode 100644 index 00000000..b2d4f1ed --- /dev/null +++ b/examples/source-maps/demo3/b3.bemhtml.js @@ -0,0 +1 @@ +block('b3').tag()('blah'); diff --git a/examples/source-maps/demo3/demo3.html b/examples/source-maps/demo3/demo3.html new file mode 100644 index 00000000..1e1bb500 --- /dev/null +++ b/examples/source-maps/demo3/demo3.html @@ -0,0 +1 @@ + diff --git a/examples/source-maps/demo3/demo3.js b/examples/source-maps/demo3/demo3.js new file mode 100644 index 00000000..c59469e8 --- /dev/null +++ b/examples/source-maps/demo3/demo3.js @@ -0,0 +1,14 @@ +var fs = require('fs'), + bemxjst = require('../../../').bemhtml, + tmpl = 'b3.bemhtml.js', + bundle = 'bundle.bemhtml.js'; + +var result = bemxjst.generate(fs.readFileSync(tmpl, 'utf8'), { + to: bundle, + sourceMap: { + prev: 'block(\'a\').tag()(\'a\');', + from: tmpl + }, +}); + +fs.writeFileSync(bundle, result); diff --git a/lib/compiler.js b/lib/compiler.js index 4fd3d14d..cf0ca675 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -1,5 +1,6 @@ var fnToString = require('./bemxjst/utils').fnToString; var readFileSync = require('fs').readFileSync; +var SourceMapFile = require('enb-source-map/lib/file'); var engines = { bemhtml: require('./bemhtml'), bemtree: require('./bemtree') @@ -81,12 +82,14 @@ Compiler.prototype.compile = function compile(code, options) { Compiler.prototype.generate = function generate(code, options) { options = options || {}; - code = fnToString(code); - var exportName = options.exportName || this.engineName; + var sourceMap = options.sourceMap; + + if (!options.to) options.to = process.cwd(); + var file = new SourceMapFile(options.to, { sourceMap: sourceMap }); code = [ - code + ';', + fnToString(code) + ';', 'oninit(function(exports, context) {', 'var BEMContext = exports.BEMContext || context.BEMContext;', // Provides third-party libraries from different modular systems @@ -98,26 +101,42 @@ Compiler.prototype.generate = function generate(code, options) { var deps = getDeps(options.requires); - var source = [ - 'var ' + exportName + ';', - '(function(global) {', - 'function buildBemXjst(libs) {', - 'var exports;', - - '/* BEM-XJST Runtime Start */', - 'var ' + exportName + ' = function(module, exports) {', - bundles[this.engineName] + ';', - 'return module.exports || exports.' + exportName + ';', - '}({}, {});', - - 'var api = new ' + exportName + '(' + JSON.stringify(options) + ');', + file + .writeContent([ + 'var ' + exportName + ';', + '(function(global) {', + 'function buildBemXjst(libs) {', + 'var exports;', + + '/* BEM-XJST Runtime Start */', + 'var ' + exportName + ' = function(module, exports) {', + bundles[this.engineName] + ';', + 'return module.exports || exports.' + exportName + ';', + '}({}, {});', + + 'var api = new ' + exportName + '(' + JSON.stringify(options) + ');', + + 'api.compile(function(' + + require('./bemxjst').prototype.locals.join(', ') + + ') {', + '/* BEM-XJST User code here: */' + ].join(EOL)); + + if (sourceMap && sourceMap.from) { + file.writeFileContent(sourceMap.from, getCode(code, options.runtimeLint)); + + if (sourceMap.prev) { + file.writeContent( + file.writeFileFromPrevMap(sourceMap.from, sourceMap.prev)); + } + } else { + file.writeContent(getCode(code, options.runtimeLint)); + } - '/* BEM-XJST User-code Start */', - 'api.compile(function(' + - require('./bemxjst').prototype.locals.join(', ') + - ') {', - getCode(code, options.runtimeLint) + ';' + - '});', + file + .write(';') + .writeContent([ + '});', 'exports = api.exportApply(exports);', 'if (libs) exports.BEMContext.prototype._libs = libs;', @@ -173,9 +192,14 @@ Compiler.prototype.generate = function generate(code, options) { '})(typeof window !== "undefined" ? ' + 'window : typeof global !== "undefined" ? global : this);' - ].join(EOL); + ].join(EOL)); - return source; + return options.sourceMap && options.sourceMap.include === false ? + { + content: file.getContent(), + sourceMap: file.getSourceMap() + } : + file.render(); }; module.exports = Compiler; diff --git a/package.json b/package.json index 7dd0a72e..324224f5 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "license": "MPL-2.0", "dependencies": { "coa": "^1.0.1", + "enb-source-map": "^1.11.0", "inherits": "^2.0.1", "q": "^2.0.3" },