-
Notifications
You must be signed in to change notification settings - Fork 48
Notable changes between bem [email protected] and bem [email protected]
If you have 1.x templates there is a way to convert old templates to JS syntax: https://github.com/bem/bem-templates-converter
Install package:
npm install -g bemhtml-syntax
Convert old templates:
bemhtml-syntax --format bemhtml -Qr -i path/to/block.bemhtml -o path/to/block.bemhtml.js
To convert all templates at once and format the code according to your coding style (using jscs), use the command:
find *.blocks -name '*.bemhtml' -type f | while read f; do \
bemhtml-syntax --format bemhtml -Qr -i "${f}" -o "${f}.js"; jscs --fix "${f}.js";\
done
After checking the operability of new templates (.bemhtml.js), the old templates (.bemhtml) can be removed using:
find *.blocks -name '*.bemhtml' -type f -delete
Since 2.x
version there is no more templates compilation (and as a result much faster build time). Also execution performance increased.
var bemxjst = require('bem-xjst');
var template = bemxjst.compile('... your source code ...');
template.apply(/* ... your input data here ... */);
// Or even better:
template = bem.compile(function() {
block('b1').content()('yay');
});
template.apply({ block: 'b1' });
var bemxjst = require('bem-xjst');
var template = bemxjst.compile(function() {
block('main').tag()('body');
});
var html = template.apply({ block: 'main' });
// html = '<body class="page"></body>'
// Add more templates:
template = bem.compile(function() {
block('main').tag()('article');
});
html = template.apply({ block: 'main' });
// html = '<article class="page"></article>'
With new compiler came new responsibility, the following breaking (or just surprising) changes were made.
Contents:
- return values
this._
no moremode('')
no moreblock(...)
is required in all templatesthis._str
is goneelemMatch()
andthis.elem
changesapply()
behaves slightly differentapplyNext()
don't use guard-flags to prevent executing template in nested- Static objects shortcuts in mix, content, etc.
once()
helper to apply template just oncewrap()
helper for wrappingreplace()
helper for replacing- tag behaviour changed
Default (def()
) mode of templates MUST return a string now. This string will represent the result of template execution and will be added to the final HTML output.
block('b1').def()(function() {
return applyNext();
});
All helpers have finally migrated to this
from this._
. From now on this._
will be undefined
.
block('b1').content()(function() {
return this.extend({
block: 'b2'
}, {
tag: 'a'
});
});
Empty mode (mode('')
) is no longer supported. Template authors are advised to use oninit
instead:
oninit(function(exports) {
var proto = exports.BEMContext.prototype;
proto.cleverSubstring = function cleverSubstring() {
// ...
};
});
oninit
is called only once during template compilation.
In case (ONLY IN THIS CASE) where template author may need to do something for every block, one should use wildcard block (block('*')
):
// Will make all blocks have `a` tag
block('*').tag()('a');
// Will wrap all HTML tags in `{` and `}`
block('*').def()(function() {
return '{' + applyNext() + '}';
});
NOTE: This should be used only as the last resort method. The price for using block('*')
is reduced performance.
It is now required to specify block predicate (block('...')
) in every template.
VALID:
block('b1')(
tag()('a'),
content()({ block: 'b2' })
);
INVALID:
match(function() { return this.my.custom.match; }).tag()('a');
HTML chunks are no longer pushed to this._str
. If data needs to be flushed - special _flush
method should be added to the BEMContext
prototype.
oninit(function(exports) {
var proto = exports.BEMContext.prototype;
proto._flush = function _flush(string) {
// Send chunk directly to a client
// NOTE: This is just an example, no actual socket implementation
// is provided by bem-xjst
socket.send(string);
// `_flush()` MUST return string value:
// * returning input string will make `_flush` act as no-op
// * any returned string will be present in resulting HTML
return '';
};
});
Blocks with overridden def()
mode will be flushed in a single chunk (including their contents), unless .def().xjstOptions({ flush: true })
is used as a predicate.
Еxample of an element that forces flushing to stream:
oninit(function(exports, shared) {
var proto = shared.BEMContext.prototype;
proto.flush = function() {
return _buf.splice(0).join('');
};
var _buf = [];
proto._flush = function(chunk){
_buf.push(chunk);
return '';
};
});
// and
block('page').elem('flusher').def()(function(){ return this.flush(); });
ATM elemMatch()
can be used after .elem()
only since you can't test this.elem
outside of .elem()
.
.elemMatch(F)
later will be a synonym to .elem('*').match(F)
or a similar thing. See also #135.
Since a notable performance jump apply()
can't call custom modes in other blocks.
block('b2').mode('custom')('in b2'); // won't be called
block('b1').mode('custom')('in b1'); // will be called instead
block('b1').content()(function() {
return apply('custom', { block: 'b2' });
});
Also there is no more polymorphism in apply()
and you should always explicitly pass a first argument, e.g. 'default'
.
Every template now applies to the each block of the same name in nested tree when called in applyNext()
.
E.g. for template:
block('b1').content()(function() {
return {
elem: 'inner',
content: applyNext()
};
});
And BEMJSON:
{ block: 'b1', content: { block: 'b1' } }
Will result in:
<div class="b1"><div class="b1__inner"><div class="b1"><div class="b1__inner"></div></div></div></div>
Instead of:
<div class="b1"><div class="b1__inner"><div class="b1"></div></div></div>
Because new templates are not compiled, variables will share references to the same objects and that can look like a memory leak.
Just switch to function generator if you're not sure how somebody will redefine it.
For bemjson:
([
{ block: 'b1', mods: { a: true } },
{ block: 'b1' },
{ block: 'b1' }
]);
And templates:
// Don't do that!
block('b1')(
mix()([]), // Use function generator instead: `function() { return []; }`.
mix()(function () {
var res = applyNext();
res.push({block: 'b2'});
return res;
}),
attrs()([]), // Use function generator instead: `function() { return {}; }`.
mod('a', true).attrs()(function () {
var res = applyNext();
res.withMod = 'a';
return res;
})
);
You'll see unnecessary attribute withMod
and more duplicates in mixes for each next block:
<div class="b1 b1_a b2" withMod="a"></div>
<div class="b1 b2 b2" withMod="a"></div>
<div class="b1 b2 b2 b2" withMod="a"></div>
Literally, it guards template block from being applied several times.
NB: It behaves like an old applyNext()
.
Code like:
block('b1').def()(function() {
return applyNext();
});
now should be rewritten to:
block('b1').match(function() { return !this._guard; }).def()(function() {
return applyNext({_guard: true});
});
Or with once()
:
block('b1').once().def()(function() {
return applyNext();
});
once()
will be removed in later versions of bem-xjst
.
Literally, it sets guard flag into this.ctx
and applies context.
Sugar for def()
and applyCtx()
.
Code like:
block('b1').def()(function() {
return applyCtx({ block: 'wrapper', content: this.ctx });
});
can now be rewritten to:
block('b1').match(function() { return !this.ctx._guard; }).def()(function() {
this.ctx._guard = true;
return applyCtx({ block: 'wrapper', content: this.ctx });
});
Or with wrap()
helper:
block('b1').wrap()(function() {
return { block: 'wrapper', content: this.ctx };
});
block('b1').def()(function() {
return applyCtx({ block: 'replacer' });
});
block('b1').replace()(function() {
return { block: 'replacer' };
});
In BEMHTML 1.x tag
in templates has more priority than tag
in BEMJSON. In bemhtml 4.x is the opposite. NOTE: Yes, we know this is a bug and we already fixed it in 6.x.
bem-xjst 1.x template:
block b1 {
tag: 'a’
}
BEMJSON:
{ block: ‘b1’, tag: ‘b’ }
Result:
<a class=“b1”></a>
bem-xjst 4.x template:
block('b1').tag()('a');
bem-xjst 4.x result:
<b class="b1"></b>