diff --git a/.gitignore b/.gitignore
index ba2a97b..648ea07 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
node_modules
coverage
+npm-debug.log
diff --git a/README.md b/README.md
index 22cef2c..7f78cd7 100644
--- a/README.md
+++ b/README.md
@@ -121,6 +121,17 @@ module.exports = function(config) {
* [в браузере](#Исполнение-шаблонов-в-браузере)
* [для сборки HTML](#Использование-шаблонов-для-сборки-html)
+### Способы экспортирования бандла шаблонов
+
+Существуют разные способы сборки:
+
+* в виде CommonJS-модуля для [исполнения шаблонов в Node.js](#Исполнение-шаблонов-в-nodejs) или сборки с Browserify
+* в виде YM-модуля для [исполнения шаблонов в браузере](#Исполнение-шаблонов-в-браузере)
+
+По умолчанию сборщик пытается собрать шаблоны как CommonJS- и YM-модуль одновременно (будет возможно подключать как в среде браузера с модульной системой, так и в среде Node.js) и, если файл подключается иным способом, то при его исполнении объект с шаблонизатором экспортируется в глобальную область видимости.
+
+Это поведение описывается объектом опции `exports: { globals: true, commonJS: true, ym: true }` и является значением по умолчанию. В свойстве `globals` допустимо передать строку `'force'` для явного экспорта объекта с шаблонизатором в глобальную область видимости.
+
### Исполнение шаблонов в Node.js
Скомпилированный файл подключается как модуль в формате [CommonJS](http://www.commonjs.org/).
@@ -148,14 +159,19 @@ var bemjson = BEMTREE.apply({ block: 'page', data: { /* ... */ } }),
Шаблоны доступны из глобальной переменной `BEMTREE` или `BEMHTML`.
+ Значение опции `exports`: `{ globals: true }`.
+
```js
var bemjson = BEMTREE.apply({ block: 'page', data: { /* ... */ } }),
html = BEMHTML.apply(bemjson); // ...
```
+
* **С модульной системой YModules**
Шаблоны доступны из модульной системы ([YModules](https://ru.bem.info/tools/bem/modules/)):
+ Значение опции `exports`: `{ ym: true }`.
+
```js
modules.require(['BEMTREE', 'BEMHTML'], function(BEMTREE, BEMHTML) {
var bemjson = BEMTREE.apply({ block: 'page', data: { /* ... */ } }),
diff --git a/api.ru.md b/api.ru.md
index 038b4d9..fc1fcc1 100644
--- a/api.ru.md
+++ b/api.ru.md
@@ -25,6 +25,7 @@ bemhtml
* [sourceSuffixes](#sourcesuffixes)
* [requires](#requires)
* [exportName](#exportname)
+* [exports](#exports)
* [forceBaseTemplates](#forceBaseTemplates)
* [engineOptions](#engineoptions)
* [naming](#naming)
@@ -83,6 +84,24 @@ bemhtml
});
```
+#### exports
+
+Тип: `{globals: ?(Boolean|'force'), commonJS: ?Boolean, ym: ?Boolean}`.
+
+Указывает каким именно образом должен экспортироваться экземпляр BEMHTML.
+
+По умолчанию: `{ globals: true, commonJS: true, ym: true }`.
+
+Доступны следующие параметры:
+* `globals` указывает выгружать ли ссылку на экземпляр в глобальную область видимости, игнорируется, если указана одна из модульных систем, со значением `'force'` экспортируется безусловно;
+* `ym` — регистрировать ли экземпляр как модуль в YModules (при наличии переменной modules);
+* `commonJS` — записывать ли экземпляр в `modules.exports` (при его наличии).
+
+Примеры использования:
+- В случае переходного периода с глобального объекта к YModules можно передать `{globals: 'force', ym: true}` и шаблонизатор будет экспортирован для использования и из YModules, и из глобальной области видимости.
+- Передав `{ym: true}` или `{commonJS: true}` можно запретить экспорт в глобальную область видимости.
+- Передав `{ym: true, commonJS}` можно собрать бандл и для node.js, и для браузера с YModules, но без экспорта в глобальную область видимости.
+
#### forceBaseTemplates
Тип: `Boolean`. По умолчанию `false`.
diff --git a/lib/assets/bundle.jst b/lib/assets/bundle.jst
index 9eaef5d..304cdf1 100644
--- a/lib/assets/bundle.jst
+++ b/lib/assets/bundle.jst
@@ -14,13 +14,13 @@ var ${ exportName };
var defineAsGlobal = true;
// Provide with CommonJS
- if (typeof module === 'object' && typeof module.exports === 'object') {
- exports['${ exportName }'] = buildBemXjst(${ commonJSDependencies });
+ if (${ exportsOption.commonJS === true } && typeof module === 'object' && typeof module.exports === 'object') {
+ module.exports['${ exportName }'] = buildBemXjst(${ commonJSDependencies });
defineAsGlobal = false;
}
// Provide to YModules
- if (typeof modules === 'object') {
+ if (${ exportsOption.ym === true } && typeof modules === 'object') {
modules.define(
'${ exportName }',
[<%_.each(ymDependencyNames, function(name) {%>'${ name }',<%});%>],
@@ -31,12 +31,11 @@ var ${ exportName };
provide(buildBemXjst(${ ymDependencies }));
}
);
-
defineAsGlobal = false;
}
// Provide to global scope
- if (defineAsGlobal) {
+ if (${ exportsOption.globals === true } && defineAsGlobal || ${ exportsOption.globals === 'force' }) {
${ exportName } = buildBemXjst(${ globalDependencies });
global['${ exportName }'] = ${ exportName };
}
diff --git a/lib/bundle.js b/lib/bundle.js
index c473632..0837433 100644
--- a/lib/bundle.js
+++ b/lib/bundle.js
@@ -12,6 +12,7 @@ var compileCommonJS = require('./compile-commonjs'),
* @param {String} opts.dirname Path to a directory with compiled file.
* @param {String} [options.exportName=BEMHTML] Name for exports.
* @param {Object} [options.requires={}] Names for dependencies.
+ * @param {Object} [options.exports={globals: true, commonJS: true, ym: true}] Export settings.
* @returns {String}
*/
exports.compile = function (code, options) {
@@ -24,6 +25,7 @@ exports.compile = function (code, options) {
return template(code, {
exportName: options.exportName,
requires: requires,
+ exports: options.exports,
commonJSModules: commonJSModules
});
});
diff --git a/lib/templates/bundle.js b/lib/templates/bundle.js
index 8d0615b..e19060f 100644
--- a/lib/templates/bundle.js
+++ b/lib/templates/bundle.js
@@ -21,6 +21,7 @@ _.mapKeys(templates, function (template, name) {
* @param {Object} options Options.
* @param {String} [options.exportName=BEMHTML] Name for exports.
* @param {Object} [options.requires={}] Names for dependencies.
+ * @param {Object} [options.exports={globals: true, commonJS: true, ym: true}] Export settings.
* @param {String} [options.commonJSModules] Code of CommonJS modules: require function
* compiled with `browserify`.
* @returns {String}
@@ -29,6 +30,7 @@ module.exports = function (code, options) {
options || (options = {});
var requires = options.requires || {},
+ exports = options.exports || { globals: true, commonJS: true, ym: true },
templateOpts = { requires: requires },
ymDependencyNames = [],
ymDependencyVars = [];
@@ -43,6 +45,7 @@ module.exports = function (code, options) {
return templates.bundle({
exportName: options.exportName || 'BEMHTML',
bemxjst: code,
+ exportsOption: exports,
commonJSModules: options.commonJSModules,
globalDependencies: templates.globals(templateOpts),
diff --git a/package.json b/package.json
index 6156eff..5e46a31 100644
--- a/package.json
+++ b/package.json
@@ -35,6 +35,7 @@
"enb-async-require": "1.0.1",
"enb-require-or-eval": "1.0.2",
"lodash": "4.13.1",
+ "node-eval": "1.0.2",
"vow": "0.4.12",
"vow-node": "0.3.0"
},
diff --git a/techs/bem-xjst.js b/techs/bem-xjst.js
index c77a382..1657a1b 100644
--- a/techs/bem-xjst.js
+++ b/techs/bem-xjst.js
@@ -12,14 +12,20 @@ var EOL = require('os').EOL,
*
* Compiles BEMXJST template files with BEMXJST translator and merges them into a single template bundle.
*
- * Important: Normally you don't need to use this tech directly.
+ * Important: Usually you don't need to use this tech directly.
*
* @param {Object} [options] Options
* @param {String} [options.target='?.bem-xjst.js'] Path to a target with compiled file.
+ * @param {Object} [options.exports={globals: true, commonJS: true, ym: true}] Export settings.
*/
module.exports = buildFlow.create()
.name('bem-xjst')
.target('target', '?.bem-xjst.js')
+ .defineOption('exports', {
+ globals: true,
+ commonJS: true,
+ ym: true
+ })
.methods({
/**
* Returns filenames to compile.
@@ -146,7 +152,8 @@ module.exports = buildFlow.create()
return bundle.compile(compiledCode, {
dirname: this.node.getDir(),
exportName: this._exportName,
- requires: this._requires
+ requires: this._requires,
+ exports: this._exports
});
}, this);
},
diff --git a/techs/bemhtml.js b/techs/bemhtml.js
index 7d53824..0caf5cb 100644
--- a/techs/bemhtml.js
+++ b/techs/bemhtml.js
@@ -24,6 +24,7 @@ var path = require('path');
* Default as `__`.
* * String `mod` — separates names and values of modifiers
* from blocks and elements. Default as `_`.
+ * @param {Object} [options.exports={globals: true, commonJS: true, ym: true}] Export settings.
*
* @example
* var BemhtmlTech = require('enb-bemxjst/techs/bemhtml'),
diff --git a/techs/bemtree.js b/techs/bemtree.js
index 283a784..09511aa 100644
--- a/techs/bemtree.js
+++ b/techs/bemtree.js
@@ -17,6 +17,7 @@ var path = require('path');
* @param {Object} [options.requires] Names of dependencies which should be available from
* code of templates.
* @param {Boolean} [options.forceBaseTemplates=false] Include base templates if no user templates present
+ * @param {Object} [options.exports={globals: true, commonJS: true, ym: true}] Export settings.
*
* @example
* var BemtreeTech = require('enb-bemxjst/techs/bemtree'),
diff --git a/test/techs/bemhtml.test.js b/test/techs/bemhtml.test.js
index 47694b2..3f214bc 100644
--- a/test/techs/bemhtml.test.js
+++ b/test/techs/bemhtml.test.js
@@ -1,6 +1,7 @@
var fs = require('fs'),
path = require('path'),
mock = require('mock-fs'),
+ safeEval = require('node-eval'),
MockNode = require('mock-enb/lib/mock-node'),
Tech = require('../../techs/bemhtml'),
loadDirSync = require('mock-enb/utils/dir-utils').loadDirSync,
@@ -205,6 +206,74 @@ describe('bemhtml', function () {
});
});
});
+
+ describe('with option `exports`', function () {
+ it('must export ym only', function () {
+ var modules = {};
+ return build(['block("a").tag()("a")'], {
+ exports: { ym: true },
+ context: {
+ module: undefined,
+ modules: {
+ define: function (name, _, p) {
+ p(function (res) { modules[name] = res; });
+ }
+ }
+ }
+ })
+ .spread(function () {
+ modules.BEMHTML.apply({ block: 'a' })
+ .must.eql('');
+ });
+ });
+
+ it('must export commonjs only', function () {
+ var m = { exports: {} };
+ return build(['block("a").tag()("a")'], {
+ exports: { commonJS: true },
+ context: { module: m }
+ })
+ .spread(function () {
+ m.exports.BEMHTML.apply({ block: 'a' })
+ .must.eql('');
+ });
+ });
+
+ it('must export global only', function () {
+ var window = {};
+ return build(['block("a").tag()("a")'], {
+ exports: { globals: true },
+ context: { module: undefined, window: window }
+ })
+ .spread(function () {
+ window.BEMHTML.apply({ block: 'a' })
+ .must.eql('');
+ });
+ });
+
+ it('must export as ym and global', function () {
+ var modules = {}, window = {};
+ return build(['block("a").tag()("a")'], {
+ exports: { globals: 'force', ym: true },
+ context: {
+ module: undefined,
+ window: window,
+ modules: {
+ define: function (name, _, p) {
+ p(function (res) { modules[name] = res; });
+ }
+ }
+ }
+ })
+ .spread(function () {
+ modules.BEMHTML.apply({ block: 'a' })
+ .must.eql('', 'modules failed');
+ window.BEMHTML.apply({ block: 'a' })
+ .must.eql('', 'global failed');
+ });
+ });
+ // return build(templates, { exports: {globals: true, commonJS: true, ym: true} })
+ });
});
function build(templates, options) {
@@ -215,6 +284,7 @@ function build(templates, options) {
blocks: {},
bundle: {}
},
+ noEval = typeof options.context === 'object',
bundle, fileList;
// hack for mock-fs
@@ -230,12 +300,6 @@ function build(templates, options) {
scheme.blocks = templates;
}
- if (templates.length) {
- templates.forEach(function (item, i) {
- scheme.blocks['block-' + i + '.bemhtml.js'] = item;
- });
- }
-
mock(scheme);
bundle = new MockNode('bundle');
@@ -243,11 +307,15 @@ function build(templates, options) {
fileList.addFiles(loadDirSync('blocks'));
bundle.provideTechData('?.files', fileList);
- return bundle.runTechAndRequire(Tech, options)
+ return bundle[noEval ? 'runTech' : 'runTechAndRequire'](Tech, options)
.spread(function (res) {
var filename = bundle.resolvePath(bundle.unmaskTargetName(options.target || '?.bemhtml.js')),
str = fs.readFileSync(filename, 'utf-8');
+ if (noEval) {
+ res = safeEval(str, filename, options.context);
+ }
+
return [res, str];
});
}