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"
},