Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tech): add exports option #178

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
coverage
npm-debug.log
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,17 @@ module.exports = function(config) {
* [в браузере](#Исполнение-шаблонов-в-браузере)
* [для сборки HTML](#Использование-шаблонов-для-сборки-html)

### Способы экспортирования бандла шаблонов
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Давай ещё добавим описание опции exports в api.ru.md.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Добавил


Существуют разные способы сборки:

* в виде 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/).
Expand Down Expand Up @@ -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); // <html>...</html>
```

* **С модульной системой 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: { /* ... */ } }),
Expand Down
19 changes: 19 additions & 0 deletions api.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ bemhtml
* [sourceSuffixes](#sourcesuffixes)
* [requires](#requires)
* [exportName](#exportname)
* [exports](#exports)
* [forceBaseTemplates](#forceBaseTemplates)
* [engineOptions](#engineoptions)
* [naming](#naming)
Expand Down Expand Up @@ -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`.
Expand Down
9 changes: 4 additions & 5 deletions lib/assets/bundle.jst
Original file line number Diff line number Diff line change
Expand Up @@ -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 }',<%});%>],
Expand All @@ -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 };
}
Expand Down
2 changes: 2 additions & 0 deletions lib/bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -24,6 +25,7 @@ exports.compile = function (code, options) {
return template(code, {
exportName: options.exportName,
requires: requires,
exports: options.exports,
commonJSModules: commonJSModules
});
});
Expand Down
3 changes: 3 additions & 0 deletions lib/templates/bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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 = [];
Expand All @@ -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),
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
11 changes: 9 additions & 2 deletions techs/bem-xjst.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,20 @@ var EOL = require('os').EOL,
*
* Compiles BEMXJST template files with BEMXJST translator and merges them into a single template bundle.<br/><br/>
*
* 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.
Expand Down Expand Up @@ -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);
},
Expand Down
1 change: 1 addition & 0 deletions techs/bemhtml.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
1 change: 1 addition & 0 deletions techs/bemtree.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
82 changes: 75 additions & 7 deletions test/techs/bemhtml.test.js
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -205,6 +206,74 @@ describe('bemhtml', function () {
});
});
});

describe('with option `exports`', function () {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Почему тесты не где-то в test/unit/*?

Хочется как можно меньше интеграционных тестов, если нет реальной необходимости.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кто ж знал, что тут есть разделение на интеграционные тесты ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@blond Я посмотрел туда. И ничо не понял.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Пробую перенести в отдельный файл, разбиение по --node, --browser — мешает

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('<a class="a"></a>');
});
});

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('<a class="a"></a>');
});
});

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('<a class="a"></a>');
});
});

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('<a class="a"></a>', 'modules failed');
window.BEMHTML.apply({ block: 'a' })
.must.eql('<a class="a"></a>', 'global failed');
});
});
// return build(templates, { exports: {globals: true, commonJS: true, ym: true} })
});
});

function build(templates, options) {
Expand All @@ -215,6 +284,7 @@ function build(templates, options) {
blocks: {},
bundle: {}
},
noEval = typeof options.context === 'object',
bundle, fileList;

// hack for mock-fs
Expand All @@ -230,24 +300,22 @@ 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');
fileList = new FileList();
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];
});
}