├── docs ├── en │ ├── README.md │ ├── book.json │ ├── SUMMARY.md │ ├── 2-quick-start.md │ └── 1-about.md ├── ru │ ├── README.md │ ├── book.json │ ├── SUMMARY.md │ ├── 2-quick-start.md │ └── 1-about.md └── LANGS.md ├── bench ├── .gitignore ├── templates │ ├── basic.bemhtml │ └── basic.bemjson ├── prepare.sh ├── package.json ├── README.md ├── test.js └── lib │ └── compare.py ├── examples ├── source-maps │ ├── demo1 │ │ ├── b1.bemhtml.js │ │ ├── demo1.html │ │ └── demo1.js │ ├── demo3 │ │ ├── b3.bemhtml.js │ │ ├── demo3.html │ │ └── demo3.js │ ├── demo2 │ │ ├── demo2.html │ │ ├── demo2.js │ │ └── tmpls-with-sourcemap.bemhtml.js │ └── demo-in-nodejs │ │ ├── b1.bemhtml.js │ │ └── test-in-node.js └── simple-page │ ├── package.json │ ├── index.js │ ├── README.md │ ├── data.js │ ├── bemtree-templates.js │ └── bemhtml-templates.js ├── scripts └── update-authors.sh ├── migration ├── lib │ ├── transformers │ │ ├── __testfixtures__ │ │ │ ├── 0-func-to-simple-1.output.js │ │ │ ├── 0-func-to-simple-2.input.js │ │ │ ├── 0-func-to-simple-2.output.js │ │ │ ├── 2-def-must-return-something-2.input.js │ │ │ ├── 2-def-must-return-something-2.output.js │ │ │ ├── 8-chain-mix-to-chain-addmix-1.input.js │ │ │ ├── 8-chain-attrs-to-chain-addattrs-1.input.js │ │ │ ├── 8-chain-attrs-to-chain-addattrs-1.output.js │ │ │ ├── 8-chain-mix-to-chain-addmix-1.output.js │ │ │ ├── 7-once-to-def-1.input.js │ │ │ ├── 7-once-to-def-1.output.js │ │ │ ├── 2-no-more-this-underscore-1.output.js │ │ │ ├── 2-no-more-this-underscore-2.input.js │ │ │ ├── 2-no-more-this-underscore-2.output.js │ │ │ ├── 2-no-more-this-underscore-1.input.js │ │ │ ├── 8-mix-to-addmix-1.input.js │ │ │ ├── 5-api-changed-1.input.js │ │ │ ├── 8-attrs-to-addattrs-1.input.js │ │ │ ├── 8-attrs-to-addattrs-1.output.js │ │ │ ├── 8-mix-to-addmix-1.output.js │ │ │ ├── 5-api-changed-1.output.js │ │ │ ├── 0-func-to-simple-1.input.js │ │ │ ├── 0-func-to-simple-3.output.js │ │ │ ├── 5-elemmatch-to-elem-match-1.input.js │ │ │ ├── 7-thisisarray-to-arrayisarray-1.input.js │ │ │ ├── 7-thisisarray-to-arrayisarray-1.output.js │ │ │ ├── 5-elemmatch-to-elem-match-1.output.js │ │ │ ├── 5-elemmatch-to-elem-match-2.input.js │ │ │ ├── 5-elemmatch-to-elem-match-2.output.js │ │ │ ├── 7-xhtml-true-1.input.js │ │ │ ├── 2-no-empty-mode-1.input.js │ │ │ ├── 2-def-must-return-something-3.input.js │ │ │ ├── 2-def-must-return-something-1.input.js │ │ │ ├── 2-def-must-return-something-3.output.js │ │ │ ├── 8-chain-attrs-to-chain-addattrs-2.input.js │ │ │ ├── 2-no-empty-mode-1.output.js │ │ │ ├── 2-no-empty-mode-2.input.js │ │ │ ├── 2-no-empty-mode-2.output.js │ │ │ ├── 7-xhtml-true-1.output.js │ │ │ ├── 8-chain-attrs-to-chain-addattrs-2.output.js │ │ │ ├── 8-chain-mix-to-chain-addmix-2.input.js │ │ │ ├── 0-func-to-simple-3.input.js │ │ │ ├── 2-no-empty-mode-call-1.input.js │ │ │ ├── 7-xhtml-true-2.input.js │ │ │ ├── 8-chain-mix-to-chain-addmix-2.output.js │ │ │ ├── 7-xhtml-true-3.input.js │ │ │ ├── 7-xhtml-true-3.output.js │ │ │ ├── 2-no-empty-mode-call-1.output.js │ │ │ ├── 7-xhtml-true-2.output.js │ │ │ ├── 8-chain-js-to-chain-addjs-1.input.js │ │ │ ├── 8-js-to-addjs-1.input.js │ │ │ ├── 8-chain-js-to-chain-addjs-1.output.js │ │ │ ├── 8-js-to-addjs-1.output.js │ │ │ ├── 8-js-to-addjs-2.input.js │ │ │ ├── 8-js-to-addjs-2.output.js │ │ │ ├── 7-thisisarray-to-arrayisarray-2.input.js │ │ │ ├── 2-def-must-return-something-1.output.js │ │ │ ├── 7-thisisarray-to-arrayisarray-2.output.js │ │ │ ├── 8-attrs-to-addattrs-2.input.js │ │ │ ├── 0-dont-check-this-mods-1.output.js │ │ │ ├── 8-attrs-to-addattrs-2.output.js │ │ │ ├── 8-mix-to-addmix-2.input.js │ │ │ ├── 8-mix-to-addmix-2.output.js │ │ │ ├── 0-dont-check-this-elemmods-1.output.js │ │ │ ├── 3-apply-call-to-apply.output.js │ │ │ ├── 3-apply-call-to-apply.input.js │ │ │ ├── 0-dont-check-this-mods-1.input.js │ │ │ ├── 0-dont-check-this-elemmods-1.input.js │ │ │ ├── 0-arr-to-func-generator-1.input.js │ │ │ ├── 0-obj-to-func-generator.input.js │ │ │ ├── 7-mods-value-1.input.js │ │ │ ├── 7-mods-value-1.output.js │ │ │ ├── 2-def-must-return-something-5.input.js │ │ │ ├── 0-arr-to-func-generator-1.output.js │ │ │ ├── 2-def-must-return-something-5.output.js │ │ │ ├── 0-obj-to-func-generator.output.js │ │ │ ├── 2-def-must-return-something-4.input.js │ │ │ └── 2-def-must-return-something-4.output.js │ │ ├── 8-js-to-addjs.js │ │ ├── 8-mix-to-addmix.js │ │ ├── 0-html-entities.js │ │ ├── 8-attrs-to-addattrs.js │ │ ├── 7-thisisarray-to-arrayisarray.js │ │ ├── 7-once-to-def.js │ │ ├── 5-elemmatch-to-elem-match.js │ │ ├── 8-chain-js-to-chain-addjs.js │ │ ├── 8-chain-mix-to-chain-addmix.js │ │ ├── 0-dont-check-this-mods.js │ │ ├── 8-chain-attrs-to-chain-addattrs.js │ │ ├── 0-dont-check-this-elemmods.js │ │ ├── 0-func-to-simple.js │ │ ├── 5-api-changed.js │ │ ├── 2-no-empty-mode.js │ │ ├── 2-no-empty-mode-call.js │ │ ├── 2-no-more-this-underscore.js │ │ ├── 7-mods-value.js │ │ ├── 7-xhtml-true.js │ │ ├── 0-arr-to-func-generator.js │ │ ├── 0-obj-to-func-generator.js │ │ ├── 2-def-must-return-something.js │ │ └── 3-apply-call-to-apply.js │ ├── logger.js │ └── transformer.js ├── sample-config.json ├── package.json └── README.md ├── .gitignore ├── .editorconfig ├── book.json ├── PULL_REQUEST_TEMPLATE.md ├── test ├── api-apply-test.js ├── bemjson-content-test.js ├── modes-def-test.js ├── runtime-local-test.js ├── runtime-reapply-test.js ├── bemcontext-isshorttag-test.js ├── bemcontext-block-test.js ├── bemcontext-elem-test.js ├── bemcontext-extend-test.js ├── modes-custom-test.js ├── bemjson-tag-test.js ├── bemcontext-custom-error-logger-test.js ├── bemcontext-custom-fields-test.js ├── runtime-applyctx-test.js ├── runtime-bemcontext-test.js ├── bemcontext-elemmods-test.js ├── custom-naming-test.js ├── bemjson-elem-test.js ├── bemcontext-islast-test.js ├── modes-cls-test.js ├── modes-bem-test.js ├── bemcontext-generateid-test.js ├── bemjson-elemmods-test.js ├── bemcontext-identify-test.js ├── utils-isunquotedattr-test.js ├── bemcontext-issimple-test.js ├── modes-tag-test.js ├── modes-prependcontent-test.js ├── bemjson-values-test.js ├── modes-addmods-test.js ├── modes-block-test.js ├── bemjson-block-test.js ├── modes-attrs-test.js ├── modes-addmix-test.js ├── fixtures.js ├── modes-appendcontent-test.js ├── modes-content-test.js ├── modes-addelemmods-test.js ├── modes-wrap-test.js ├── modes-elem-test.js ├── modes-match-test.js ├── modes-js-test.js ├── modes-addattrs-test.js ├── modes-replace-test.js ├── bemjson-mods-test.js ├── bemjson-cls-test.js ├── modes-addjs-test.js ├── bemjson-bem-test.js ├── runtime-escaping-test.js ├── modes-mods-test.js ├── bemcontext-xmlescape-test.js ├── runtime-match-test.js ├── bemcontext-attrescape-test.js ├── bemcontext-jsattrescape-test.js ├── init-test.js ├── modes-extend-test.js ├── modes-elemmods-test.js ├── bemcontext-isfirst-test.js └── runtime-apply-test.js ├── .travis.yml ├── index.js ├── lib ├── bemxjst │ ├── error.js │ ├── class-builder.js │ └── context.js ├── bemtree │ ├── entity.js │ └── index.js └── bemhtml │ └── entity.js ├── runtime-lint ├── README.md └── stand.js ├── ISSUE_TEMPLATE.md ├── .github └── workflows │ └── node.js.yml ├── CONTRIBUTING.md ├── AUTHORS ├── .jscsrc └── bin └── bem-xjst /docs/en/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /docs/ru/README.md: -------------------------------------------------------------------------------- 1 | ../../README.ru.md -------------------------------------------------------------------------------- /bench/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /docs/en/book.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "./" 3 | } 4 | -------------------------------------------------------------------------------- /docs/ru/book.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "./" 3 | } 4 | -------------------------------------------------------------------------------- /docs/LANGS.md: -------------------------------------------------------------------------------- 1 | # Languages 2 | 3 | * [English](en/) 4 | * [Russian](ru/) -------------------------------------------------------------------------------- /examples/source-maps/demo1/b1.bemhtml.js: -------------------------------------------------------------------------------- 1 | block('b1').tag()('blah'); 2 | -------------------------------------------------------------------------------- /examples/source-maps/demo3/b3.bemhtml.js: -------------------------------------------------------------------------------- 1 | block('b3').tag()('blah'); 2 | -------------------------------------------------------------------------------- /examples/source-maps/demo1/demo1.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/source-maps/demo2/demo2.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/source-maps/demo3/demo3.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /bench/templates/basic.bemhtml: -------------------------------------------------------------------------------- 1 | block('b1').content()(function() { 2 | return '#' + applyNext() + '#'; 3 | }); 4 | -------------------------------------------------------------------------------- /scripts/update-authors.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | git log --reverse --format='%aN <%aE>' | sort | uniq -c | sort -bgr > AUTHORS 4 | 5 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/0-func-to-simple-1.output.js: -------------------------------------------------------------------------------- 1 | block('b').elem('e').tag()('span'); 2 | block('b').tag()('span'); 3 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/0-func-to-simple-2.input.js: -------------------------------------------------------------------------------- 1 | block('b').content()(function() { 2 | return [ 'span' ]; 3 | }); 4 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/0-func-to-simple-2.output.js: -------------------------------------------------------------------------------- 1 | block('b').content()(function() { 2 | return [ 'span' ]; 3 | }); 4 | -------------------------------------------------------------------------------- /bench/templates/basic.bemjson: -------------------------------------------------------------------------------- 1 | module.exports = ({ 2 | block: 'b1', 3 | content: [ 4 | { 5 | block: 'b2' 6 | } 7 | ] 8 | }); 9 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/2-def-must-return-something-2.input.js: -------------------------------------------------------------------------------- 1 | block('test').def()(function() { 2 | return 'text'; 3 | }); 4 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/2-def-must-return-something-2.output.js: -------------------------------------------------------------------------------- 1 | block('test').def()(function() { 2 | return 'text'; 3 | }); 4 | -------------------------------------------------------------------------------- /examples/source-maps/demo-in-nodejs/b1.bemhtml.js: -------------------------------------------------------------------------------- 1 | block('b1').tag()('blah'); 2 | block('b1').content()(function() { 3 | return this.ctx.a.b.c; 4 | }); 5 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-chain-mix-to-chain-addmix-1.input.js: -------------------------------------------------------------------------------- 1 | block('b').mix()(function() { 2 | return { block: 'mixed' }; 3 | }); 4 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-chain-attrs-to-chain-addattrs-1.input.js: -------------------------------------------------------------------------------- 1 | block('b').attrs()(function() { 2 | return { id: 'test' }; 3 | }); 4 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-chain-attrs-to-chain-addattrs-1.output.js: -------------------------------------------------------------------------------- 1 | block('b').addAttrs()(function() { 2 | return { id: 'test' }; 3 | }); 4 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-chain-mix-to-chain-addmix-1.output.js: -------------------------------------------------------------------------------- 1 | block('b').addMix()(function() { 2 | return { block: 'mixed' }; 3 | }); 4 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/7-once-to-def-1.input.js: -------------------------------------------------------------------------------- 1 | block('test').once()(function() { 2 | this.mods.once = 1; 3 | 4 | return applyNext(); 5 | }); 6 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/7-once-to-def-1.output.js: -------------------------------------------------------------------------------- 1 | block('test').def()(function() { 2 | this.mods.once = 1; 3 | 4 | return applyNext(); 5 | }); 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib/bem*/bundle.js 2 | /node_modules/ 3 | _book 4 | npm-debug.log 5 | /.idea/ 6 | /coverage/ 7 | /bench/bem-xjst-*/ 8 | /bench/dat-*/ 9 | /bench/web-data/ 10 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/2-no-more-this-underscore-1.output.js: -------------------------------------------------------------------------------- 1 | block('test').attrs()(function() { 2 | return this.extend(this.ctx.attrs, { test: 1 }); 3 | }); 4 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/2-no-more-this-underscore-2.input.js: -------------------------------------------------------------------------------- 1 | block('test').content()(function() { 2 | return this._.isSimpleTag(this.ctx.tag) || []; 3 | }); 4 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/2-no-more-this-underscore-2.output.js: -------------------------------------------------------------------------------- 1 | block('test').content()(function() { 2 | return this.isSimpleTag(this.ctx.tag) || []; 3 | }); 4 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/2-no-more-this-underscore-1.input.js: -------------------------------------------------------------------------------- 1 | block('test').attrs()(function() { 2 | return this._.extend(this.ctx.attrs, { test: 1 }); 3 | }); 4 | 5 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-mix-to-addmix-1.input.js: -------------------------------------------------------------------------------- 1 | block('b')( 2 | mix()(function() { 3 | return { block: 'mixed' }; 4 | }), 5 | 6 | content()('test') 7 | ); 8 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/5-api-changed-1.input.js: -------------------------------------------------------------------------------- 1 | var bemhtml = require('bem-xjst'); 2 | var templates = bemhtml.compile(function() {}) 3 | var html = templates.apply({}); 4 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-attrs-to-addattrs-1.input.js: -------------------------------------------------------------------------------- 1 | block('b')( 2 | attrs()(function() { 3 | return { id: 'test' }; 4 | }), 5 | 6 | content()('test') 7 | ); 8 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-attrs-to-addattrs-1.output.js: -------------------------------------------------------------------------------- 1 | block('b')( 2 | addAttrs()(function() { 3 | return { id: 'test' }; 4 | }), 5 | 6 | content()('test') 7 | ); 8 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-mix-to-addmix-1.output.js: -------------------------------------------------------------------------------- 1 | block('b')( 2 | addMix()(function() { 3 | return { block: 'mixed' }; 4 | }), 5 | 6 | content()('test') 7 | ); 8 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/5-api-changed-1.output.js: -------------------------------------------------------------------------------- 1 | var bemhtml = require('bem-xjst').bemhtml; 2 | var templates = bemhtml.compile(function() {}) 3 | var html = templates.apply({}); 4 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/0-func-to-simple-1.input.js: -------------------------------------------------------------------------------- 1 | block('b').elem('e').tag()(function() { 2 | return 'span'; 3 | }); 4 | block('b').tag()(function() { 5 | return 'span'; 6 | }); 7 | -------------------------------------------------------------------------------- /migration/sample-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "usetabs": true, 4 | "reuseWhitespace": true, 5 | "wrapColumn": 120, 6 | "quote": "single", 7 | "trailingComma": false 8 | } 9 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/0-func-to-simple-3.output.js: -------------------------------------------------------------------------------- 1 | block('link') 2 | .tag()('span'); 3 | 4 | block('link') 5 | .match(function() { return this.mods.disabled; }) 6 | .tag()('span'); 7 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/5-elemmatch-to-elem-match-1.input.js: -------------------------------------------------------------------------------- 1 | block('b') 2 | .elemMatch(function() { 3 | return this.elem !== 'name' && this.elem !== 'url'; 4 | }) 5 | .tag()('li'); 6 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/7-thisisarray-to-arrayisarray-1.input.js: -------------------------------------------------------------------------------- 1 | block('test').content()(function() { 2 | var ret = applyNext(); 3 | 4 | return this.isArray(ret) ? ret : [ ret ]; 5 | }); 6 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/7-thisisarray-to-arrayisarray-1.output.js: -------------------------------------------------------------------------------- 1 | block('test').content()(function() { 2 | var ret = applyNext(); 3 | 4 | return Array.isArray(ret) ? ret : [ ret ]; 5 | }); 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | 8 | [*.js] 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/5-elemmatch-to-elem-match-1.output.js: -------------------------------------------------------------------------------- 1 | block('b') 2 | .elem('*').match(function() { 3 | return this.elem !== 'name' && this.elem !== 'url'; 4 | }) 5 | .tag()('li'); 6 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/5-elemmatch-to-elem-match-2.input.js: -------------------------------------------------------------------------------- 1 | block('b')( 2 | elemMatch(function() { 3 | return this.elem !== 'name' && this.elem !== 'url'; 4 | }) 5 | .tag()('li') 6 | ); 7 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/5-elemmatch-to-elem-match-2.output.js: -------------------------------------------------------------------------------- 1 | block('b')( 2 | elem('*').match(function() { 3 | return this.elem !== 'name' && this.elem !== 'url'; 4 | }) 5 | .tag()('li') 6 | ); 7 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/7-xhtml-true-1.input.js: -------------------------------------------------------------------------------- 1 | var bemhtml = require('bem-xjst').bemhtml; 2 | 3 | var templates = bemhtml.compile(function() {}); 4 | 5 | var html = bemhtml.apply({ block: 'page' }); 6 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/2-no-empty-mode-1.input.js: -------------------------------------------------------------------------------- 1 | block('test')( 2 | mode('')(function() { 3 | return { test: 1 }; 4 | }), 5 | 6 | def()(function() { 7 | return apply(''); 8 | }) 9 | ); 10 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/2-def-must-return-something-3.input.js: -------------------------------------------------------------------------------- 1 | block('image').mod('name', 'maestro').def()(function() { 2 | var newCtx = this.ctx; 3 | newCtx.alt = 'maestro'; 4 | applyCtx(newCtx); 5 | }); 6 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "description":"bem-xjst", 3 | "root": "./docs", 4 | "plugins":[ 5 | "comment", 6 | "theme-api", 7 | "-sharing", 8 | "search", 9 | "collapsible-menu" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/2-def-must-return-something-1.input.js: -------------------------------------------------------------------------------- 1 | block('test').elem('e').def()(function() { 2 | this.mods = { test: 1 }; 3 | }); 4 | block('test').def()(function() { 5 | this.mods = { test: 1 }; 6 | }); 7 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/2-def-must-return-something-3.output.js: -------------------------------------------------------------------------------- 1 | block('image').mod('name', 'maestro').def()(function() { 2 | var newCtx = this.ctx; 3 | newCtx.alt = 'maestro'; 4 | return applyCtx(newCtx); 5 | }); 6 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-chain-attrs-to-chain-addattrs-2.input.js: -------------------------------------------------------------------------------- 1 | block('test').attrs()(function() { 2 | var attrs = this.ctx.attrs; 3 | 4 | attrs = this.extend(attrs, { id: 'test' }); 5 | 6 | return attrs; 7 | }); 8 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/2-no-empty-mode-1.output.js: -------------------------------------------------------------------------------- 1 | block('test')( 2 | mode('custom-mode')(function() { 3 | return { test: 1 }; 4 | }), 5 | 6 | def()(function() { 7 | return apply(''); 8 | }) 9 | ); 10 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/2-no-empty-mode-2.input.js: -------------------------------------------------------------------------------- 1 | block('test')( 2 | mode('custom')(function() { 3 | return { test: 1 }; 4 | }), 5 | 6 | def()(function() { 7 | return apply('custom'); 8 | }) 9 | ); 10 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/2-no-empty-mode-2.output.js: -------------------------------------------------------------------------------- 1 | block('test')( 2 | mode('custom')(function() { 3 | return { test: 1 }; 4 | }), 5 | 6 | def()(function() { 7 | return apply('custom'); 8 | }) 9 | ); 10 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/7-xhtml-true-1.output.js: -------------------------------------------------------------------------------- 1 | var bemhtml = require('bem-xjst').bemhtml; 2 | 3 | var templates = bemhtml.compile(function() {}, { 4 | xhtml: true 5 | }); 6 | 7 | var html = bemhtml.apply({ block: 'page' }); 8 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-chain-attrs-to-chain-addattrs-2.output.js: -------------------------------------------------------------------------------- 1 | block('test').addAttrs()(function() { 2 | var attrs = this.ctx.attrs; 3 | 4 | attrs = this.extend(attrs, { id: 'test' }); 5 | 6 | return attrs; 7 | }); 8 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-chain-mix-to-chain-addmix-2.input.js: -------------------------------------------------------------------------------- 1 | block('test').mix()(function() { 2 | // TODO: rewrite push() 3 | var mix = this.ctx.mix; 4 | 5 | mix.push({ block: 'mixed' }); 6 | 7 | return mix; 8 | }); 9 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/0-func-to-simple-3.input.js: -------------------------------------------------------------------------------- 1 | block('link') 2 | .tag()(function() { return 'span'; }); 3 | 4 | block('link') 5 | .match(function() { return this.mods.disabled; }) 6 | .tag()(function() { return 'span'; }); 7 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/2-no-empty-mode-call-1.input.js: -------------------------------------------------------------------------------- 1 | block('test')( 2 | mode('')(function() { 3 | return { test: 1 }; 4 | }), 5 | 6 | def()(function() { 7 | return apply('', { test: 1 }); 8 | }) 9 | ); 10 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/7-xhtml-true-2.input.js: -------------------------------------------------------------------------------- 1 | var bemhtml = require('bem-xjst').bemhtml; 2 | 3 | var templates = bemhtml.compile(function() {}, { 4 | someOption: 1 5 | }); 6 | 7 | var html = bemhtml.apply({ block: 'page' }); 8 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-chain-mix-to-chain-addmix-2.output.js: -------------------------------------------------------------------------------- 1 | block('test').addMix()(function() { 2 | // TODO: rewrite push() 3 | var mix = this.ctx.mix; 4 | 5 | mix.push({ block: 'mixed' }); 6 | 7 | return mix; 8 | }); 9 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/7-xhtml-true-3.input.js: -------------------------------------------------------------------------------- 1 | var bemhtml = require('bem-xjst').bemhtml; 2 | 3 | var templates = bemhtml.compile(function() {}, { 4 | test: 1, 5 | xhtml: false 6 | }); 7 | 8 | var html = bemhtml.apply({ block: 'page' }); 9 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/7-xhtml-true-3.output.js: -------------------------------------------------------------------------------- 1 | var bemhtml = require('bem-xjst').bemhtml; 2 | 3 | var templates = bemhtml.compile(function() {}, { 4 | test: 1, 5 | xhtml: true 6 | }); 7 | 8 | var html = bemhtml.apply({ block: 'page' }); 9 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/2-no-empty-mode-call-1.output.js: -------------------------------------------------------------------------------- 1 | block('test')( 2 | mode('')(function() { 3 | return { test: 1 }; 4 | }), 5 | 6 | def()(function() { 7 | return apply('custom-mode', { test: 1 }); 8 | }) 9 | ); 10 | 11 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | ### Changes proposed in this pull request 4 | 5 | - 6 | - 7 | - 8 | 9 | ### Checklist 10 | 11 | - [ ] Documentation changed 12 | - [ ] Tests added 13 | - [ ] Benchmark checked 14 | 15 | 16 | ### Benchmark result 17 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/7-xhtml-true-2.output.js: -------------------------------------------------------------------------------- 1 | var bemhtml = require('bem-xjst').bemhtml; 2 | 3 | var templates = bemhtml.compile(function() {}, { 4 | someOption: 1, 5 | xhtml: true 6 | }); 7 | 8 | var html = bemhtml.apply({ block: 'page' }); 9 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-chain-js-to-chain-addjs-1.input.js: -------------------------------------------------------------------------------- 1 | block('test').js()(function() { 2 | // TODO: ololo 3 | var js = this.ctx.js; 4 | 5 | js = { data: 'lol' }; 6 | 7 | return js; 8 | }); 9 | 10 | block('test').js()(true); 11 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-js-to-addjs-1.input.js: -------------------------------------------------------------------------------- 1 | block('b')( 2 | js()(function() { 3 | return { data: 'lol' }; 4 | }), 5 | 6 | content()('test') 7 | ); 8 | 9 | block('b')( 10 | js()(true), 11 | 12 | content()('test') 13 | ); 14 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-chain-js-to-chain-addjs-1.output.js: -------------------------------------------------------------------------------- 1 | block('test').addJs()(function() { 2 | // TODO: ololo 3 | var js = this.ctx.js; 4 | 5 | js = { data: 'lol' }; 6 | 7 | return js; 8 | }); 9 | 10 | block('test').addJs()(true); 11 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-js-to-addjs-1.output.js: -------------------------------------------------------------------------------- 1 | block('b')( 2 | addJs()(function() { 3 | return { data: 'lol' }; 4 | }), 5 | 6 | content()('test') 7 | ); 8 | 9 | block('b')( 10 | addJs()(true), 11 | 12 | content()('test') 13 | ); 14 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-js-to-addjs-2.input.js: -------------------------------------------------------------------------------- 1 | block('test')( 2 | js()(function() { 3 | // TODO: ololo 4 | var js = this.ctx.js; 5 | 6 | js = { data: 'lol' }; 7 | 8 | return js; 9 | }), 10 | 11 | content()('test') 12 | ); 13 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-js-to-addjs-2.output.js: -------------------------------------------------------------------------------- 1 | block('test')( 2 | addJs()(function() { 3 | // TODO: ololo 4 | var js = this.ctx.js; 5 | 6 | js = { data: 'lol' }; 7 | 8 | return js; 9 | }), 10 | 11 | content()('test') 12 | ); 13 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/7-thisisarray-to-arrayisarray-2.input.js: -------------------------------------------------------------------------------- 1 | block('test').mix()(function() { 2 | var mix = this.mix; 3 | 4 | if (!this.isArray(mix)) 5 | mix = [ this.mix ]; 6 | 7 | mix.push({ block: 'mixed' }); 8 | 9 | return mix; 10 | }); 11 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/2-def-must-return-something-1.output.js: -------------------------------------------------------------------------------- 1 | block('test').elem('e').def()(function() { 2 | this.mods = { test: 1 }; 3 | return applyNext(); 4 | }); 5 | block('test').def()(function() { 6 | this.mods = { test: 1 }; 7 | return applyNext(); 8 | }); 9 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/7-thisisarray-to-arrayisarray-2.output.js: -------------------------------------------------------------------------------- 1 | block('test').mix()(function() { 2 | var mix = this.mix; 3 | 4 | if (!Array.isArray(mix)) 5 | mix = [ this.mix ]; 6 | 7 | mix.push({ block: 'mixed' }); 8 | 9 | return mix; 10 | }); 11 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-attrs-to-addattrs-2.input.js: -------------------------------------------------------------------------------- 1 | block('test')( 2 | attrs()(function() { 3 | var attrs = this.ctx.attrs; 4 | 5 | attrs = this.extend(attrs, { id: 'test' }); 6 | 7 | return attrs; 8 | }), 9 | 10 | content()('test') 11 | ); 12 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/0-dont-check-this-mods-1.output.js: -------------------------------------------------------------------------------- 1 | block('b1').elem('e').def()(function() { 2 | var some = this.mods.some; 3 | 4 | return some; 5 | }); 6 | block('b1').def()(function() { 7 | var some = this.mods.some; 8 | 9 | return some; 10 | }); 11 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-attrs-to-addattrs-2.output.js: -------------------------------------------------------------------------------- 1 | block('test')( 2 | addAttrs()(function() { 3 | var attrs = this.ctx.attrs; 4 | 5 | attrs = this.extend(attrs, { id: 'test' }); 6 | 7 | return attrs; 8 | }), 9 | 10 | content()('test') 11 | ); 12 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-mix-to-addmix-2.input.js: -------------------------------------------------------------------------------- 1 | block('test')( 2 | mix()(function() { 3 | // TODO: rewrite push 4 | var mix = this.ctx.mix; 5 | 6 | mix.push({ block: 'mixed' }); 7 | 8 | return mix; 9 | }), 10 | 11 | content()('test') 12 | ); 13 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/8-mix-to-addmix-2.output.js: -------------------------------------------------------------------------------- 1 | block('test')( 2 | addMix()(function() { 3 | // TODO: rewrite push 4 | var mix = this.ctx.mix; 5 | 6 | mix.push({ block: 'mixed' }); 7 | 8 | return mix; 9 | }), 10 | 11 | content()('test') 12 | ); 13 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/0-dont-check-this-elemmods-1.output.js: -------------------------------------------------------------------------------- 1 | block('b1').def()(function() { 2 | var some = this.elemMods.some; 3 | 4 | return some; 5 | }); 6 | block('b1').elem('e').def()(function() { 7 | var some = this.elemMods.some; 8 | 9 | return some; 10 | }); 11 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/3-apply-call-to-apply.output.js: -------------------------------------------------------------------------------- 1 | var bemhtml = require('bem-xjst').bemhtml; 2 | 3 | var templates = bemhtml.compile(function() {}); 4 | 5 | var html = bemhtml.apply({ block: 'page' }); 6 | 7 | var apply = bemhtml.apply; 8 | 9 | apply({ block: 'page' }); 10 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/3-apply-call-to-apply.input.js: -------------------------------------------------------------------------------- 1 | var bemhtml = require('bem-xjst').bemhtml; 2 | 3 | var templates = bemhtml.compile(function() {}); 4 | 5 | var html = bemhtml.apply.call({ block: 'page' }); 6 | 7 | var apply = bemhtml.apply; 8 | 9 | apply.call({ block: 'page' }); 10 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/0-dont-check-this-mods-1.input.js: -------------------------------------------------------------------------------- 1 | block('b1').elem('e').def()(function() { 2 | var some = this.mods && this.mods.some; 3 | 4 | return some; 5 | }); 6 | block('b1').def()(function() { 7 | var some = this.mods && this.mods.some; 8 | 9 | return some; 10 | }); 11 | -------------------------------------------------------------------------------- /docs/ru/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Введение](README.md) 4 | * [О bem-xjst](1-about.md) 5 | * [Быстрый старт](2-quick-start.md) 6 | * [API](3-api.md) 7 | * [Входные данные — BEMJSON](4-data.md) 8 | * [Синтаксис шаблонов](5-templates-syntax.md) 9 | * [Что доступно в теле шаблона?](6-templates-context.md) 10 | * [Runtime](7-runtime.md) -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/0-dont-check-this-elemmods-1.input.js: -------------------------------------------------------------------------------- 1 | block('b1').def()(function() { 2 | var some = this.elemMods && this.elemMods.some; 3 | 4 | return some; 5 | }); 6 | block('b1').elem('e').def()(function() { 7 | var some = this.elemMods && this.elemMods.some; 8 | 9 | return some; 10 | }); 11 | -------------------------------------------------------------------------------- /docs/en/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Introduction](README.md) 4 | * [About bem-xjst](1-about.md) 5 | * [Quick start](2-quick-start.md) 6 | * [API](3-api.md) 7 | * [Input data — BEMJSON](4-data.md) 8 | * [Template syntax](5-templates-syntax.md) 9 | * [What is available in the template body?](6-templates-context.md) 10 | * [Runtime](7-runtime.md) 11 | -------------------------------------------------------------------------------- /test/api-apply-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var bemhtml = require('../').bemhtml; 3 | 4 | describe('API apply', function() { 5 | it('should support apply', function() { 6 | var templates = bemhtml.compile(); 7 | var html = templates.apply({ block: 'b' }); 8 | assert.equal(html, '
'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | - 0.12 5 | - 4 6 | - 5 7 | - 6 8 | - 7 9 | - 8 10 | 11 | env: 12 | global: 13 | - ISTANBUL_COVERAGE: yes 14 | 15 | after_success: 16 | - npm i coveralls 17 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && echo "Coverage data was sent to coveralls!" 18 | -------------------------------------------------------------------------------- /examples/source-maps/demo1/demo1.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | bemxjst = require('../../../').bemhtml, 3 | tmpl = 'b1.bemhtml.js', 4 | bundle = 'bundle.bemhtml.js'; 5 | 6 | var result = bemxjst.generate(fs.readFileSync(tmpl, 'utf8'), { 7 | to: bundle, 8 | sourceMap: { from: tmpl } 9 | }); 10 | 11 | fs.writeFileSync(bundle, result); 12 | -------------------------------------------------------------------------------- /test/bemjson-content-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('BEMJSON content', function() { 5 | it('should render block by default as div', function () { 6 | test(function() {}, 7 | [ { content: 'Hello, bemhtml!' } ], 8 | '
Hello, bemhtml!
'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /bench/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rev1=$1; 4 | rev2=$2; 5 | 6 | get_revision(){ 7 | if [ ! -d "bem-xjst-$1" ]; then 8 | curl https://codeload.github.com/bem/bem-xjst/zip/$1 > $1.zip && unzip $1.zip && rm $1.zip 9 | cd bem-xjst-$1 && npm i && npm run make && cd ../ 10 | fi 11 | } 12 | 13 | get_revision "$rev1" 14 | get_revision "$rev2" 15 | -------------------------------------------------------------------------------- /examples/source-maps/demo2/demo2.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | bemxjst = require('../../../').bemhtml, 3 | tmpl = 'tmpls-with-sourcemap.bemhtml.js', 4 | bundle = 'bundle.bemhtml.js'; 5 | 6 | var result = bemxjst.generate(fs.readFileSync(tmpl, 'utf8'), { 7 | to: bundle, 8 | sourceMap: { from: tmpl } 9 | }); 10 | 11 | fs.writeFileSync(bundle, result); 12 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Compiler = require('./lib/compiler'); 2 | var _cache = {}; 3 | 4 | function getEngine(engineName) { 5 | var engine = _cache[engineName]; 6 | 7 | return engine || (engine = new Compiler(engineName)); 8 | }; 9 | 10 | 11 | module.exports = { 12 | get bemtree() { return getEngine('bemtree'); }, 13 | get bemhtml() { return getEngine('bemhtml'); } 14 | }; 15 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/0-arr-to-func-generator-1.input.js: -------------------------------------------------------------------------------- 1 | block('b').mix()(['abs', 'abc']); 2 | block('b').elem('e').mix()(['test']); 3 | block('b').mod('mn', 'mv').elem('e').mix()(['test']); 4 | block('b')( 5 | mix()([0,1,2]) 6 | ); 7 | 8 | block('b')( 9 | mix()(function() { return []; }) 10 | ); 11 | 12 | block('b')( 13 | content()([]) 14 | ); 15 | -------------------------------------------------------------------------------- /test/modes-def-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var bemhtml = require('./fixtures')('bemhtml'); 3 | 4 | describe('Modes def', function() { 5 | it('should throw error when args passed to def mode', function() { 6 | assert.throws(function() { 7 | bemhtml.compile(function() { 8 | block('b1').def('blah'); 9 | }); 10 | }); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/0-obj-to-func-generator.input.js: -------------------------------------------------------------------------------- 1 | block('b').attrs()({ id: 'attrs' }); 2 | block('b').elem('e').js()({ id: 'js-test' }); 3 | block('b')( 4 | js()({ id: 'js-test2' }) 5 | ); 6 | block('b')( 7 | attrs()({ id: 'attrs-test' }) 8 | ); 9 | block('b')( 10 | mix()({ block: 'a' }) 11 | ); 12 | block('b')( 13 | elem('e').mix()({ block: 'a' }) 14 | ); 15 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/7-mods-value-1.input.js: -------------------------------------------------------------------------------- 1 | block('a').mod('m', 1).content()('number'); 2 | block('a')( 3 | mod('m', 1).content()('number') 4 | ); 5 | 6 | block('a').elem('e').elemMod('m', 1).content()('number'); 7 | block('a').elem('e')( 8 | elemMod('m', 1).content()('number') 9 | ); 10 | block('a')( 11 | elem('e').elemMod('m', 1).content()('number') 12 | ); 13 | -------------------------------------------------------------------------------- /migration/lib/logger.js: -------------------------------------------------------------------------------- 1 | module.exports = function logger(opts) { 2 | 'use strict'; 3 | var file = opts.file; 4 | var path = opts.path; 5 | var start = path.loc.start; 6 | 7 | console.warn([ 8 | 'BEM-XJST WARNING:', 9 | opts.descr, 10 | [ file.path, start.line, start.column ].join(':'), 11 | file.source.slice(path.start - 5, path.end + 15), 12 | '\n' 13 | ].join('\n>>>> ')); 14 | }; 15 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/7-mods-value-1.output.js: -------------------------------------------------------------------------------- 1 | block('a').mod('m', '1').content()('number'); 2 | block('a')( 3 | mod('m', '1').content()('number') 4 | ); 5 | 6 | block('a').elem('e').elemMod('m', '1').content()('number'); 7 | block('a').elem('e')( 8 | elemMod('m', '1').content()('number') 9 | ); 10 | block('a')( 11 | elem('e').elemMod('m', '1').content()('number') 12 | ); 13 | -------------------------------------------------------------------------------- /examples/source-maps/demo3/demo3.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | bemxjst = require('../../../').bemhtml, 3 | tmpl = 'b3.bemhtml.js', 4 | bundle = 'bundle.bemhtml.js'; 5 | 6 | var result = bemxjst.generate(fs.readFileSync(tmpl, 'utf8'), { 7 | to: bundle, 8 | sourceMap: { 9 | prev: 'block(\'a\').tag()(\'a\');', 10 | from: tmpl 11 | }, 12 | }); 13 | 14 | fs.writeFileSync(bundle, result); 15 | -------------------------------------------------------------------------------- /lib/bemxjst/error.js: -------------------------------------------------------------------------------- 1 | function BEMXJSTError(msg, func) { 2 | this.name = 'BEMXJSTError'; 3 | this.message = msg; 4 | 5 | if (Error.captureStackTrace) 6 | Error.captureStackTrace(this, func || this.constructor); 7 | else 8 | this.stack = (new Error()).stack; 9 | } 10 | 11 | BEMXJSTError.prototype = Object.create(Error.prototype); 12 | BEMXJSTError.prototype.constructor = BEMXJSTError; 13 | 14 | exports.BEMXJSTError = BEMXJSTError; 15 | -------------------------------------------------------------------------------- /test/runtime-local-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('Runtime local()', function() { 5 | it('should support local()', function() { 6 | test(function() { 7 | block('b1').content()(function() { 8 | return local({ tmp: 'b2' })(function() { 9 | return this.tmp; 10 | }); 11 | }); 12 | }, { block: 'b1' }, '
b2
'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /examples/simple-page/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-page", 3 | "version": "1.0.0", 4 | "description": "bem-xjst example: simple page", 5 | "main": "index.js", 6 | "keywords": [ 7 | "BEMTREE", 8 | "BEMHTML", 9 | "BEMJSON", 10 | "template engine", 11 | "bem-xjst example" 12 | ], 13 | "author": "Slava Oliyanchuk (http://miripiruni.org/)", 14 | "license": "MIT", 15 | "dependencies": { 16 | "bem-xjst": "^6.4.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/2-def-must-return-something-5.input.js: -------------------------------------------------------------------------------- 1 | block('username').def()(function() { 2 | var size = this.ctx.size || 3; 3 | 4 | applyCtx([ 5 | { 6 | block: 'font', 7 | mods: { color: 'accent' }, 8 | size: size, 9 | content: this.ctx.content[0] 10 | }, 11 | { 12 | block: 'font', 13 | color: '#000000', 14 | size: size, 15 | content: this.ctx.content.substr(1) 16 | } 17 | ]); 18 | }); 19 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/0-arr-to-func-generator-1.output.js: -------------------------------------------------------------------------------- 1 | block('b').mix()(function () { 2 | return ['abs', 'abc']; 3 | }); 4 | block('b').elem('e').mix()(function () { 5 | return ['test']; 6 | }); 7 | block('b').mod('mn', 'mv').elem('e').mix()(function () { 8 | return ['test']; 9 | }); 10 | block('b')( 11 | mix()(function () { 12 | return [0,1,2]; 13 | }) 14 | ); 15 | 16 | block('b')( 17 | mix()(function() { return []; }) 18 | ); 19 | 20 | block('b')( 21 | content()(function () { 22 | return []; 23 | }) 24 | ); 25 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/2-def-must-return-something-5.output.js: -------------------------------------------------------------------------------- 1 | block('username').def()(function() { 2 | var size = this.ctx.size || 3; 3 | 4 | return applyCtx([ 5 | { 6 | block: 'font', 7 | mods: { color: 'accent' }, 8 | size: size, 9 | content: this.ctx.content[0] 10 | }, 11 | { 12 | block: 'font', 13 | color: '#000000', 14 | size: size, 15 | content: this.ctx.content.substr(1) 16 | } 17 | ]); 18 | }); 19 | -------------------------------------------------------------------------------- /examples/simple-page/index.js: -------------------------------------------------------------------------------- 1 | var bemxjst = require('bem-xjst'); 2 | var data = require('./data'); 3 | 4 | // In this section we convert JSON from backend to BEMJSON 5 | var bemtreeRuntime = bemxjst 6 | .bemtree 7 | .compile(require('./bemtree-templates')); 8 | var bemjson = bemtreeRuntime.apply({ block: 'root', data: data }); 9 | 10 | // In this section we convert BEMJSON to HTML 11 | var bemhtmlRuntime = bemxjst 12 | .bemhtml 13 | .compile(require('./bemhtml-templates'), { xhtml: false }); 14 | var html = bemhtmlRuntime.apply(bemjson); 15 | 16 | console.log(html); 17 | -------------------------------------------------------------------------------- /migration/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bem-xjst-static-analyzer", 3 | "description": "bem-xjst templates analyzer. Can be used as static linter or migration tool.", 4 | "version": "1.0.0", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "./node_modules/.bin/jest --bail" 8 | }, 9 | "author": "Slava Oliyanchuk (http://miripiruni.org/)", 10 | "license": "ISC", 11 | "dependencies": { 12 | "jest": "^15.1.1", 13 | "jscodeshift": "^0.3.29", 14 | "lodash.get": "^4.4.2", 15 | "yargs": "^6.3.0" 16 | }, 17 | "devDependencies": {} 18 | } 19 | -------------------------------------------------------------------------------- /test/runtime-reapply-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('Runtime this.reapply()', function() { 5 | it('should support this.reapply()', function() { 6 | test(function() { 7 | block('b1').content()(function() { 8 | this.wtf = 'fail'; 9 | return this.reapply({ block: 'b2' }); 10 | }); 11 | 12 | block('b2').content()(function() { 13 | return this.wtf || 'ok'; 14 | }); 15 | }, 16 | { block: 'b1' }, 17 | '
<div class="b2">ok</div>
'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/0-obj-to-func-generator.output.js: -------------------------------------------------------------------------------- 1 | block('b').attrs()(function () { 2 | return { id: 'attrs' }; 3 | }); 4 | block('b').elem('e').js()(function () { 5 | return { id: 'js-test' }; 6 | }); 7 | block('b')( 8 | js()(function () { 9 | return { id: 'js-test2' }; 10 | }) 11 | ); 12 | block('b')( 13 | attrs()(function () { 14 | return { id: 'attrs-test' }; 15 | }) 16 | ); 17 | block('b')( 18 | mix()(function () { 19 | return { block: 'a' }; 20 | }) 21 | ); 22 | block('b')( 23 | elem('e').mix()(function () { 24 | return { block: 'a' }; 25 | }) 26 | ); 27 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/2-def-must-return-something-4.input.js: -------------------------------------------------------------------------------- 1 | block('table').mod('theme', 'grey')( 2 | elem('cell').elemMod('type', 'head').def()(function() { 3 | var defaultStyles = { 4 | width: 'width: 180px;', 5 | color: 'color: #605d5d;' 6 | }; 7 | 8 | var defaultStylesConcated = ''; 9 | Object.keys(defaultStyles).map(function() { 10 | defaultStylesConcated = defaultStylesConcated + defaultStyles[style]; 11 | }); 12 | 13 | var style = defaultStylesConcated + (this.ctx.style || ''); 14 | 15 | return applyNext({ 16 | 'ctx.style': style 17 | }); 18 | }) 19 | ); 20 | -------------------------------------------------------------------------------- /migration/lib/transformers/__testfixtures__/2-def-must-return-something-4.output.js: -------------------------------------------------------------------------------- 1 | block('table').mod('theme', 'grey')( 2 | elem('cell').elemMod('type', 'head').def()(function() { 3 | var defaultStyles = { 4 | width: 'width: 180px;', 5 | color: 'color: #605d5d;' 6 | }; 7 | 8 | var defaultStylesConcated = ''; 9 | Object.keys(defaultStyles).map(function() { 10 | defaultStylesConcated = defaultStylesConcated + defaultStyles[style]; 11 | }); 12 | 13 | var style = defaultStylesConcated + (this.ctx.style || ''); 14 | 15 | return applyNext({ 16 | 'ctx.style': style 17 | }); 18 | }) 19 | ); 20 | -------------------------------------------------------------------------------- /bench/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bem-xjst-bench", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "Benchmarks for bem-xjst", 6 | "main": "run.js", 7 | "scripts": { 8 | "postinstall": "cd ./node_modules/bem-xjst && npm install && npm run make", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": { 14 | "bem-xjst": "https://github.com/bem/bem-xjst.git#4d90404", 15 | "benchmark": "https://github.com/miripiruni/benchmark.js.git", 16 | "browserify": "^11.0.0", 17 | "microtime": "^2.0.0", 18 | "yargs": "^3.15.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /migration/lib/transformers/8-js-to-addjs.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var Transformer = require('../transformer'); 3 | var t = new Transformer(); 4 | 5 | module.exports = function(file, api, opts) { 6 | t.description = 'From v8.x you should use addJs() instead of js()'; 7 | 8 | t.find = function(file, j) { 9 | return j(file.source) 10 | .find(j.CallExpression, { callee: { type: 'Identifier', name: 'js' } }); 11 | }; 12 | 13 | t.replace = function(ret, j) { 14 | return ret.replaceWith(function(p) { 15 | return j.callExpression(j.identifier('addJs'), []); 16 | }); 17 | }; 18 | 19 | return t.run(file, api, opts); 20 | }; 21 | -------------------------------------------------------------------------------- /migration/lib/transformers/8-mix-to-addmix.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var Transformer = require('../transformer'); 3 | var t = new Transformer(); 4 | 5 | module.exports = function(file, api, opts) { 6 | t.description = 'From v8.x you should use addMix() instead of mix()'; 7 | 8 | t.find = function(file, j) { 9 | return j(file.source) 10 | .find(j.CallExpression, { callee: { type: 'Identifier', name: 'mix' } }); 11 | }; 12 | 13 | t.replace = function(ret, j) { 14 | return ret.replaceWith(function(p) { 15 | return j.callExpression(j.identifier('addMix'), []); 16 | }); 17 | }; 18 | 19 | return t.run(file, api, opts); 20 | }; 21 | -------------------------------------------------------------------------------- /migration/lib/transformers/0-html-entities.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var Transformer = require('../transformer'); 3 | var t = new Transformer(); 4 | 5 | module.exports = function(file, api, opts) { 6 | t.description = 'Use UTF-8 symbols instead of HTML entities. If you turn on escaping this code will be broken. With non visible UTF-8 symbols like non breaking space you can leave verbose comment in code.'; 7 | 8 | t.find = function(file, j) { 9 | return j(file.source) 10 | .find(j.Literal) 11 | .filter(function(p) { 12 | return /\&[\w\D]+;/.test(p.value.value); 13 | }); 14 | }; 15 | 16 | return t.run(file, api, opts); 17 | }; 18 | -------------------------------------------------------------------------------- /test/bemcontext-isshorttag-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var compile = fixtures.compile; 3 | 4 | describe('BEMContext this.isShortTag(str)', function() { 5 | var bemhtml; 6 | 7 | before(function() { 8 | bemhtml = compile(function() { 9 | block('b').def()(function() { 10 | return this.isShortTag(this.ctx.tag); 11 | }); 12 | }); 13 | }); 14 | 15 | it('should return true for br', function() { 16 | bemhtml.apply({ block: 'b', tag: 'br' }).should.equal(true); 17 | }); 18 | 19 | it('should return false for form', function() { 20 | bemhtml.apply({ block: 'b', tag: 'form' }).should.equal(false); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /migration/lib/transformers/8-attrs-to-addattrs.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var Transformer = require('../transformer'); 3 | var t = new Transformer(); 4 | 5 | module.exports = function(file, api, opts) { 6 | t.description = 'From v8.x you should use addAttrs() instead of attrs()'; 7 | 8 | t.find = function(file, j) { 9 | return j(file.source) 10 | .find(j.CallExpression, { callee: { type: 'Identifier', name: 'attrs' } }); 11 | }; 12 | 13 | t.replace = function(ret, j) { 14 | return ret 15 | .replaceWith(function(p) { 16 | return j.callExpression(j.identifier('addAttrs'), []); 17 | }); 18 | }; 19 | 20 | return t.run(file, api, opts); 21 | }; 22 | -------------------------------------------------------------------------------- /examples/source-maps/demo-in-nodejs/test-in-node.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var bemxjst = require('../../../'); 3 | const BUNDLE = 'bundle.bemhtml.js'; 4 | const TMPL = 'b1.bemhtml.js'; 5 | 6 | var bundle = bemxjst.bemhtml.generate(fs.readFileSync(TMPL, 'utf8'), { 7 | to: BUNDLE, 8 | sourceMap: { from: TMPL, include: false } 9 | }); 10 | 11 | // Create bundle with bem-xjst enginge and templates 12 | fs.writeFileSync(BUNDLE, 13 | 'require(\'source-map-support\').install();' + 14 | bundle.content + 15 | 'bemhtml.apply({block:"b1"});\n//# sourceMappingURL=' + BUNDLE + '.map'); 16 | 17 | // Create source map file 18 | fs.writeFileSync(BUNDLE + '.map', JSON.stringify(bundle.sourceMap, null, '\t')); 19 | -------------------------------------------------------------------------------- /test/bemcontext-block-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('BEMContent this.block', function() { 5 | it('should support this.block', function() { 6 | test(function() { 7 | block('b').def()(function() { 8 | return this.block; 9 | }); 10 | }, 11 | { block: 'b' }, 12 | 'b'); 13 | }); 14 | 15 | it('should properly set this.block', function() { 16 | test(function() { 17 | block('b').elem('e').def()(function() { 18 | return this.block; 19 | }); 20 | }, 21 | { 22 | block: 'b', 23 | content: { elem: 'e' } 24 | }, 25 | '
b
'); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /runtime-lint/README.md: -------------------------------------------------------------------------------- 1 | # Runtime linter 2 | 3 | Runtime linter can be used for runtime check templates and BEMJSON. To use runtime linter you need to turn on [`runtimeLint` option](https://github.com/bem/bem-xjst/blob/master/docs/en/3-api.md#runtime-linting). 4 | 5 | List of checks: 6 | 7 | * boolean value in attributes (recomend to use string value) 8 | * mods for elem 9 | * changes or additons in `this.ctx.mods` 10 | * class in `attrs` (recomend use `cls`) 11 | * `data-bem` attribute in `attrs` (recomend use `js`) 12 | * mix the same mod of elemMod (warning about useless operation) 13 | * wrong naming (elem delimeter in block names or mod delimeter in mod name and etc) 14 | * `mods` and `elemMods` values can’t be an Array type 15 | -------------------------------------------------------------------------------- /examples/simple-page/README.md: -------------------------------------------------------------------------------- 1 | # bem-xjst example: simple page 2 | 3 | Simplest usage example of bem-xjst. 4 | 5 | Install dependencies (bem-xjst): 6 | ```bash 7 | npm install 8 | ``` 9 | 10 | Run example: 11 | ```bash 12 | node index.js > index.html 13 | ``` 14 | 15 | The result is `index.html` file with markup of simple page on top of `data.js`. 16 | 17 | Project structure: 18 | 19 | * [index.js](index.js) — entry point. 20 | * [data.js](data.js) — typical data from any API or your backend. 21 | * [bemtree-templates.js](bemtree-templates.js) — templates to convert JSON to [BEMJSON](https://github.com/bem/bem-xjst/blob/master/docs/en/4-data.md). 22 | * [bemhtml-templates.js](bemhtml-templates.js) — templates to convert BEMJSON to HTML. 23 | -------------------------------------------------------------------------------- /migration/lib/transformers/7-thisisarray-to-arrayisarray.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var Transformer = require('../transformer'); 3 | var t = new Transformer(); 4 | 5 | module.exports = function(file, api, opts) { 6 | t.description = 'this.isArray() is deprecated. Please use Array.isArray().'; 7 | 8 | t.find = function(file, j) { 9 | return j(file.source) 10 | .find(j.MemberExpression, { 11 | property: { type: 'Identifier', name: 'isArray' }, 12 | object: { type: 'ThisExpression' } 13 | }); 14 | }; 15 | 16 | t.replace = function(ret, j) { 17 | return ret.replaceWith(function(p) { 18 | return 'Array.isArray'; 19 | }); 20 | }; 21 | 22 | return t.run(file, api, opts); 23 | }; 24 | -------------------------------------------------------------------------------- /migration/lib/transformers/7-once-to-def.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var Transformer = require('../transformer'); 3 | var t = new Transformer(); 4 | 5 | module.exports = function(file, api, opts) { 6 | t.description = 'once() is deprecated. Please use def().'; 7 | 8 | t.find = function(file, j) { 9 | return j(file.source) 10 | .find(j.MemberExpression, { 11 | property: { type: 'Identifier' }, 12 | object: { callee: { type: 'Identifier', name: 'block' }} 13 | }) 14 | .find(j.Identifier, { name: 'once' }); 15 | }; 16 | 17 | t.replace = function(ret, j) { 18 | return ret.replaceWith(function(p) { return j.identifier('def'); }); 19 | }; 20 | 21 | return t.run(file, api, opts); 22 | }; 23 | -------------------------------------------------------------------------------- /migration/lib/transformers/5-elemmatch-to-elem-match.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var Transformer = require('../transformer'); 3 | var t = new Transformer(); 4 | 5 | module.exports = function(file, api, opts) { 6 | t.description = 'elemMatch is deprecated. Please use elem(\'*\').match(function() { … })'; 7 | 8 | t.find = function(file, j) { 9 | return j(file.source) 10 | .find(j.Identifier, { name: 'elemMatch' }); 11 | }; 12 | 13 | t.replace = function(ret, j) { 14 | return ret 15 | .replaceWith( 16 | j.memberExpression( 17 | j.callExpression(j.identifier('elem'), [ j.literal('*') ]), 18 | j.identifier('match')) 19 | ); 20 | }; 21 | 22 | return t.run(file, api, opts); 23 | }; 24 | -------------------------------------------------------------------------------- /test/bemcontext-elem-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('BEMContent this.elem', function() { 5 | it('should support this.elem', function() { 6 | test(function() { 7 | block('b').elem('e').def()(function() { 8 | return this.elem; 9 | }); 10 | }, 11 | { block: 'b', elem: 'e' }, 12 | 'e'); 13 | }); 14 | 15 | it('should properly set this.block', function() { 16 | test(function() { 17 | block('b2').def()(function() { 18 | return this.elem; 19 | }); 20 | }, 21 | { 22 | block: 'b1', 23 | elem: 'e', 24 | content: { block: 'b2' } 25 | }, 26 | '
undefined
'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/bemcontext-extend-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('BEMContext this.extend()', function() { 5 | it('should extend empty target', function() { 6 | test(function() { 7 | block('button').def()(function() { 8 | return this.extend(null, { foo: 'bar' }).foo; 9 | }); 10 | }, 11 | { block: 'button' }, 12 | 'bar'); 13 | }); 14 | 15 | it('should extend object', function() { 16 | test(function() { 17 | block('button').def()(function() { 18 | return this.extend( 19 | { foo: 'bar' }, 20 | { foo: 'foo' } 21 | ).foo; 22 | }); 23 | }, 24 | { block: 'button' }, 25 | 'foo'); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /migration/lib/transformers/8-chain-js-to-chain-addjs.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var Transformer = require('../transformer'); 3 | var t = new Transformer(); 4 | 5 | module.exports = function(file, api, opts) { 6 | t.description = 'From v8.x you should use addJs() instead of js()'; 7 | 8 | t.find = function(file, j) { 9 | return j(file.source) 10 | .find(j.MemberExpression, { 11 | property: { type: 'Identifier' }, 12 | object: { callee: { type: 'Identifier', name: 'block' }} 13 | }) 14 | .find(j.Identifier, { name: 'js' }); 15 | }; 16 | 17 | t.replace = function(ret, j) { 18 | return ret.replaceWith(function(p) { 19 | return j.identifier('addJs'); 20 | }); 21 | }; 22 | 23 | return t.run(file, api, opts); 24 | }; 25 | -------------------------------------------------------------------------------- /migration/lib/transformers/8-chain-mix-to-chain-addmix.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var Transformer = require('../transformer'); 3 | var t = new Transformer(); 4 | 5 | module.exports = function(file, api, opts) { 6 | t.description = 'From v8.x you should use addMix() instead of mix()'; 7 | 8 | t.find = function(file, j) { 9 | return j(file.source) 10 | .find(j.MemberExpression, { 11 | property: { type: 'Identifier' }, 12 | object: { callee: { type: 'Identifier', name: 'block' }} 13 | }) 14 | .find(j.Identifier, { name: 'mix' }); 15 | }; 16 | 17 | t.replace = function(ret, j) { 18 | return ret.replaceWith(function(p) { 19 | return j.identifier('addMix'); 20 | }); 21 | }; 22 | 23 | return t.run(file, api, opts); 24 | }; 25 | -------------------------------------------------------------------------------- /migration/lib/transformers/0-dont-check-this-mods.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var Transformer = require('../transformer'); 3 | var t = new Transformer(); 4 | 5 | module.exports = function(file, api, opts) { 6 | t.description = 'this.mods always exists. You don’t need to check it.'; 7 | 8 | t.find = function(file, j) { 9 | return j(file.source) 10 | .find(j.LogicalExpression, { 11 | left: { 12 | type: 'MemberExpression', 13 | object: { type: 'ThisExpression' }, 14 | property: { name: 'mods' } 15 | } 16 | }); 17 | }; 18 | 19 | t.replace = function(ret, j) { 20 | return ret.map(function(item) { 21 | return item.replace(item.value.right); 22 | }) 23 | }; 24 | 25 | return t.run(file, api, opts); 26 | }; 27 | -------------------------------------------------------------------------------- /migration/lib/transformers/8-chain-attrs-to-chain-addattrs.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var Transformer = require('../transformer'); 3 | var t = new Transformer(); 4 | 5 | module.exports = function(file, api, opts) { 6 | t.description = 'From v8.x you should use addAttrs() instead of attrs()'; 7 | 8 | t.find = function(file, j) { 9 | return j(file.source) 10 | .find(j.MemberExpression, { 11 | property: { type: 'Identifier' }, 12 | object: { callee: { type: 'Identifier', name: 'block' }} 13 | }) 14 | .find(j.Identifier, { name: 'attrs' }); 15 | }; 16 | 17 | t.replace = function(ret, j) { 18 | return ret.replaceWith(function(p) { 19 | return j.identifier('addAttrs'); 20 | }); 21 | }; 22 | 23 | return t.run(file, api, opts); 24 | }; 25 | -------------------------------------------------------------------------------- /migration/lib/transformers/0-dont-check-this-elemmods.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var Transformer = require('../transformer'); 3 | var t = new Transformer(); 4 | 5 | module.exports = function(file, api, opts) { 6 | t.description = 'this.elemMods always exists. You don’t need to check it.'; 7 | 8 | t.find = function(file, j) { 9 | return j(file.source) 10 | .find(j.LogicalExpression, { 11 | left: { 12 | type: 'MemberExpression', 13 | object: { type: 'ThisExpression' }, 14 | property: { name: 'elemMods' } 15 | } 16 | }); 17 | }; 18 | 19 | t.replace = function(ret, j) { 20 | return ret.map(function(item) { 21 | return item.replace(item.value.right); 22 | }); 23 | }; 24 | 25 | return t.run(file, api, opts); 26 | }; 27 | -------------------------------------------------------------------------------- /lib/bemtree/entity.js: -------------------------------------------------------------------------------- 1 | var inherits = require('inherits'); 2 | var BemxjstEntity = require('../bemxjst/entity').Entity; 3 | 4 | function Entity() { 5 | BemxjstEntity.apply(this, arguments); 6 | } 7 | 8 | inherits(Entity, BemxjstEntity); 9 | exports.Entity = Entity; 10 | 11 | Entity.prototype.defaultBody = function(context) { 12 | context.mods = this.mods.exec(context); 13 | if (context.ctx.elem) context.elemMods = this.elemMods.exec(context); 14 | 15 | return this.bemxjst.render(context, 16 | this, 17 | this.content.exec(context), 18 | this.js.exec(context), 19 | this.mix.exec(context), 20 | context.mods, 21 | context.elemMods); 22 | }; 23 | -------------------------------------------------------------------------------- /test/modes-custom-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('Modes custom', function() { 5 | it('should support custom modes', function () { 6 | test(function() { 7 | block('b1').mode('custom')('ok'); 8 | block('b1').content()(function() { 9 | return apply('custom'); 10 | }); 11 | }, { 12 | block: 'b1' 13 | }, '
ok
'); 14 | }); 15 | 16 | it('should support custom modes with changes', function () { 17 | test(function() { 18 | block('b1').mode('custom')(function() { 19 | return this.yikes; 20 | }); 21 | block('b1').content()(function() { 22 | return apply('custom', { yikes: 'ok' }); 23 | }); 24 | }, { 25 | block: 'b1' 26 | }, '
ok
'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /migration/lib/transformers/0-func-to-simple.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var Transformer = require('../transformer'); 3 | var t = new Transformer(); 4 | 5 | module.exports = function(file, api, opts) { 6 | t.description = 'Function that returns a literal can be replaced with literal itself.'; 7 | 8 | t.find = function(file, j) { 9 | return j(file.source) 10 | .find(j.FunctionExpression) 11 | .filter(function(p) { 12 | var body = p.node.body.body; 13 | return body.length === 1 && 14 | body[0].type === 'ReturnStatement' && 15 | body[0].argument.type === 'Literal'; 16 | }); 17 | }; 18 | 19 | t.replace = function(ret, j) { 20 | return ret.replaceWith(function(p) { 21 | return j.literal(p.node.body.body[0].argument.value); 22 | }); 23 | }; 24 | 25 | return t.run(file, api, opts); 26 | }; 27 | -------------------------------------------------------------------------------- /test/bemjson-tag-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('BEMJSON tag', function() { 5 | it('should render default tag as `div`', function() { 6 | test(function() {}, 7 | { block: 'b' }, 8 | '
'); 9 | }); 10 | 11 | it('should return html tag', function() { 12 | test(function() { 13 | block('btn').def()(function() { 14 | return this.ctx.tag; 15 | }); 16 | }, 17 | { block: 'btn', tag: 'button' }, 18 | 'button'); 19 | }); 20 | 21 | it('should render without tag', function() { 22 | test(function() { 23 | }, { tag: false, content: 'ok' }, 'ok'); 24 | }); 25 | 26 | it('should render empty string ' + 27 | 'if block with no content and no tag', function() { 28 | test(function() { 29 | }, { block: 'test', tag: false }, ''); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/bemcontext-custom-error-logger-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var bemhtml = require('../').bemhtml; 3 | var sinon = require('sinon'); 4 | 5 | describe('BEMContext: custom error logger', function() { 6 | it('should use custom function', function() { 7 | var templates = bemhtml.compile(function() { 8 | block('b1').attrs()(function() { 9 | var attrs = applyNext(); 10 | attrs.foo = 'bar'; 11 | return attrs; 12 | }); 13 | }, { production: true }); 14 | 15 | var p = templates.BEMContext.prototype; 16 | p.onError = function(context, e) { 17 | console.info('>>> Error occurred', context.ctx, e); 18 | }; 19 | var onError = sinon.spy(p, 'onError'); 20 | 21 | assert.doesNotThrow(function() { 22 | assert.equal(templates.apply({ block: 'b1' }), ''); 23 | }); 24 | 25 | sinon.assert.calledOnce(onError); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### BEFORE YOU SUBMIT please do the following: 2 | * Search if [the issue already open or closed](https://github.com/bem/bem-xjst/issues?utf8=✓&q=is%3Aissue%20)? 3 | * Is there is answer in [documentation](https://github.com/bem/bem-xjst/tree/master/docs)? 4 | * Is there is answer in Readme? [README](https://github.com/bem/bem-xjst/blob/master/README.md) 5 | * Is there is answer in release notes? [CHANGELOG](https://github.com/bem/bem-xjst/blob/master/CHANGELOG.md) 6 | * Is there is answer in migration guides? [1.x to 2.x](https://github.com/bem/bem-xjst/wiki/Notable-changes-between-bem-xjst@1.x-and-bem-xjst@2.x) or [4.x to 5.x](https://github.com/bem/bem-xjst/wiki/Migration-guide-from-4.x-to-5.x) 7 | 8 | ### Input code or something about issue background 9 | 10 | ### Expected Behavior 11 | 12 | ### Actual Behavior 13 | 14 | ### Possible Solution 15 | 16 | ### Your Environment 17 | 18 | bem-xjst version and all relevant details 19 | -------------------------------------------------------------------------------- /migration/lib/transformers/5-api-changed.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | 3 | module.exports = function(file, api, opts) { 4 | var j = api.jscodeshift; 5 | 6 | var ret = j(file.source) 7 | .find(j.CallExpression, { 8 | callee: { type: 'Identifier', name: 'require' }, 9 | arguments: [ { type: 'Literal', value: 'bem-xjst' } ] 10 | }); 11 | 12 | 13 | if (opts.lint) { 14 | if (ret.length === 0) 15 | return; 16 | 17 | ret.forEach(function(p) { 18 | log({ 19 | descr: 'bem-xjst API changed: require(\'bem-xjst\') now returns two engines bemhtml and bemtree.', 20 | path: p.value, 21 | ret: ret, 22 | file: file 23 | }); 24 | }); 25 | 26 | } else { 27 | return ret 28 | .replaceWith(function(p) { 29 | var val = p.value; 30 | 31 | return j.memberExpression(val, j.identifier('bemhtml')) 32 | }) 33 | .toSource({ quote: 'single' }); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /bench/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | ## New benchmark test 4 | 5 | 1. Install dependencies: `npm i` 6 | 2. Install python and gnuplot (e.g. `brew install gnuplot`) if you want to see charts 7 | 3. Prepare json data files and template (you can request large production bemjson files from @miripiruni) 8 | 4. Run test 9 | 10 | `node runner.js --rev1 d53646f2c340b5496fbd75a5313e3284b58e238d --rev2 d53646f2c340b5496fbd75a5313e3284b58e238d --bemjson 1000 --dataPath ~/web-data/data --templatePath ~/web-data/templates.js` 11 | 12 | `--dataPath` and `--templatePath` should point to your data files and template 13 | 14 | The results of test would be directory `dat-d5364-and-d5364` with svg histogram and STDOUT with some numbers. 15 | 16 | ## Old test 17 | 18 | Or you can run old test: 19 | 20 | 1. Make sure the `bench/package.json` has proper git hash of `bem-xjst` before running! 21 | 2. Run `node run.js --compile --compare --min-samples 1000` 22 | 23 | Benchmark help: 24 | 25 | ```bash 26 | node run.js --help 27 | ``` 28 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [10.x, 12.x, 14.x, 16.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v2 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: npm ci 30 | - run: npm run build --if-present 31 | - run: npm test 32 | -------------------------------------------------------------------------------- /test/bemcontext-custom-fields-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('BEMContext: custom BEMJSON fields', function() { 5 | it('should preserve users fields', function() { 6 | test(function() { 7 | block('select').elem('control').def()(function() { 8 | this.lol = 333; 9 | return applyNext(); 10 | }); 11 | block('select').def()(function() { 12 | this.foo = 222; 13 | return applyNext(); 14 | }); 15 | block('select').mod('disabled', true).def()(function() { 16 | this.bar = 111; 17 | return applyNext(); 18 | }); 19 | block('select').elem('control').def()(function() { 20 | applyNext(); 21 | return this.foo + this.bar + this.lol; 22 | }); 23 | }, 24 | { 25 | block: 'select', 26 | mods: { disabled: true }, 27 | content: { elem: 'control' } 28 | }, 29 | '
666
'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/runtime-applyctx-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('Runtime applyCtx()', function() { 5 | it('should work with just context', function() { 6 | test(function() { 7 | block('b1').content()(function() { 8 | return applyCtx([ 9 | { block: 'b2', content: 'omg' }, 10 | { block: 'b3', tag: 'br' } 11 | ]); 12 | }); 13 | }, 14 | { block: 'b1' }, 15 | '
' + 16 | '<div class="b2">omg</div><br class="b3">
'); 17 | }); 18 | 19 | it('should work with both context and changes', function() { 20 | test(function() { 21 | block('b2').content()(function() { 22 | return this.wtf; 23 | }); 24 | 25 | block('b1').content()(function() { 26 | return applyCtx([ { block: 'b2' } ], { wtf: 'ohai' }); 27 | }); 28 | }, 29 | { block: 'b1' }, 30 | '
<div class="b2">ohai</div>
'); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /migration/lib/transformers/2-no-empty-mode.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var Transformer = require('../transformer'); 3 | var t = new Transformer(); 4 | 5 | module.exports = function(file, api, opts) { 6 | t.description = 'We find definition of empty mode. Empty mode(\'\') is no longer supported. Please read: https://github.com/bem/bem-xjst/wiki/Notable-changes-between-bem-xjst@1.x-and-bem-xjst@2.x#user-content-mode-no-more'; 7 | 8 | t.find = function(file, j) { 9 | return j(file.source) 10 | .find(j.CallExpression, 11 | { callee: { 12 | type: 'CallExpression', 13 | arguments: [ { type: 'Literal', value: '' } ] 14 | } }); 15 | }; 16 | 17 | t.replace = function(ret, j) { 18 | return ret 19 | .replaceWith(function(p) { 20 | return j.callExpression( 21 | j.callExpression(j.identifier('mode'), 22 | [ j.literal('custom-mode') ]), 23 | p.node.arguments); 24 | }); 25 | }; 26 | 27 | return t.run(file, api, opts); 28 | }; 29 | -------------------------------------------------------------------------------- /test/runtime-bemcontext-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var bemhtml = require('./fixtures')('bemhtml'); 3 | 4 | describe('Runtime BEMContext', function() { 5 | it('should support extending of templates.BEMContext prototype', function() { 6 | var templates = bemhtml.compile(); 7 | templates.BEMContext.prototype.myField = 'opa'; 8 | templates.compile(function() { 9 | block('b').content()(function() { return this.myField; }); 10 | }); 11 | assert.equal(templates.apply({ block: 'b' }), '
opa
'); 12 | }); 13 | 14 | it('should redefine templates.BEMContext prototype later', function() { 15 | var templates = bemhtml.compile(); 16 | var bemjson = { block: 'b', tag: false }; 17 | templates.BEMContext.prototype.what = 'hip'; 18 | templates.compile(function() { 19 | block('b').content()(function() { return this.what; }); 20 | }); 21 | assert.equal(templates.apply(bemjson), 'hip'); 22 | templates.BEMContext.prototype.what = 'hipier'; 23 | assert.equal(templates.apply(bemjson), 'hipier'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /docs/en/2-quick-start.md: -------------------------------------------------------------------------------- 1 | # Quick start 2 | 3 | ## Installation 4 | 5 | To use bem-xjst, you need [Node.js](https://nodejs.org/) v0.10 or later, and [npm](https://www.npmjs.com/). 6 | 7 | Install: 8 | 9 | ```bash 10 | npm install bem-xjst 11 | ``` 12 | 13 | ## Basic example 14 | 15 | ```js 16 | var bemxjst = require('bem-xjst'); 17 | 18 | // bem-xjst contains two engines, BEMHTML and BEMTREE (starting from v5.0.0) 19 | // Choose the BEMHTML engine 20 | var bemhtml = bemxjst.bemhtml; 21 | 22 | // Add templates using the `compile` method 23 | var templates = bemhtml.compile(() => { 24 | block('text')({ tag: 'span' }); 25 | }); 26 | 27 | // Add data in BEMJSON format 28 | var bemjson = [ 29 | { block: 'text', content: 'First' }, 30 | { block: 'text', content: 'Second' } 31 | ]; 32 | 33 | // Apply templates 34 | var html = templates.apply(bemjson); 35 | ``` 36 | 37 | The resulting `html` contains this string: 38 | 39 | ```html 40 | FirstSecond 41 | ``` 42 | 43 | [Online demo](https://bem.github.io/bem-xjst/). 44 | 45 | Read next: [API](3-api.md) 46 | -------------------------------------------------------------------------------- /migration/lib/transformer.js: -------------------------------------------------------------------------------- 1 | var log = require('./logger'); 2 | 3 | function Transformer() { 4 | this.init(); 5 | }; 6 | 7 | Transformer.prototype.init = function() {}; 8 | 9 | Transformer.prototype.description = ''; 10 | 11 | Transformer.prototype.replace = function(ret) { 12 | return ret; 13 | }; 14 | 15 | Transformer.prototype.find = function(file) { 16 | return file; 17 | }; 18 | 19 | Transformer.prototype.run = function(file, api, opts) { 20 | var j = api.jscodeshift; 21 | var ret = this.find(file, j); 22 | var config = opts.config ? require(opts.config) : {}; 23 | 24 | if (!config.quote) config.quote = 'single'; 25 | 26 | if (opts.lint) { 27 | if (ret.length === 0) return; 28 | this.log(ret, file); 29 | return; 30 | } 31 | 32 | return this.replace(ret, j).toSource(config); 33 | }; 34 | 35 | Transformer.prototype.log = function(ret, file) { 36 | var t = this; 37 | 38 | ret.forEach(function(p) { 39 | log({ 40 | descr: t.description, 41 | path: p.value, 42 | ret: ret, 43 | file: file 44 | }); 45 | }); 46 | }; 47 | 48 | module.exports = Transformer; 49 | -------------------------------------------------------------------------------- /migration/lib/transformers/2-no-empty-mode-call.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var Transformer = require('../transformer'); 3 | var t = new Transformer(); 4 | 5 | module.exports = function(file, api, opts) { 6 | t.description = 'We find a call of empty mode. Empty mode mode(\'\') is no longer supported. Please read: https://github.com/bem/bem-xjst/wiki/Notable-changes-between-bem-xjst@1.x-and-bem-xjst@2.x#user-content-mode-no-more'; 7 | 8 | t.find = function(file, j) { 9 | return j(file.source) 10 | .find(j.CallExpression, { callee: { type: 'Identifier', name: 'apply' } }) 11 | .filter(function(p) { 12 | return p.value.arguments.filter(function(arg) { 13 | return arg.value === ''; 14 | }).length !== 0; 15 | }); 16 | }; 17 | 18 | t.replace = function(ret, j) { 19 | return ret 20 | .replaceWith(function(p) { 21 | var args = p.node.arguments; 22 | args[0] = j.literal('custom-mode'); 23 | 24 | return j.callExpression(j.identifier('apply'), args); 25 | }); 26 | }; 27 | 28 | return t.run(file, api, opts); 29 | }; 30 | -------------------------------------------------------------------------------- /docs/ru/2-quick-start.md: -------------------------------------------------------------------------------- 1 | # Быстрый старт 2 | 3 | ## Установка 4 | 5 | Для использования bem-xjst вам понадобится [Node.js](https://nodejs.org/) v0.10 и выше и [npm](https://www.npmjs.com/). 6 | 7 | Установка: 8 | 9 | ```bash 10 | npm install bem-xjst 11 | ``` 12 | 13 | ## Простой пример 14 | 15 | ```js 16 | var bemxjst = require('bem-xjst'); 17 | 18 | // bem-xjst содержит два движка: BEMHTML и BEMTREE (начиная с v5.0.0) 19 | // Выбираем движок BEMHTML 20 | var bemhtml = bemxjst.bemhtml; 21 | 22 | // Добавляем шаблоны с помощью метода compile 23 | var templates = bemhtml.compile(() => { 24 | block('text')({ tag: 'span' }); 25 | }); 26 | 27 | // Добавляем данные в формате BEMJSON 28 | var bemjson = [ 29 | { block: 'text', content: 'Первый' }, 30 | { block: 'text', content: 'Второй' } 31 | ]; 32 | 33 | // Применяем шаблоны 34 | var html = templates.apply(bemjson); 35 | ``` 36 | 37 | В результате `html` будет содержать строку: 38 | 39 | ```html 40 | ПервыйВторой 41 | ``` 42 | 43 | [Online демо](https://bem.github.io/bem-xjst/). 44 | 45 | Читать далее: [API](3-api.md) 46 | -------------------------------------------------------------------------------- /migration/lib/transformers/2-no-more-this-underscore.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var Transformer = require('../transformer'); 3 | var t = new Transformer(); 4 | 5 | module.exports = function(file, api, opts) { 6 | t.description = 'All helpers have finally migrated to this from this._. From now on this._ will be undefined. Please read: https://github.com/bem/bem-xjst/wiki/Notable-changes-between-bem-xjst@1.x-and-bem-xjst@2.x#this_-no-more'; 7 | 8 | t.find = function(file, j) { 9 | return j(file.source) 10 | .find(j.MemberExpression) 11 | .filter(function(p) { 12 | var node = p.node; 13 | var property = node.object && node.object.property; 14 | 15 | return property && 16 | property.name === '_' && 17 | node.object.object && 18 | node.object.object.type === 'ThisExpression'; 19 | }); 20 | }; 21 | 22 | t.replace = function(ret, j) { 23 | return ret.replaceWith(function(p) { 24 | return j.memberExpression( 25 | j.thisExpression(), 26 | j.identifier(p.node.property.name) 27 | ); 28 | }); 29 | }; 30 | 31 | return t.run(file, api, opts); 32 | }; 33 | -------------------------------------------------------------------------------- /examples/simple-page/data.js: -------------------------------------------------------------------------------- 1 | // This is typical data from any API or your backend 2 | module.exports = { 3 | name: { 4 | first: 'Slava', 5 | last: 'Oliyanchuk', 6 | }, 7 | username: 'miripiruni', 8 | position: 'Front-End Web Developer', 9 | avatar: { 10 | small: { 11 | url: 'http://miripiruni.org/i/miripiruni_moscow_27-03-2011-250x375.jpg', 12 | author: 'Patrick H. Lauke', 13 | authorUrl: 'http://www.splintered.co.uk/about/' 14 | }, 15 | big: { 16 | url: 'http://miripiruni.org/i/miripiruni_moscow_27-03-2011.jpg', 17 | author: 'Patrick H. Lauke', 18 | authorUrl: 'http://www.splintered.co.uk/about/' 19 | }, 20 | }, 21 | email: 'mail@miripiruni.org', 22 | profiles: [ 23 | { 24 | service: 'Soundcloud', 25 | url: 'https://soundcloud.com/miripiruni' 26 | }, 27 | { 28 | service: 'Bandcamp', 29 | url: 'https://miripiruni.bandcamp.com' 30 | }, 31 | { 32 | service: 'Github', 33 | url: 'https://github.com/miripiruni' 34 | }, 35 | { 36 | url: 'https://twitter.com/miripiruni' 37 | }, 38 | { 39 | url: 'http://wantr.ru/miripiruni' 40 | }, 41 | ] 42 | }; 43 | -------------------------------------------------------------------------------- /test/bemcontext-elemmods-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('BEMContext this.elemMods', function() { 5 | it('should support elemMods', function() { 6 | test(function() { 7 | block('b').elem('e').def()(function() { 8 | return JSON.stringify(this.elemMods); 9 | }); 10 | }, 11 | { 12 | block: 'b', 13 | elem: 'e', 14 | elemMods: { type: 'button' } 15 | }, 16 | '{"type":"button"}'); 17 | }); 18 | 19 | it('should lazily define elemMods', function() { 20 | test(function() { 21 | block('b1').elem('e1').content()(function() { 22 | return this.elemMods.a || 'yes'; 23 | }); 24 | }, { block: 'b1', content: { elem: 'e1' } }, 25 | '
yes
'); 26 | }); 27 | 28 | it('should support changing elemMods in runtime', function() { 29 | test(function() { 30 | block('b1').elem('e1').def()(function() { 31 | this.elemMods.a = 'b'; 32 | return applyNext(); 33 | }); 34 | }, { 35 | block: 'b1', 36 | elem: 'e1' 37 | }, '
'); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/custom-naming-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('Custom delimeters for BEM naming', function() { 5 | it('should support custom naming', function() { 6 | test(function() { 7 | }, [ 8 | { 9 | block: 'b1', 10 | elem: 'e1', 11 | elemMods: { 12 | a: 'b' 13 | } 14 | }, 15 | { 16 | block: 'b1', 17 | elem: 'e1' 18 | }, 19 | { 20 | block: 'b1', 21 | mix: { elem: 'e2' } 22 | } 23 | ], '
' + 24 | '
' + 25 | '
', { 26 | naming: { 27 | elem: '$$', 28 | mod: '@@' 29 | } 30 | }); 31 | }); 32 | 33 | it('should support custom naming ' + 34 | 'for modName and modVal delimeters', function() { 35 | test(function() { 36 | }, [ 37 | { 38 | block: 'b1', 39 | elem: 'e1', 40 | elemMods: { a: 'b' } 41 | } 42 | ], '
', { 43 | naming: { 44 | elem: '__', 45 | mod: { name: '--', val: '_' } 46 | } 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/bemjson-elem-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('BEMJSON elem', function() { 5 | it('should assume elem=\'\' is a falsey value', function() { 6 | test(function() { 7 | block('b1').elem('e1').def()(function() { 8 | return applyCtx(this.extend(this.ctx, { 9 | block: 'b2', 10 | elem: '' 11 | })); 12 | }); 13 | }, { block: 'b1' }, '
'); 14 | }); 15 | 16 | it('wildcard elem should be called before the matched templates', 17 | function() { 18 | test(function() { 19 | block('b1').content()(function() { 20 | return 'block'; 21 | }); 22 | block('b1').elem('a').content()(function() { 23 | return 'block-a'; 24 | }); 25 | block('b1').elem('*').content()(function() { 26 | return '%' + applyNext() + '%'; 27 | }); 28 | }, [ { 29 | block: 'b1' 30 | }, { 31 | block: 'b1', 32 | elem: 'a' 33 | }, { 34 | block: 'b3', 35 | elem: 'b', 36 | content: 'ok' 37 | } ], '
block
' + 38 | '
%block-a%
' + 39 | '
%ok%
'); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /migration/lib/transformers/7-mods-value.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var get = require('lodash.get'); 3 | var Transformer = require('../transformer'); 4 | var t = new Transformer(); 5 | 6 | module.exports = function(file, api, opts) { 7 | t.description = 'Modifier value must be a string type'; 8 | 9 | t.find = function(file, j) { 10 | return j(file.source) 11 | .find(j.Literal) 12 | .filter(function(p) { 13 | var arg = p.value.arguments; 14 | 15 | if (typeof p.value.rawValue === 'number') { 16 | var callee = get(p, 'parentPath.parentPath.value.callee'); 17 | 18 | if (!callee) 19 | return false; 20 | 21 | if (callee.property && callee.property.type === 'Identifier' && 22 | (callee.property.name === 'mod' || callee.property.name === 'elemMod')) { 23 | callee = callee.property; 24 | } 25 | 26 | return callee.name === 'mod' || callee.name === 'elemMod'; 27 | } 28 | 29 | return false; 30 | }); 31 | }; 32 | 33 | t.replace = function(ret, j) { 34 | return ret.replaceWith(function(p) { 35 | return j.literal(p.value.rawValue.toString()); 36 | }); 37 | }; 38 | 39 | return t.run(file, api, opts); 40 | }; 41 | -------------------------------------------------------------------------------- /examples/simple-page/bemtree-templates.js: -------------------------------------------------------------------------------- 1 | // This is BEMTREE template to convert regular data in JSON to 2 | // BEMJSON (see https://github.com/bem/bem-xjst/blob/master/docs/en/4-data.md) 3 | module.exports = function() { 4 | block('root').replace()(function() { 5 | // Save `data` in `this.data` for usage in other templates. 6 | var data = this.data = this.ctx.data; 7 | 8 | return { 9 | block: 'page', 10 | title: [ data.name.first, data.name.last, 'profile' ].join(' '), 11 | data: data 12 | }; 13 | }); 14 | 15 | block('page').content()(function() { 16 | var data = this.data; 17 | 18 | return [ 19 | { 20 | block: 'user', 21 | content: [ data.name.first, data.name.last ].join(' '), 22 | username: data.username, 23 | position: data.position, 24 | }, 25 | { 26 | block: 'avatar', 27 | avatar: data.avatar 28 | }, 29 | { 30 | block: 'links', 31 | profiles: data.profiles 32 | } 33 | ]; 34 | }); 35 | 36 | block('links').content()(function() { 37 | return this.ctx.profiles.map(function(link) { 38 | return { 39 | elem: 'item', 40 | content: link.service, 41 | url: link.url 42 | }; 43 | }); 44 | }); 45 | }; 46 | -------------------------------------------------------------------------------- /bench/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const fs = require('fs'); 3 | 4 | const argv = require('yargs') 5 | .describe('rev', 'bem-xjst revision (commit hash)') 6 | .describe('bemjson', 'amount of bemjson files to render (default 2000)') 7 | .describe('dataPath', 'path to bemjson files') 8 | .describe('templatePath', 'path to template file') 9 | .describe('verbose', 'verbose results') 10 | .help('h') 11 | .alias('help', 'h') 12 | .argv; 13 | 14 | const dataPath = argv.dataPath || './node_modules/web-data/data'; 15 | const templatePath = argv.templatePath || './node_modules/web-data/templates.js'; 16 | const files = fs.readdirSync(dataPath); 17 | const times = argv.bemjson || 2000; 18 | const secInNs = 1e9; 19 | const res = []; 20 | 21 | const bemhtml = require('./bem-xjst-' + argv.rev).bemhtml; 22 | const templates = bemhtml.compile(fs.readFileSync(templatePath, 'utf8'), { escapeContent: true }); 23 | 24 | for (var i = 0; i < times; i++) { 25 | var path = dataPath + '/' + files[i % files.length]; 26 | // argv.verbose && console.warn(path); 27 | var data = JSON.parse(fs.readFileSync(path, 'utf8')); 28 | 29 | var time = process.hrtime(); 30 | templates.apply(data); 31 | var diff = process.hrtime(time); 32 | 33 | res.push((diff[0] * secInNs + diff[1]) / 1000000); 34 | } 35 | 36 | console.log(JSON.stringify(res)); 37 | -------------------------------------------------------------------------------- /lib/bemxjst/class-builder.js: -------------------------------------------------------------------------------- 1 | function ClassBuilder(options) { 2 | this.elemDelim = options.elem || '__'; 3 | 4 | this.modDelim = typeof options.mod === 'string' ? 5 | { 6 | name: options.mod || '_', 7 | val: options.mod || '_' 8 | } : 9 | { 10 | name: options.mod && options.mod.name || '_', 11 | val: options.mod && options.mod.val || '_' 12 | }; 13 | } 14 | 15 | exports.ClassBuilder = ClassBuilder; 16 | 17 | ClassBuilder.prototype.build = function(block, elem) { 18 | if (!elem) 19 | return block; 20 | else 21 | return block + this.elemDelim + elem; 22 | }; 23 | 24 | ClassBuilder.prototype.buildModPostfix = function(modName, modVal) { 25 | var res = this.modDelim.name + modName; 26 | if (modVal !== true) res += this.modDelim.val + modVal; 27 | return res; 28 | }; 29 | 30 | ClassBuilder.prototype.buildBlockClass = function(name, modName, modVal) { 31 | var res = name; 32 | if (modVal) res += this.buildModPostfix(modName, modVal); 33 | return res; 34 | }; 35 | 36 | ClassBuilder.prototype.buildElemClass = function(block, name, modName, modVal) { 37 | return this.buildBlockClass(block) + 38 | this.elemDelim + 39 | name + 40 | this.buildModPostfix(modName, modVal); 41 | }; 42 | 43 | ClassBuilder.prototype.split = function(key) { 44 | return key.split(this.elemDelim, 2); 45 | }; 46 | -------------------------------------------------------------------------------- /migration/lib/transformers/7-xhtml-true.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var Transformer = require('../transformer'); 3 | var t = new Transformer(); 4 | 5 | module.exports = function(file, api, opts) { 6 | t.description = 'For backward capability use option `xhtml:true`' 7 | 8 | t.find = function(file, j) { 9 | return j(file.source) 10 | .find(j.CallExpression, { 11 | callee: { property: { type: 'Identifier', name: 'compile' } 12 | }}); 13 | }; 14 | 15 | t.replace = function(ret, j) { 16 | return ret 17 | .replaceWith(function(p) { 18 | var val = p.value; 19 | var args = val.arguments; 20 | 21 | if (args.length === 1) { 22 | args.push( 23 | j.objectExpression( 24 | [ j.property('init', j.identifier('xhtml'), j.literal(true)) ] 25 | ) 26 | ); 27 | } else if (args.length === 2) { 28 | args[1].properties = args[1].properties.filter(function(p) { 29 | return p.key.name !== 'xhtml'; 30 | }); 31 | args[1].properties.push(j.property('init', j.identifier('xhtml'), j.literal(true))) 32 | } 33 | 34 | return j.callExpression( 35 | j.memberExpression(j.identifier(val.callee.object.name), j.identifier('compile')), 36 | args) 37 | }); 38 | }; 39 | 40 | return t.run(file, api, opts); 41 | }; 42 | -------------------------------------------------------------------------------- /test/bemcontext-islast-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('BEMContext this.isLast()', function() { 5 | it('should support this.isLast()', function() { 6 | test(function() { 7 | block('b1')( 8 | match(function() { return this.isLast(); }) 9 | .mix()({ mods: { position: 'last' } }) 10 | ); 11 | }, [ 12 | { 13 | tag: 'table', 14 | content: { 15 | block: 'b1', 16 | tag: 'tr', 17 | content: [ 18 | { content: '', tag: 'td' }, 19 | { content: '', tag: 'td' } 20 | ] 21 | } 22 | }, 23 | { 24 | block: 'b1', 25 | content: 'first' 26 | }, 27 | { 28 | block: 'b1', 29 | content: 'last' 30 | } 31 | ], '
' + 32 | '
first
' + 33 | '
last
'); 34 | }); 35 | 36 | it('should preserve position', function() { 37 | test(function() { 38 | block('button') 39 | .match(function() { return this.isLast(); }) 40 | .addMods()({ last: 'yes' }); 41 | }, 42 | [ 43 | { block: 'button' }, 44 | { block: 'button' } 45 | ], 46 | '
' + 47 | '
'); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/modes-cls-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | var assert = require('assert'); 4 | var bemxjst = require('../').bemhtml; 5 | var compile = fixtures.compile; 6 | 7 | describe('Modes cls', function() { 8 | it('should throw error when args passed to cls mode', function() { 9 | assert.throws(function() { 10 | bemxjst.compile(function() { 11 | block('b1').cls('blah'); 12 | }); 13 | }); 14 | }); 15 | 16 | it('should set cls', function() { 17 | test(function() { 18 | block('button').cls()('btn'); 19 | }, 20 | { block: 'button' }, 21 | '
'); 22 | }); 23 | 24 | it('should not override later declarations', function() { 25 | test(function() { 26 | block('button').cls()('control'); 27 | block('button').cls()('btn'); 28 | }, 29 | { block: 'button' }, 30 | '
'); 31 | }); 32 | 33 | it('should trim cls', function() { 34 | compile(function() { 35 | block('button').cls()(' one two '); 36 | }) 37 | .apply({ block: 'button' }) 38 | .should.equal('
'); 39 | }); 40 | 41 | it('should escape cls', function() { 42 | compile(function() { 43 | block('button').cls()('">'); 44 | }) 45 | .apply({ block: 'button' }) 46 | .should.equal('
'); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # bem-xjst contributing notes 2 | 3 | If you want help you can fix issues than labeled [help wanted](https://github.com/bem/bem-xjst/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22). 4 | 5 | ## Development dependencies 6 | 7 | All `devDependencies` must fix only major version. Use `^` in package.json. 8 | 9 | ## Unit Tests 10 | 11 | To run tests cast `npm run test`. 12 | 13 | ## Benchmarks 14 | 15 | To run benchmarks see [bench readme](https://github.com/bem/bem-xjst/blob/master/bench/README.md). 16 | 17 | ## Release version 18 | 19 | ### Before 20 | 21 | 1. Functional: unit tests. 22 | 2. Performance: benchmarks. 23 | 3. Integration: check Islands and web4 (skip if you not from Yandex :). 24 | 4. Write changelog (use [changelog-maker](https://github.com/rvagg/changelog-maker)) и release notes. 25 | 5. Don’t forget about `git tag`. 26 | 6. If you build package from support branch (4.x, 5.x etc) don’t forget about [`-- tag`](https://docs.npmjs.com/cli/publish) parameter of `npm publish`. 27 | 28 | ### After 29 | 1. Merge PR about update `bem-xjst` dependency to [`enb-bemxjst`](https://github.com/enb/enb-bemxjst/). If you build package from support branch update `enb-bemxjst-6x`. 30 | 2. Write changelog `enb-bemxjst`. 31 | 3. Publish `enb-bemxjst` npm release. 32 | 4. Update bem-xjst online demo. 33 | 34 | 35 | # Online demo 36 | 37 | To update or change online demo read [manual](https://github.com/bem/bem-xjst/blob/gh-pages/README.md). 38 | -------------------------------------------------------------------------------- /migration/lib/transformers/0-arr-to-func-generator.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var get = require('lodash.get'); 3 | var Transformer = require('../transformer'); 4 | var t = new Transformer(); 5 | 6 | module.exports = function(file, api, opts) { 7 | t.description = 'Use function generator instead (Example: `function() { return []; }`. ' + 8 | 'See docs: https://github.com/bem/bem-xjst/wiki/Notable-changes-' + 9 | 'between-bem-xjst@1.x-and-bem-xjst@2.x#static-objects-shortcuts-in-mix-content-etc' 10 | 11 | t.find = function(file, j) { 12 | return j(file.source) 13 | .find(j.ArrayExpression) 14 | .filter(function(p) { 15 | var arg = p.value.arguments; 16 | var callee = get(p, 'parentPath.parentPath.value.callee.callee'); 17 | 18 | if (! callee) 19 | return false; 20 | 21 | callee = callee.name || (callee.property && callee.property.name); 22 | 23 | return [ 24 | 'js', 25 | 'mix', 26 | 'content', 27 | 'def', 28 | 'addMix', 29 | 'appendContent', 30 | 'prependContent' 31 | ].indexOf(callee) !== -1; 32 | }); 33 | }; 34 | 35 | t.replace = function(ret, j) { 36 | return ret.replaceWith(function(p) { 37 | return j.functionExpression( 38 | j.identifier(''), 39 | [], 40 | j.blockStatement([j.returnStatement(p.value)]) 41 | ); 42 | }); 43 | }; 44 | 45 | return t.run(file, api, opts); 46 | }; 47 | -------------------------------------------------------------------------------- /test/modes-bem-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | var assert = require('assert'); 4 | var bemxjst = require('../').bemhtml; 5 | 6 | describe('Modes bem', function() { 7 | it('should throw error when args passed to bem mode', function() { 8 | assert.throws(function() { 9 | bemxjst.compile(function() { 10 | block('b1').bem('blah'); 11 | }); 12 | }); 13 | }); 14 | 15 | it('should return bem by default', function() { 16 | test(function() { 17 | block('button').def()(function() { 18 | return typeof this.ctx.bem; 19 | }); 20 | }, 21 | { block: 'button' }, 22 | 'undefined'); 23 | }); 24 | 25 | it('should set bem to false', function() { 26 | test(function() { 27 | block('button').bem()(false); 28 | }, 29 | { block: 'button' }, 30 | '
'); 31 | }); 32 | 33 | it('should not override later declarations', function() { 34 | test(function() { 35 | block('button').bem()(false); 36 | block('button').bem()(true); 37 | }, 38 | { block: 'button' }, 39 | '
'); 40 | }); 41 | 42 | it('should output cls value if bem:false', function() { 43 | test(function() { 44 | block('b')( 45 | bem()(false), 46 | js()(true) 47 | ); 48 | }, 49 | [ 50 | { block: 'b', cls: 'anything' }, 51 | { block: 'b' } 52 | ], 53 | '
'); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /docs/en/1-about.md: -------------------------------------------------------------------------------- 1 | # About bem-xjst 2 | 3 | ## What is bem-xjst? 4 | 5 | bem-xjst is a template engine for web development using the BEM methodology. 6 | 7 | It contains two engines: 8 | 9 | 1. **BEMHTML** — for transforming BEMJSON to HTML. 10 | 2. **BEMTREE** — for transforming BEMJSON with data to BEMJSON with a BEM tree for further transformation using BEMHTML. 11 | 12 | The template engine is based on the declarative principles of [XSLT](https://www.w3.org/TR/xslt) (eXtensible Stylesheet Language Transformations). The name XJST (eXtensible JavaScript Transformations) was also created as an analogy to XSLT. 13 | 14 | Before using the template engine, you should review: 15 | 16 | 1. [BEMJSON format for input data](4-data.md) 17 | 2. [How to write templates](5-templates-syntax.md) 18 | 3. [Processes for selecting and applying templates](7-runtime.md) 19 | 20 | ## Features 21 | 22 | 1. Templates are extensible: they can be redefined or extended. 23 | 2. Templates are written using [pattern matching](7-runtime.md#how-templates-are-selected-and-applied) for the values and structure of input data. 24 | 3. Traverses input data by default. 25 | 4. Built-in rendering behavior is used by default, even if the user didn’t add templates. 26 | 5. Written in JavaScript, so the entire JavaScript infrastructure is available for checking code quality and conforming to best practices. 27 | 6. Doesn’t require compiling templates. 28 | 7. API provided for adding templates in runtime. 29 | 8. Runs on a server and client. 30 | 31 | Read next: [Quick Start](2-quick-start.md) 32 | -------------------------------------------------------------------------------- /test/bemcontext-generateid-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var bemxjst = require('./fixtures')('bemhtml'); 3 | 4 | describe('BEMContext this.generateId()', function() { 5 | var template; 6 | 7 | beforeEach(function() { 8 | template = bemxjst.compile(function() { 9 | block('b1')( 10 | tag()(''), 11 | content()(function() { 12 | return this.generateId(); 13 | }) 14 | ); 15 | }); 16 | }); 17 | 18 | it('starts with uniq', function() { 19 | var str = template.apply({ block: 'b1' }); 20 | assert(str.indexOf('uniq') === 0); 21 | }); 22 | 23 | it('should be unique in different applies', function() { 24 | assert.notEqual( 25 | template.apply({ block: 'b1' }), 26 | template.apply({ block: 'b1' }) 27 | ); 28 | }); 29 | 30 | it('should be unique in one apply', function() { 31 | var sep = '❄'; 32 | var str = template.apply([ { block: 'b1' }, sep, { block: 'b1' } ]); 33 | var arr = str.split(sep); 34 | 35 | assert.notEqual(arr[0], arr[1]); 36 | }); 37 | 38 | it('should be equal for same ctx', function() { 39 | var sep = '❄'; 40 | var template = bemxjst.compile(function() { 41 | var sep = '❄'; 42 | block('b2')( 43 | tag()(''), 44 | content()(function() { 45 | return [ this.generateId(), sep, this.generateId() ]; 46 | }) 47 | ); 48 | }); 49 | var str = template.apply({ block: 'b2' }); 50 | var arr = str.split(sep); 51 | 52 | assert.equal(arr[0], arr[1]); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /docs/ru/1-about.md: -------------------------------------------------------------------------------- 1 | # О bem-xjst 2 | 3 | ## Что такое bem-xjst? 4 | 5 | bem-xjst — шаблонизатор для тех, кто ведёт веб-разработку в рамках БЭМ-методологии. 6 | 7 | Шаблонизатор содержит два движка: 8 | 9 | 1. **BEMHTML** — для преобразования BEMJSON в HTML. 10 | 2. **BEMTREE** — для преобразования BEMJSON с данными в BEMJSON с БЭМ-деревом для последующего преобразования с помощью BEMHTML. 11 | 12 | В основе шаблонизатора лежат декларативные принципы из [XSLT](https://www.w3.org/TR/xslt) (eXtensible Stylesheet Language Transformations). По аналогии было придумано название XJST — eXtensible JavaScript Transformations. 13 | 14 | Для работы с шаблонизатором вам стоит изучить: 15 | 16 | 1. [Формат входных данных — BEMJSON](4-data.md) 17 | 2. [Как писать шаблоны](5-templates-syntax.md) 18 | 3. [Процессы выбора и применения шаблонов](7-runtime.md) 19 | 20 | ## Отличительные черты 21 | 22 | 1. Шаблоны расширяемы: их можно переопределить или доопределить. 23 | 2. Для написания шаблонов используется [сопоставление с образцом](7-runtime.md#Как-выбираются-и-применяются-шаблоны) (pattern matching) по значениям и структуре входных данных. 24 | 3. Обходит входные данные по умолчанию. 25 | 4. Есть встроенное поведение рендеринга по умолчанию, даже если пользователь не добавил шаблонов. 26 | 5. Написан на JavaScript — можно проводить проверки качества и корректности кода, пользоваться всей инфраструктурой JS. 27 | 6. Не требует компиляции шаблонов. 28 | 7. Предоставляет API для добавления шаблонов в рантайме. 29 | 8. Работает на сервере и клиенте. 30 | 31 | Читать далее: [быстрый старт](2-quick-start.md) 32 | -------------------------------------------------------------------------------- /migration/lib/transformers/0-obj-to-func-generator.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var get = require('lodash.get'); 3 | var Transformer = require('../transformer'); 4 | var t = new Transformer(); 5 | 6 | module.exports = function(file, api, opts) { 7 | t.description = 'Use function generator instead (Example: `function() { return { … }; }`. ' + 8 | 'See docs: https://github.com/bem/bem-xjst/wiki/Notable-changes-' + 9 | 'between-bem-xjst@1.x-and-bem-xjst@2.x#static-objects-shortcuts-in-mix-content-etc'; 10 | 11 | t.find = function(file, j) { 12 | return j(file.source) 13 | .find(j.ObjectExpression) 14 | .filter(function(p) { 15 | var arg = p.value.arguments; 16 | var callee = get(p, 'parentPath.parentPath.value.callee.callee'); 17 | 18 | if (! callee) 19 | return false; 20 | 21 | callee = callee.name || (callee.property && callee.property.name); 22 | 23 | return [ 24 | 'attrs', 25 | 'addAttrs', 26 | 'js', 27 | 'addJs', 28 | 'mix', 29 | 'addMix', 30 | 'mods', 31 | 'addMods', 32 | 'elemMods', 33 | 'addElemMods' 34 | ].indexOf(callee) !== -1; 35 | }); 36 | }; 37 | 38 | t.replace = function(ret, j) { 39 | return ret.replaceWith(function(p) { 40 | return j.functionExpression( 41 | j.identifier(''), 42 | [], 43 | j.blockStatement([j.returnStatement(p.value)]) 44 | ); 45 | }); 46 | }; 47 | 48 | return t.run(file, api, opts); 49 | }; 50 | -------------------------------------------------------------------------------- /migration/lib/transformers/2-def-must-return-something.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var Transformer = require('../transformer'); 3 | var t = new Transformer(); 4 | var get = require('lodash.get'); 5 | 6 | module.exports = function(file, api, opts) { 7 | t.description = 'def() mode must return something.'; 8 | 9 | t.find = function(file, j) { 10 | return j(file.source) 11 | .find(j.CallExpression, { callee: { callee: { property: { name: 'def', type: 'Identifier' } } } }) 12 | .find(j.FunctionExpression) 13 | .filter(function(p) { 14 | //if (get(p, 'parentPath.parentPath.value.callee.callee.property.name') === 'def') 15 | //return false; 16 | 17 | var functionBody = p.node.body.body; 18 | var noReturn = function(item) { 19 | return item.type === 'ReturnStatement'; 20 | }; 21 | 22 | return functionBody.filter(noReturn).length === 0; 23 | }); 24 | }; 25 | 26 | t.replace = function(ret, j) { 27 | return ret.replaceWith(function(p) { 28 | var body = p.node.body.body; 29 | var len = body.length; 30 | var b = body[len - 1]; 31 | 32 | if (b.type === 'ExpressionStatement' && 33 | get(b, 'expression.callee.name') === 'applyCtx') { 34 | body[len - 1] = (j.returnStatement(b.expression)); 35 | } else { 36 | body.push(j.returnStatement(j.callExpression(j.identifier('applyNext'), []))); 37 | } 38 | 39 | return j.functionExpression(p.node.id, [], p.node.body) 40 | }); 41 | }; 42 | 43 | return t.run(file, api, opts); 44 | }; 45 | -------------------------------------------------------------------------------- /lib/bemxjst/context.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils'); 2 | 3 | function Context(bemxjst) { 4 | this._bemxjst = bemxjst; 5 | 6 | this.ctx = null; 7 | this.block = ''; 8 | 9 | // Save current block until the next BEM entity 10 | this._currBlock = ''; 11 | 12 | this.elem = null; 13 | this.mods = {}; 14 | this.elemMods = {}; 15 | 16 | this.position = 0; 17 | this._listLength = 0; 18 | this._notNewList = false; 19 | 20 | this.escapeContent = bemxjst.options.escapeContent !== false; 21 | } 22 | exports.Context = Context; 23 | 24 | Context.prototype._flush = null; 25 | 26 | Context.prototype.isSimple = utils.isSimple; 27 | 28 | Context.prototype.isShortTag = utils.isShortTag; 29 | Context.prototype.extend = utils.extend; 30 | Context.prototype.identify = utils.identify; 31 | 32 | Context.prototype.xmlEscape = utils.xmlEscape; 33 | Context.prototype.attrEscape = utils.attrEscape; 34 | Context.prototype.jsAttrEscape = utils.jsAttrEscape; 35 | 36 | Context.prototype.onError = function(context, e) { 37 | console.error('bem-xjst rendering error:', { 38 | block: context.ctx.block, 39 | elem: context.ctx.elem, 40 | mods: context.ctx.mods, 41 | elemMods: context.ctx.elemMods 42 | }, e); 43 | }; 44 | 45 | Context.prototype.isFirst = function() { 46 | return this.position === 1; 47 | }; 48 | 49 | Context.prototype.isLast = function() { 50 | return this.position === this._listLength; 51 | }; 52 | 53 | Context.prototype.generateId = function() { 54 | return utils.identify(this.ctx); 55 | }; 56 | 57 | Context.prototype.reapply = function(ctx) { 58 | return this._bemxjst.run(ctx); 59 | }; 60 | -------------------------------------------------------------------------------- /test/bemjson-elemmods-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('BEMJSON elemMods', function() { 5 | it('should support elemMods', function() { 6 | test(function() {}, 7 | { 8 | block: 'b', 9 | elem: 'e', 10 | elemMods: { type: 'button' } 11 | }, 12 | '
'); 13 | }); 14 | 15 | it('should take elemMods from BEMJSON', function() { 16 | test(function() { 17 | block('b1').elem('e1').content()(function() { 18 | return this.elemMods.a || 'no'; 19 | }); 20 | }, { 21 | block: 'b1', 22 | content: { 23 | elem: 'e1', 24 | elemMods: { a: 'yes' } 25 | } 26 | }, '
yes
'); 27 | }); 28 | 29 | it('should restore elemMods', function() { 30 | test(function() { 31 | block('b2').elem('e1').content()(function() { 32 | return this.elemMods.a || 'yes'; 33 | }); 34 | }, { 35 | block: 'b1', 36 | content: { 37 | elem: 'e1', 38 | elemMods: { 39 | a: 'no' 40 | }, 41 | content: { 42 | block: 'b2', 43 | elem: 'e1' 44 | } 45 | } 46 | 47 | }, '
' + 48 | '
yes
'); 49 | }); 50 | 51 | it('should not treat elemMods as mods', function() { 52 | test(function() {}, { 53 | block: 'b1', 54 | elemMods: { m1: 'v1' } 55 | }, '
'); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/bemcontext-identify-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var bemxjst = require('./fixtures')('bemhtml'); 3 | 4 | describe('BEMContext this.identify()', function() { 5 | it('should work without arguments', function() { 6 | var template = bemxjst.compile(function() { 7 | block('b').def()(function() { 8 | return this.identify(); 9 | }); 10 | }); 11 | 12 | // Compare first 10 symbols 13 | // to avoid bounce tests results because of new Date() 14 | assert.equal(template.apply({ block: 'b' }).substr(0, 10), 15 | ('uniq' + (+new Date()) + 1).substr(0, 10)); 16 | }); 17 | 18 | it('should work with one argument', function() { 19 | var template = bemxjst.compile(function() { 20 | block('b').def()(function() { 21 | var elem = { elem: 'e' }; 22 | return [ this.identify(elem), this.identify(elem) ]; 23 | }); 24 | }); 25 | 26 | var res = template.apply({ block: 'b' }); 27 | 28 | assert.equal(res[0], res[1]); 29 | }); 30 | 31 | it('should work with two arguments', function() { 32 | var template = bemxjst.compile(function() { 33 | block('b').def()(function() { 34 | var elem = { elem: 'e' }; 35 | var i1 = this.identify(elem, true); 36 | this.identify(elem); // set private field in elem object 37 | var i2 = this.identify(elem, true); 38 | return [ 39 | i1, 40 | i2 41 | ]; 42 | }); 43 | }); 44 | 45 | var res = template.apply({ block: 'b' }); 46 | 47 | assert.equal(res[0], undefined); 48 | assert.equal(res[1].substr(0, 11), 49 | ('uniq' + (+new Date()) + 1).substr(0, 11)); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /migration/lib/transformers/3-apply-call-to-apply.js: -------------------------------------------------------------------------------- 1 | var log = require('../logger'); 2 | var Transformer = require('../transformer'); 3 | 4 | module.exports = function(file, api, opts) { 5 | Transformer.prototype.init = function() { 6 | this.member = []; 7 | }; 8 | 9 | var t = new Transformer(); 10 | t.description = 'Since v3.x apply.call(bemjson) must be apply(bemjson)'; 11 | 12 | t.find = function(file, j) { 13 | var transformer = this; 14 | 15 | return j(file.source) 16 | .find(j.MemberExpression, { 17 | property: { type: 'Identifier', name: 'call' } 18 | }) 19 | .filter(function(p) { 20 | var isApply = function(o) { 21 | return o.type === 'Identifier' && o.name === 'apply' 22 | }; 23 | var isMember = function(o) { 24 | return o.type === 'MemberExpression' && isApply(o.property); 25 | } 26 | 27 | var o = p.value.object; 28 | 29 | if (isMember(o)) { 30 | transformer.member.push(o.object.name); 31 | } else if (isApply(o)){ 32 | transformer.member.push(false); 33 | } 34 | 35 | return isApply(o) || isMember(o); 36 | }); 37 | }; 38 | 39 | t.replace = function(ret, j) { 40 | var transformer = this; 41 | 42 | return ret.map(function(item, i) { 43 | return item.replace(transformer.member[i] ? 44 | j.memberExpression( 45 | j.identifier(transformer.member[i]), 46 | j.identifier('apply') 47 | ) 48 | : j.identifier('apply')); 49 | }); 50 | }; 51 | 52 | return t.run(file, api, opts); 53 | }; 54 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | 372 miripiruni 2 | 153 Slava Oliyanchuk 3 | 127 Fedor Indutny 4 | 71 Fedor Indutny 5 | 58 Vladimir Grinenko 6 | 7 v-homyakov 7 | 6 greenkeeperio-bot 8 | 6 Vitaly Harisov 9 | 5 sbmaxx 10 | 4 Vitaly Harisov 11 | 4 Vasiliy Loginevskiy 12 | 4 Vasiliy 13 | 4 Sergey Berezhnoy 14 | 4 Alexey Yaroshevich 15 | 3 Vassily Krasnov 16 | 3 Sergey Berezhnoy 17 | 3 Alexandr Shleyko 18 | 2 dustyo-O 19 | 2 blond 20 | 2 Sergei Bocharov 21 | 2 Mikhail Troshev 22 | 2 Alexey Khlebaev 23 | 1 zhdanov 24 | 1 vkz 25 | 1 baymer 26 | 1 Sergey Berezhnoy 27 | 1 Sergey Belov 28 | 1 Ruslan Posevkin 29 | 1 Rozaev Viktor 30 | 1 Inna Belaya 31 | 1 DoctorDee <33066299+DoctorDee@users.noreply.github.com> 32 | 1 Dmitry Starostin 33 | 1 Dima Belitsky 34 | 1 Andrey <30811474+Akvy@users.noreply.github.com> 35 | 1 Alexey Gurianov 36 | 1 Alexander Savin 37 | 1 Alexander Gulnyashkin 38 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "disallowKeywordsOnNewLine": [ "else" ], 3 | "disallowMixedSpacesAndTabs": true, 4 | "disallowMultipleLineStrings": true, 5 | "disallowMultipleVarDecl": true, 6 | "disallowNewlineBeforeBlockStatements": true, 7 | "disallowQuotedKeysInObjects": true, 8 | "disallowSpaceAfterObjectKeys": true, 9 | "disallowSpaceAfterPrefixUnaryOperators": true, 10 | "disallowSpaceBeforePostfixUnaryOperators": true, 11 | "disallowSpacesInCallExpression": true, 12 | "disallowTrailingComma": true, 13 | "disallowTrailingWhitespace": true, 14 | "disallowYodaConditions": true, 15 | 16 | "requireCommaBeforeLineBreak": true, 17 | "requireOperatorBeforeLineBreak": true, 18 | "requireSpaceAfterBinaryOperators": true, 19 | "requireSpaceAfterKeywords": [ "if", "for", "while", "else", "try", "catch" ], 20 | "requireSpaceAfterLineComment": true, 21 | "requireSpaceBeforeBinaryOperators": true, 22 | "requireSpaceBeforeBlockStatements": true, 23 | "requireSpaceBeforeKeywords": [ "else", "catch" ], 24 | "requireSpaceBeforeObjectValues": true, 25 | "requireSpaceBetweenArguments": true, 26 | "requireSpacesInAnonymousFunctionExpression": { 27 | "beforeOpeningCurlyBrace": true 28 | }, 29 | "requireSpacesInFunctionDeclaration": { 30 | "beforeOpeningCurlyBrace": true 31 | }, 32 | "requireSpacesInFunctionExpression": { 33 | "beforeOpeningCurlyBrace": true 34 | }, 35 | "requireSpacesInConditionalExpression": true, 36 | "requireSpacesInForStatement": true, 37 | "requireSpacesInsideArrayBrackets": "all", 38 | "requireSpacesInsideObjectBrackets": "all", 39 | "requireDotNotation": true, 40 | 41 | "maximumLineLength": 80, 42 | "validateIndentation": 2, 43 | "validateLineBreaks": "LF", 44 | "validateParameterSeparator": ", ", 45 | "validateQuoteMarks": "'" 46 | } 47 | -------------------------------------------------------------------------------- /test/utils-isunquotedattr-test.js: -------------------------------------------------------------------------------- 1 | var utils = require('../lib/bemxjst/utils'); 2 | 3 | describe('Utils', function() { 4 | 5 | var attrCheck = function attrCheck(str) { 6 | return !!utils.isUnquotedAttr(str); 7 | }; 8 | describe('isUnquotedAttr()', function() { 9 | it('should return true with simple class', function() { 10 | attrCheck('b').should.equal(true); 11 | }); 12 | 13 | it('should return false with class with space', function() { 14 | attrCheck('block mixed').should.equal(false); 15 | }); 16 | 17 | it('should return true with class with hyphens', function() { 18 | attrCheck('b-page').should.equal(true); 19 | }); 20 | 21 | it('should return true with class with uppercase', function() { 22 | attrCheck('bPage').should.equal(true); 23 | }); 24 | 25 | it('should return true with class with period', function() { 26 | attrCheck('b.page').should.equal(true); 27 | }); 28 | 29 | it('should return true with class with underscores', function() { 30 | attrCheck('page__content').should.equal(true); 31 | }); 32 | 33 | it('should return true with class with colons', function() { 34 | attrCheck('test:test').should.equal(true); 35 | }); 36 | 37 | it('should return false with double quote', function() { 38 | attrCheck('"test').should.equal(false); 39 | }); 40 | 41 | it('should return true with class with digits', function() { 42 | attrCheck('color333').should.equal(true); 43 | }); 44 | 45 | it('should return true with class with combination of above', function() { 46 | attrCheck('b-page__content_test_100').should.equal(true); 47 | }); 48 | 49 | it('should return false with empty string', function() { 50 | attrCheck('').should.equal(false); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/bemcontext-issimple-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var compile = fixtures.compile; 3 | 4 | describe('BEMContext this.isSimple(arg)', function() { 5 | var bemhtml; 6 | 7 | before(function() { 8 | bemhtml = compile(function() { 9 | block('b').def()(function() { 10 | return this.isSimple(this.ctx.val); 11 | }); 12 | }); 13 | }); 14 | 15 | it('should return true for undefined', function() { 16 | bemhtml.apply({ block: 'b', val: undefined }).should.equal(true); 17 | }); 18 | 19 | it('should return true for null', function() { 20 | bemhtml.apply({ block: 'b', val: null }).should.equal(true); 21 | }); 22 | 23 | it('should return true for Number', function() { 24 | bemhtml.apply({ block: 'b', val: 0 }).should.equal(true); 25 | }); 26 | 27 | it('should return false for NaN', function() { 28 | bemhtml.apply({ block: 'b', val: NaN }).should.equal(true); 29 | }); 30 | 31 | it('should return true for String', function() { 32 | bemhtml.apply({ block: 'b', val: '' }).should.equal(true); 33 | }); 34 | 35 | it('should return true for escaped String', function() { 36 | bemhtml.apply({ block: 'b', val: { html: '' } }).should.equal(true); 37 | }); 38 | 39 | it('should return true for Boolean', function() { 40 | bemhtml.apply({ block: 'b', val: false }).should.equal(true); 41 | }); 42 | 43 | it('should return false for Array', function() { 44 | bemhtml.apply({ block: 'b', val: [] }).should.equal(false); 45 | }); 46 | 47 | it('should return false for Object', function() { 48 | bemhtml.apply({ block: 'b', val: {} }).should.equal(false); 49 | }); 50 | 51 | it('should return false for Function', function() { 52 | bemhtml.apply({ block: 'b', val: function() {} }).should.equal(false); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/modes-tag-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | var assert = require('assert'); 4 | 5 | describe('Modes tag', function() { 6 | it('should throw error when args passed to tag mode', function() { 7 | assert.throws(function() { 8 | fixtures.compile(function() { 9 | block('b1').tag('span'); 10 | }); 11 | }); 12 | }); 13 | 14 | it('should set empty tag', function() { 15 | test(function() { 16 | block('link').tag()(''); 17 | block('button').tag()(false); 18 | }, 19 | { 20 | block: 'button', 21 | content: { 22 | block: 'link', 23 | content: 'link' 24 | } 25 | }, 26 | 'link'); 27 | }); 28 | 29 | it('should set html tag', function() { 30 | test(function() { 31 | block('button').tag()('button'); 32 | }, 33 | { block: 'button' }, 34 | ''); 35 | }); 36 | 37 | it('should override user tag', function() { 38 | test(function() { 39 | block('button').tag()('button'); 40 | }, 41 | { block: 'button', tag: 'a' }, 42 | ''); 43 | }); 44 | 45 | it('user can choose between tag in bemjson ' + 46 | 'and custom value in templates', function() { 47 | test(function() { 48 | block('b').tag()(function() { 49 | return this.ctx.tag || 'strong'; 50 | }); 51 | }, 52 | [ { block: 'b', tag: 'em' }, { block: 'b' } ], 53 | ''); 54 | }); 55 | 56 | it('should not override later declarations', function() { 57 | test(function() { 58 | block('button').tag()('input'); 59 | block('button').tag()('button'); 60 | }, 61 | { block: 'button' }, 62 | ''); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/modes-prependcontent-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('Modes prependContent', function() { 5 | it('should support prependContent', function() { 6 | test(function() { 7 | block('b').prependContent()(function() { 8 | return 'before'; 9 | }); 10 | }, 11 | { block: 'b', content: { block: 'test' } }, 12 | '
before
'); 13 | }); 14 | 15 | it('should support prependContent with literal', function() { 16 | test(function() { 17 | block('b').prependContent()('before '); 18 | }, 19 | { block: 'b', content: 'text' }, 20 | '
before text
'); 21 | }); 22 | 23 | it('should accumulate result', function() { 24 | test(function() { 25 | block('b')( 26 | prependContent()('2'), 27 | prependContent()('4') 28 | ); 29 | }, 30 | { block: 'b', content: ' is the answer' }, 31 | '
42 is the answer
'); 32 | }); 33 | 34 | it('should prepend things to content', function() { 35 | test(function() { 36 | block('b').content()('2'); 37 | block('b').prependContent()('4'); 38 | }, 39 | { block: 'b', content: 'test' }, 40 | '
42
'); 41 | }); 42 | 43 | it('should prepend non simple values to content', function() { 44 | test(function() { 45 | block('foo').prependContent()({ elem: 'test' }); 46 | }, 47 | { block: 'foo' }, 48 | '
'); 49 | }); 50 | 51 | it('should prepend function to content', function() { 52 | test(function() { 53 | block('foo').prependContent()(function() { return { elem: 'test' }; }); 54 | }, 55 | { block: 'foo' }, 56 | '
'); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/bemjson-values-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('BEMJSON values', function() { 5 | it('should work with empty input', function() { 6 | test(function() { 7 | }, '', ''); 8 | }); 9 | 10 | it('should work with false input', function() { 11 | test(function() { 12 | }, false, ''); 13 | }); 14 | 15 | it('should work with null input', function() { 16 | test(function() { 17 | }, null, ''); 18 | }); 19 | 20 | it('should work with 0 input', function() { 21 | test(function() { 22 | }, 0, '0'); 23 | }); 24 | 25 | it('should not render `undefined`', function () { 26 | test(function() { 27 | }, [ 28 | undefined, 29 | undefined, 30 | { block: 'b1' }, 31 | undefined 32 | ], '
'); 33 | }); 34 | 35 | it('should properly save context while render plain html items', function() { 36 | test(function() { 37 | }, { 38 | block: 'aaa', 39 | content: [ 40 | { 41 | elem: 'xxx1', 42 | content: { 43 | block: 'bbb', 44 | elem: 'yyy1', 45 | content: { tag: 'h1', content: 'h 1' } 46 | } 47 | }, 48 | { 49 | elem: 'xxx2' 50 | } 51 | ] 52 | }, '
' + 53 | '
' + 54 | '
' + 55 | '

h 1

' + 56 | '
' + 57 | '
' + 58 | '
' + 59 | '
'); 60 | }); 61 | 62 | it('should return undefined on failed match', function() { 63 | test(function() { 64 | block('b1').content()(function() { 65 | return { elem: 'e1' }; 66 | }); 67 | 68 | block('b1').elem('e1').mod('a', 'b').tag()('span'); 69 | }, { block: 'b1' }, '
'); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /test/modes-addmods-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('Modes addMods', function() { 5 | it('should support addMods', function() { 6 | test(function() { 7 | block('button')( 8 | addMods()(function() { 9 | return { size: 'l' }; 10 | }) 11 | ); 12 | }, 13 | { block: 'button' }, 14 | '
'); 15 | }); 16 | 17 | it('should support addMods with object literal', function() { 18 | test(function() { 19 | block('button').addMods()({ size: 'l' }); 20 | }, 21 | { block: 'button' }, 22 | '
'); 23 | }); 24 | 25 | it('should rewrite mods from bemjson if no other mods templates', 26 | function() { 27 | test(function() { 28 | block('button').addMods()(function() { 29 | return { size: 'l' }; 30 | }); 31 | }, 32 | { block: 'button', mods: { size: 's' } }, 33 | '
'); 34 | }); 35 | 36 | it('should apply templates for mods after mods changes', function() { 37 | test(function() { 38 | block('a')( 39 | mod('withTag', 'span').tag()('span'), 40 | mod('withMix', 'test').mix()('test'), 41 | addMods()({ withTag: 'span' }), 42 | addMods()({ withMix: 'test' }) 43 | ); 44 | }, 45 | { block: 'a' }, 46 | ''); 47 | }); 48 | 49 | it('should accumulate result', function() { 50 | test(function() { 51 | block('button')( 52 | addMods()(function() { 53 | return { theme: 'dark' }; 54 | }), 55 | addMods()(function() { 56 | return { size: 'xl' }; 57 | }) 58 | ); 59 | }, 60 | { block: 'button' }, 61 | '
'); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /lib/bemhtml/entity.js: -------------------------------------------------------------------------------- 1 | var inherits = require('inherits'); 2 | var Match = require('../bemxjst/match').Match; 3 | var BemxjstEntity = require('../bemxjst/entity').Entity; 4 | 5 | /** 6 | * @class Entity 7 | * @param {BEMXJST} bemxjst 8 | * @param {String} block 9 | * @param {String} elem 10 | * @param {Array} templates 11 | */ 12 | function Entity(bemxjst) { 13 | this.bemxjst = bemxjst; 14 | 15 | this.jsClass = null; 16 | 17 | // "Fast modes" about HTML 18 | this.tag = new Match(this, 'tag'); 19 | this.attrs = new Match(this, 'attrs'); 20 | this.bem = new Match(this, 'bem'); 21 | this.cls = new Match(this, 'cls'); 22 | 23 | BemxjstEntity.apply(this, arguments); 24 | } 25 | 26 | inherits(Entity, BemxjstEntity); 27 | exports.Entity = Entity; 28 | 29 | Entity.prototype.init = function(block, elem) { 30 | this.block = block; 31 | this.elem = elem; 32 | 33 | // Class for jsParams 34 | this.jsClass = this.bemxjst.classBuilder.build(this.block, this.elem); 35 | }; 36 | 37 | Entity.prototype._keys = { 38 | tag: 1, 39 | content: 1, 40 | attrs: 1, 41 | mix: 1, 42 | js: 1, 43 | mods: 1, 44 | elemMods: 1, 45 | cls: 1, 46 | bem: 1 47 | }; 48 | 49 | Entity.prototype.defaultBody = function(context) { 50 | context.mods = this.mods.exec(context); 51 | if (context.ctx.elem) context.elemMods = this.elemMods.exec(context); 52 | 53 | return this.bemxjst.render(context, 54 | this, 55 | this.tag.exec(context), 56 | this.js.exec(context), 57 | this.bem.exec(context), 58 | this.cls.exec(context), 59 | this.mix.exec(context), 60 | this.attrs.exec(context), 61 | this.content.exec(context), 62 | context.mods, 63 | context.elemMods); 64 | }; 65 | -------------------------------------------------------------------------------- /test/modes-block-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('Modes block(blockName)', function() { 5 | it('should support block() match', function () { 6 | test(function() { 7 | block('b').content()('ok'); 8 | }, 9 | { block: 'b' }, 10 | '
ok
'); 11 | }); 12 | 13 | it('should match block(*) to empty BEMJSON-object', function() { 14 | test(function() { 15 | block('*').def()(function() { 16 | return 'lol'; 17 | }); 18 | }, 19 | {}, 20 | 'lol'); 21 | }); 22 | 23 | it('should apply block(*) template', function() { 24 | test(function() { 25 | block('*').tag()('b'); 26 | }, 27 | [ 28 | { content: 'foo' }, 29 | { block: 'b' }, 30 | { block: 'input', elem: 'control' } 31 | ], 32 | 'foo'); 33 | }); 34 | 35 | it('block(*) should be called before the matched templates', function() { 36 | test(function() { 37 | block('b1').content()('ok'); 38 | block('b2').content()('yes'); 39 | block('*').content()(function() { 40 | return '#' + applyNext() + '#'; 41 | }); 42 | }, [ { block: 'b1' }, 43 | { block: 'b2' }, 44 | { block: 'b3', content: 'ya' } ], 45 | '
#ok#
' + 46 | '
#yes#
' + 47 | '
#ya#
'); 48 | }); 49 | 50 | it('should apply several block(*) templates in proper order', function() { 51 | test(function() { 52 | block('*').cls()(function() { 53 | return this.ctx.cls; 54 | }); 55 | block('*').cls()(function() { 56 | return applyNext() + '1'; 57 | }); 58 | block('*').cls()(function() { 59 | return applyNext() + '2'; 60 | }); 61 | }, 62 | { block: 'button', cls: 'foo' }, 63 | '
'); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /test/bemjson-block-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('BEMJSON block', function() { 5 | it('should render block by default as div', function () { 6 | test(function() {}, [ 7 | { block: 'b' } 8 | ], '
'); 9 | }); 10 | 11 | it('should not preserve block on tag', function () { 12 | test(function() { 13 | }, [ 14 | { 15 | block: 'b1', 16 | content: { 17 | tag: 'span', 18 | content: { 19 | block: 'b2' 20 | } 21 | } 22 | } 23 | ], '
'); 24 | }); 25 | 26 | it('should inherit block from the parent, and reset it back', function() { 27 | test(function() { 28 | }, { 29 | block: 'b2', 30 | content: [ 31 | { block: 'b1', content: { elem: 'e1' } }, 32 | { elem: 'e1' } 33 | ] 34 | }, '
' + 35 | '
'); 36 | }); 37 | 38 | it('should preserve block on next BEM entity', function() { 39 | test(function() { 40 | }, [ 41 | { 42 | block: 'b1', 43 | content: { 44 | tag: 'span', 45 | content: { 46 | elem: 'e1' 47 | } 48 | } 49 | } 50 | ], '
'); 51 | }); 52 | 53 | it('should not preserve block/elem on tag', function() { 54 | test(function() { 55 | }, [ 56 | { 57 | block: 'b1', 58 | content: { 59 | elem: 'e1', 60 | content: { 61 | tag: 'span', 62 | content: { 63 | block: 'b2' 64 | } 65 | } 66 | } 67 | } 68 | ], '
' + 69 | '
'); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /test/modes-attrs-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | var assert = require('assert'); 4 | var bemxjst = require('../').bemhtml; 5 | 6 | describe('Modes attrs', function() { 7 | it('should throw error when args passed to attrs mode', function() { 8 | assert.throws(function() { 9 | bemxjst.compile(function() { 10 | block('b1').attrs('blah'); 11 | }); 12 | }); 13 | }); 14 | 15 | it('should set attrs', function() { 16 | test(function() { 17 | block('checkbox').attrs()({ 18 | name: undefined, 19 | type: 'button', 20 | disabled: false, 21 | hidden: true, 22 | value: null 23 | }); 24 | }, 25 | { block: 'checkbox' }, 26 | ''); 27 | }); 28 | 29 | it('should override bemjson attrs', function() { 30 | test(function() { 31 | block('button').attrs()({ 32 | type: 'button', 33 | disabled: true 34 | }); 35 | }, 36 | { 37 | block: 'button', 38 | attrs: { 39 | type: 'link', 40 | disabled: undefined, 41 | name: 'button' 42 | } 43 | }, 44 | '
'); 45 | }); 46 | 47 | it('should not modify state of bemxjst if attrs non simple', function() { 48 | test(function() { 49 | block('*') 50 | .match(function() { return this.block; }) 51 | .def()(function(n) { 52 | return n.block === 'ERROR' ? ('Good: ' + n.block) : applyNext(); 53 | }); 54 | 55 | block('image').attrs()({ alt: [ 'Река Ока' ] }); 56 | }, 57 | [ 58 | { 59 | block: 'a', 60 | content: { block: 'image' } 61 | }, 62 | { 63 | block: 'b', 64 | content: { block: 'error' } 65 | } 66 | ], 67 | '
' + 68 | '
'); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/modes-addmix-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('Modes addMix', function() { 5 | it('should support addMix', function() { 6 | test(function() { 7 | block('button').addMix()(function() { 8 | return 'tmpls'; 9 | }); 10 | }, 11 | { block: 'button', mix: { block: 'bemjson' } }, 12 | '
'); 13 | }); 14 | 15 | it('should support addMix with literal', function() { 16 | test(function() { 17 | block('button').addMix()('tmpls'); 18 | }, 19 | { block: 'button', mix: { block: 'bemjson' } }, 20 | '
'); 21 | }); 22 | 23 | it('should render mix with just mods', function() { 24 | test(function() { 25 | block('b').addMix()(function() { 26 | return { mods: { type: 'test' } }; 27 | }); 28 | }, 29 | { block: 'b' }, 30 | '
'); 31 | }); 32 | 33 | it('should accumulate result', function() { 34 | test(function() { 35 | block('button')( 36 | addMix()(function() { 37 | return 'tmpls'; 38 | }), 39 | addMix()(function() { 40 | return 'tmpls1'; 41 | }) 42 | ); 43 | }, 44 | { block: 'button', mix: { block: 'bemjson' } }, 45 | '
'); 46 | }); 47 | 48 | it('should concat with mix from BEMJSON', function() { 49 | test(function() { 50 | block('button').addMix()({ block: 'templ' }); 51 | }, 52 | { block: 'button', mix: { block: 'bemjson' } }, 53 | '
'); 54 | }); 55 | 56 | it('should extend mix', function() { 57 | test(function() { 58 | block('button').mix()({ block: 'templ_1' }); 59 | block('button').addMix()({ block: 'templ_2' }); 60 | }, 61 | { block: 'button', mix: { block: 'bemjson' } }, 62 | '
'); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /bench/lib/compare.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import sys 4 | import json 5 | import codecs 6 | import os 7 | import collections 8 | import subprocess 9 | 10 | 11 | DATAFILE_NAME = 'data.dat' 12 | PLOTFILE_NAME = 'plot' 13 | 14 | 15 | def get_hist(filename): 16 | with open(filename) as uni_data: 17 | return dict(json.load(uni_data)); 18 | 19 | 20 | def get_name(filename): 21 | return os.path.splitext(filename)[0] 22 | 23 | 24 | def get_sorted_buckets(storage): 25 | buckets = set() 26 | 27 | for value in storage.itervalues(): 28 | buckets.update(value.keys()) 29 | 30 | return sorted(buckets) 31 | 32 | 33 | storage = {} 34 | 35 | for jsonfile in sys.argv[2:]: 36 | storage[get_name(jsonfile)] = get_hist(jsonfile) 37 | 38 | shootings_names = sorted(storage.keys()) 39 | buckets = get_sorted_buckets(storage) 40 | normalized_data = collections.OrderedDict() 41 | 42 | for bucket in buckets: 43 | requests = [] 44 | for name in shootings_names: 45 | requests.append(storage[name].get(bucket, 0)) 46 | normalized_data[bucket] = requests 47 | 48 | datafile = codecs.open(DATAFILE_NAME, 'w', 'utf-8') 49 | plotfile = codecs.open(PLOTFILE_NAME, 'w', 'utf-8') 50 | 51 | for bucket, requests in normalized_data.iteritems(): 52 | datafile.write('%d %s\n' % (bucket, ' '.join(str(x) for x in requests))) 53 | 54 | plotfile.write("""set terminal svg size 1024, 768 55 | set ylabel 'requests' 56 | set xlabel 'ms' 57 | plot """) 58 | 59 | for i, name in enumerate(shootings_names): 60 | plotfile.write("'{datafile}' using 1:{pos} with lines title '{name}'{delim}".format( 61 | datafile=DATAFILE_NAME, 62 | pos=i + 2, 63 | name=name, 64 | delim=(', ', '\n')[i == len(shootings_names) - 1] 65 | )) 66 | 67 | subprocess.call('gnuplot {name} > {dir}/out.svg'.format( 68 | name=PLOTFILE_NAME, 69 | dir=sys.argv[1] 70 | ) , shell=True) 71 | 72 | # os.remove(DATAFILE_NAME) 73 | # os.remove(PLOTFILE_NAME) 74 | 75 | -------------------------------------------------------------------------------- /test/fixtures.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | require('chai').should(); 4 | 5 | module.exports = function(engine) { 6 | function compile(fn, options) { 7 | if (typeof fn !== 'function') { 8 | options = fn; 9 | fn = function() {}; 10 | } 11 | 12 | if (!options) options = {}; 13 | 14 | var engineName = options.engine || 'BEMHTML'; 15 | var Engine = require('../lib/' + engineName.toLowerCase()); 16 | var api = new Engine(options); 17 | var template = {}; 18 | 19 | api.compile(fn); 20 | api.exportApply(template); 21 | 22 | return template; 23 | } 24 | 25 | function fail(fn, regexp) { 26 | assert.throws(function() { 27 | compile(fn, { engine: engine }); 28 | }, regexp); 29 | } 30 | 31 | /** 32 | * test helper 33 | * 34 | * @param {?Function} [fn] - matchers 35 | * @param {Object} data - incoming bemjson 36 | * @param {String} expected - expected resulting html 37 | * @param {?Object} [options] - compiler options 38 | */ 39 | function test(fn, data, expected, options) { 40 | if (typeof fn !== 'function') { 41 | options = expected; 42 | expected = data; 43 | data = fn; 44 | fn = function() {}; 45 | } 46 | if (!options) options = {}; 47 | 48 | var template = compile(fn, options); 49 | 50 | if (options.flush) { 51 | template._buf = []; 52 | template.BEMContext.prototype._flush = function flush(str) { 53 | if (str !== '') 54 | template._buf.push(str); 55 | return ''; 56 | }; 57 | } 58 | 59 | // Invoke multiple times 60 | var count = options.count || 1; 61 | for (var i = 0; i < count; i++) { 62 | try { 63 | assert.deepEqual(template.apply(data), expected, i); 64 | } catch (e) { 65 | console.error(e.stack); 66 | throw e; 67 | } 68 | } 69 | 70 | if (options.after) 71 | options.after(template); 72 | } 73 | 74 | return { 75 | test: test, 76 | fail: fail, 77 | compile: compile 78 | }; 79 | }; 80 | -------------------------------------------------------------------------------- /test/modes-appendcontent-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('Modes appendContent', function() { 5 | it('should support appendContent', function() { 6 | test(function() { 7 | block('button').appendContent()(function() { 8 | return 'some text'; 9 | }); 10 | }, 11 | { block: 'button', content: { block: 'test' } }, 12 | '
some text
'); 13 | }); 14 | 15 | it('should support appendContent with literal', function() { 16 | test(function() { 17 | block('button').appendContent()('more text'); 18 | }, 19 | { block: 'button', content: 'text' }, 20 | '
textmore text
'); 21 | }); 22 | 23 | it('should accumulate result', function() { 24 | test(function() { 25 | block('button')( 26 | appendContent()('tmpls_1'), 27 | appendContent()('tmpls_2') 28 | ); 29 | }, 30 | { block: 'button', content: { block: 'test' } }, 31 | '
tmpls_1tmpls_2
'); 32 | }); 33 | 34 | it('should append things to content', function() { 35 | test(function() { 36 | block('button').content()({ block: 'tmpl_1' }); 37 | block('button').appendContent()('tmpl_2'); 38 | block('tmpl_1').tag()('span'); 39 | }, 40 | { block: 'button', content: 'test' }, 41 | '
tmpl_2
'); 42 | }); 43 | 44 | it('should append non simple values to content', function() { 45 | test(function() { 46 | block('foo').appendContent()({ elem: 'test' }); 47 | }, 48 | { block: 'foo' }, 49 | '
'); 50 | }); 51 | 52 | it('should append function to content', function() { 53 | test(function() { 54 | block('foo').appendContent()(function() { return { elem: 'test' }; }); 55 | }, 56 | { block: 'foo' }, 57 | '
'); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/modes-content-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | var assert = require('assert'); 4 | var bemxjst = require('../').bemhtml; 5 | 6 | describe('Modes content', function() { 7 | it('should throw error when args passed to content mode', function() { 8 | assert.throws(function() { 9 | bemxjst.compile(function() { 10 | block('b1').content('blah'); 11 | }); 12 | }); 13 | }); 14 | 15 | it('should set bemjson content', function() { 16 | test(function() { 17 | block('button').content()({ elem: 'text' }); 18 | }, 19 | { block: 'button' }, 20 | '
'); 21 | }); 22 | 23 | it('should set bemjson array content', function() { 24 | test(function() { 25 | block('button').content()([ { elem: 'text1' }, { elem: 'text2' } ]); 26 | }, 27 | { block: 'button' }, 28 | '
' + 29 | '
' + 30 | '
'); 31 | }); 32 | 33 | it('should set bemjson string content', function() { 34 | test(function() { 35 | block('button').content()('Hello World'); 36 | }, 37 | { block: 'button' }, 38 | '
Hello World
'); 39 | }); 40 | 41 | it('should set bemjson numeric content', function() { 42 | test(function() { 43 | block('button').content()(123); 44 | }, 45 | { block: 'button' }, 46 | '
123
'); 47 | }); 48 | 49 | it('should set bemjson zero-numeric content', function() { 50 | test(function() { 51 | block('button').content()(0); 52 | }, 53 | { block: 'button' }, 54 | '
0
'); 55 | }); 56 | 57 | it('should not override later declarations', function() { 58 | test(function() { 59 | block('button').content()({ elem: 'text2' }); 60 | block('button').content()({ elem: 'text1' }); 61 | }, 62 | { block: 'button' }, 63 | '
'); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /test/modes-addelemmods-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('Modes addElemMods', function() { 5 | it('should support addElemMods', function() { 6 | test(function() { 7 | block('button').elem('e')( 8 | addElemMods()(function() { 9 | return { size: 'l' }; 10 | }) 11 | ); 12 | }, 13 | { block: 'button', elem: 'e' }, 14 | '
'); 15 | }); 16 | 17 | it('should support addElemMods with object literal', function() { 18 | test(function() { 19 | block('button').elem('e').addElemMods()({ size: 'l' }); 20 | }, 21 | { block: 'button', elem: 'e' }, 22 | '
'); 23 | }); 24 | 25 | it('should rewrite mods from bemjson if no other mods templates', 26 | function() { 27 | test(function() { 28 | block('button').elem('e').addElemMods()(function() { 29 | return { size: 'l' }; 30 | }); 31 | }, 32 | { block: 'button', elem: 'e', mods: { size: 's' } }, 33 | '
'); 34 | }); 35 | 36 | it('should apply templates for mods after mods changes', function() { 37 | test(function() { 38 | block('a').elem('e')( 39 | elemMod('withTag', 'span').tag()('span'), 40 | elemMod('withMix', 'test').mix()('test'), 41 | addElemMods()({ withTag: 'span' }), 42 | addElemMods()({ withMix: 'test' }) 43 | ); 44 | }, 45 | { block: 'a', elem: 'e' }, 46 | ''); 47 | }); 48 | 49 | it('should accumulate result', function() { 50 | test(function() { 51 | block('button').elem('e')( 52 | addElemMods()(function() { 53 | return { theme: 'dark' }; 54 | }), 55 | addElemMods()(function() { 56 | return { size: 'xl' }; 57 | }) 58 | ); 59 | }, 60 | { block: 'button', elem: 'e' }, 61 | '
'); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /test/modes-wrap-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | var assert = require('assert'); 4 | 5 | describe('Modes wrap', function() { 6 | it('should throw error when args passed to wrap mode', function() { 7 | assert.throws(function() { 8 | fixtures.compile(function() { 9 | block('b1').wrap('blah'); 10 | }); 11 | }); 12 | }); 13 | 14 | it('should support `.wrap()`', function() { 15 | test(function() { 16 | block('b1').wrap()(function() { 17 | return { 18 | block: 'wrap', 19 | content: this.ctx 20 | }; 21 | }); 22 | }, [ { 23 | block: 'b1', 24 | tag: 'a', 25 | content: { 26 | block: 'b1', 27 | tag: 'a' 28 | } 29 | } ], ''); 31 | }); 32 | 33 | it('should support predicates after `wrap()`', function() { 34 | test(function() { 35 | block('b1')( 36 | wrap().match(function() { 37 | return this.ctx.key; 38 | })(function() { 39 | return { 40 | block: 'wrap', 41 | content: this.ctx 42 | }; 43 | }) 44 | ); 45 | }, [ { 46 | block: 'b1', 47 | tag: 'a', 48 | key: 'val' 49 | } ], '
'); 50 | }); 51 | 52 | it('should protected from infinite loop', function() { 53 | test(function() { 54 | block('b1').wrap()(function() { 55 | return { block: 'b2' }; 56 | }); 57 | block('b2').wrap()({ block: 'b1' }); 58 | }, { block: 'b1' }, '
'); 59 | }); 60 | 61 | 62 | it('should use current context (with simple value)', function() { 63 | test(function() { 64 | block('page').wrap()([ { elem: 'head' } ]); 65 | }, { block: 'page' }, '
'); 66 | }); 67 | 68 | it('should use current context (with function)', function() { 69 | test(function() { 70 | block('page').wrap()(function() { return [ { elem: 'head' } ]; }); 71 | }, { block: 'page' }, '
'); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /test/modes-elem-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('Modes elem(elemName)', function() { 5 | it('should support elem() match', function () { 6 | test(function() { 7 | block('b').elem('e').content()('ok'); 8 | }, 9 | { block: 'b', elem: 'e' }, 10 | '
ok
'); 11 | }); 12 | 13 | it('should apply elem(*) template', function() { 14 | test(function() { 15 | block('b').elem('*').tag()('b'); 16 | }, 17 | [ 18 | { block: 'b' }, 19 | { block: 'b', elem: 'foo' }, 20 | { 21 | block: 'b', 22 | elem: 'bar', 23 | content: { 24 | block: 'b', 25 | elem: 'inner', 26 | content: 'test' 27 | } 28 | } 29 | ], 30 | '
' + 31 | 'test'); 32 | }); 33 | 34 | it('elem(*) should be called before the matched templates', 35 | function() { 36 | test(function() { 37 | block('b1').content()(function() { 38 | return 'block'; 39 | }); 40 | block('b1').elem('a').content()(function() { 41 | return 'block-a'; 42 | }); 43 | block('b1').elem('*').content()(function() { 44 | return '%' + applyNext() + '%'; 45 | }); 46 | }, [ 47 | { block: 'b1' }, 48 | { 49 | block: 'b1', 50 | elem: 'a' 51 | }, 52 | { 53 | block: 'b3', 54 | elem: 'b', 55 | content: 'ok' 56 | } 57 | ], '
block
%block-a%
' + 58 | '
%ok%
'); 59 | }); 60 | 61 | it('should apply several elem(*) templates in proper order', function() { 62 | test(function() { 63 | block('b').elem('*').cls()(function() { 64 | return this.ctx.cls; 65 | }); 66 | block('b').elem('*').cls()(function() { 67 | return applyNext() + '1'; 68 | }); 69 | block('b').elem('*').cls()(function() { 70 | return applyNext() + '2'; 71 | }); 72 | }, 73 | { block: 'b', elem: 'e', cls: 'foo' }, 74 | '
'); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /test/modes-match-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | var fail = fixtures.fail; 4 | 5 | describe('Modes match', function() { 6 | it('should support custom matches', function () { 7 | test(function() { 8 | block('b1').content()('!'); 9 | block('b1').match(function() { return this.ctx.test2; }).content()('ok'); 10 | block('b1').match(function() { return this.ctx.test1; }).content()('!'); 11 | }, { block: 'b1', test2: true }, '
ok
'); 12 | }); 13 | 14 | it('should verify match() argument', function() { 15 | fail(function() { 16 | block('b1').match('123')('123'); 17 | }, /Wrong.*match.*argument/); 18 | }); 19 | 20 | it('should’t work without match() argument', function() { 21 | fail(function() { 22 | block('b1').match()('123'); 23 | }, /.match.*must.*have.*argument/); 24 | }); 25 | 26 | it('should execute matches in right order', function() { 27 | test(function() { 28 | block('bla')( 29 | tag()('span'), 30 | // this.ctx.d is undefined 31 | match(function() { return this.ctx.d; })( 32 | tag()('a'), 33 | attrs()(match(function() { return this.ctx.d.a; })(function() { 34 | // this will throw error 35 | return { f: 1 }; 36 | })) 37 | ) 38 | ); 39 | }, { 40 | block: 'bla' 41 | }, ''); 42 | }); 43 | 44 | it('should work with apply() inside match()', 45 | function() { 46 | test(function() { 47 | block('b')( 48 | mode('test')(function() { return true; }), 49 | 50 | match(function() { return apply('test'); }) 51 | .content()(function() { return 'OK'; }) 52 | ); 53 | }, { block: 'b' }, '
OK
'); 54 | }); 55 | 56 | it('should work with apply() with changes inside match()', 57 | function() { 58 | test(function() { 59 | block('b')( 60 | mode('test')(function() { return this.changes; }), 61 | 62 | match(function() { return apply('test', { changes: true }); }) 63 | .content()(function() { return 'OK'; }) 64 | ); 65 | }, { block: 'b' }, '
OK
'); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /lib/bemtree/index.js: -------------------------------------------------------------------------------- 1 | var inherits = require('inherits'); 2 | var BEMXJST = require('../bemxjst'); 3 | var Entity = require('./entity').Entity; 4 | var utils = require('../bemxjst/utils'); 5 | 6 | function BEMTREE() { 7 | BEMXJST.apply(this, arguments); 8 | } 9 | 10 | inherits(BEMTREE, BEMXJST); 11 | module.exports = BEMTREE; 12 | 13 | BEMTREE.prototype.Entity = Entity; 14 | 15 | BEMTREE.prototype.runMany = function(arr) { 16 | var out = []; 17 | var context = this.context; 18 | var prevPos = context.position; 19 | var prevNotNewList = context._notNewList; 20 | 21 | if (prevNotNewList) { 22 | context._listLength += arr.length - 1; 23 | } else { 24 | context.position = 0; 25 | context._listLength = arr.length; 26 | } 27 | context._notNewList = true; 28 | 29 | if (this.canFlush) { 30 | for (var i = 0; i < arr.length; i++) 31 | out += context._flush(this._run(arr[i])); // TODO: fixme! 32 | } else { 33 | for (var i = 0; i < arr.length; i++) 34 | out.push(this._run(arr[i])); 35 | } 36 | 37 | if (!prevNotNewList) 38 | context.position = prevPos; 39 | 40 | return out; 41 | }; 42 | 43 | BEMTREE.prototype.render = function(context, entity, content, js, mix, mods, 44 | elemMods) { 45 | var ctx = utils.extend({}, context.ctx); 46 | var isBEM = !!(ctx.block || ctx.elem || ctx.bem); 47 | 48 | if (typeof js !== 'undefined') 49 | ctx.js = js; 50 | 51 | if (typeof mix !== 'undefined') 52 | ctx.mix = mix; 53 | 54 | if (!entity.elem && mods && Object.keys(mods).length > 0) 55 | ctx.mods = utils.extend(ctx.mods || {}, mods); 56 | 57 | if (entity.elem && elemMods && Object.keys(elemMods).length > 0) 58 | ctx.elemMods = utils.extend(ctx.elemMods || {}, elemMods); 59 | 60 | if (typeof content === 'undefined') 61 | return ctx; 62 | 63 | ctx.content = this.renderContent(content, isBEM); 64 | 65 | return ctx; 66 | }; 67 | 68 | BEMTREE.prototype._run = function(context) { 69 | if (!context || context === true) return context; 70 | return BEMXJST.prototype._run.call(this, context); 71 | }; 72 | 73 | BEMTREE.prototype.runUnescaped = function(context) { 74 | this.context._listLength--; 75 | return context; 76 | }; 77 | -------------------------------------------------------------------------------- /test/modes-js-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | var assert = require('assert'); 4 | 5 | describe('Modes js', function() { 6 | it('should throw error when args passed to js mode', function() { 7 | assert.throws(function() { 8 | fixtures.compile(function() { 9 | block('b1').js('blah'); 10 | }); 11 | }); 12 | }); 13 | 14 | it('should set js', function() { 15 | test(function() { 16 | block('button').js()(true); 17 | }, 18 | { block: 'button' }, 19 | '
'); 20 | }); 21 | 22 | it('should not set js', function() { 23 | test(function() { 24 | block('button').js()(false); 25 | }, 26 | { block: 'button' }, 27 | '
'); 28 | }); 29 | 30 | it('should override user declarations', function() { 31 | test(function() { 32 | block('button').js()(true); 33 | }, 34 | { block: 'button', js: false }, 35 | '
'); 36 | }); 37 | 38 | it('should not override later declarations #1', function() { 39 | test(function() { 40 | block('button').js()(false); 41 | block('button').js()(true); 42 | }, 43 | { block: 'button' }, 44 | '
'); 45 | }); 46 | 47 | it('should not override later declarations #2', function() { 48 | test(function() { 49 | block('button').js()(true); 50 | block('button').js()(false); 51 | }, 52 | { block: 'button' }, 53 | '
'); 54 | }); 55 | 56 | it('should render i-bem for elems with elemJsInstances option', function() { 57 | test(function() { 58 | block('b').elem('e').js()(true); 59 | }, 60 | { block: 'b', elem: 'e' }, 61 | '
', 62 | { elemJsInstances: true }); 63 | }); 64 | 65 | it('should override js from bemjson via js from templates', function() { 66 | test(function() { 67 | block('b').js()({ templ: '1' }); 68 | }, 69 | { block: 'b', js: { bemjson: '2' } }, 70 | '
'); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /test/modes-addattrs-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('Modes addAttrs', function() { 5 | it('should support addAttrs', function() { 6 | test(function() { 7 | block('button')( 8 | addAttrs()(function() { 9 | return { id: 'test' }; 10 | }) 11 | ); 12 | }, 13 | { block: 'button' }, 14 | '
'); 15 | }); 16 | 17 | it('should support addAttrs with object literal', function() { 18 | test(function() { 19 | block('button').addAttrs()({ id: 'test' }); 20 | }, 21 | { block: 'button' }, 22 | '
'); 23 | }); 24 | 25 | it('should extend attrs from BEMJSON', function() { 26 | test(function() { 27 | block('button')( 28 | addAttrs()(function() { 29 | return { c: 'd' }; 30 | }) 31 | ); 32 | }, 33 | { block: 'button', attrs: { a: 'b' } }, 34 | '
'); 35 | }); 36 | 37 | it('should rewrite attrs from bemjson if no other attrs templates', 38 | function() { 39 | test(function() { 40 | block('button')( 41 | addAttrs()(function() { 42 | return { id: 'from-tmpls' }; 43 | }) 44 | ); 45 | }, 46 | { block: 'button', attrs: { id: 'from-data' } }, 47 | '
'); 48 | }); 49 | 50 | it('should accumulate result', function() { 51 | test(function() { 52 | block('button')( 53 | addAttrs()(function() { 54 | return { one: 'true' }; 55 | }), 56 | addAttrs()(function() { 57 | return { two: 'false' }; 58 | }) 59 | ); 60 | }, 61 | { block: 'button' }, 62 | '
'); 63 | }); 64 | 65 | it('should extend attrs from attrs mode', function() { 66 | test(function() { 67 | block('button')( 68 | attrs()(function() { 69 | return { id: 'action' }; 70 | }), 71 | addAttrs()(function() { 72 | return { name: 'test' }; 73 | }) 74 | ); 75 | }, 76 | { block: 'button' }, 77 | '
'); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/modes-replace-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | var assert = require('assert'); 4 | 5 | describe('Modes replace', function() { 6 | it('should throw error when args passed to replace mode', function() { 7 | assert.throws(function() { 8 | fixtures.compile(function() { 9 | block('b1').replace('blah'); 10 | }); 11 | }); 12 | }); 13 | 14 | it('should support basic mode of operation', function () { 15 | test(function() { 16 | block('b1').content()('ok'); 17 | block('b2').content()('replaced'); 18 | block('b1').replace()(function () { return { block: 'b2' }; }); 19 | }, { block: 'b1' }, '
replaced
'); 20 | }); 21 | 22 | it('should have proper `this`', function () { 23 | test(function() { 24 | block('b1').content()('ok'); 25 | block('b2').content()('replaced'); 26 | block('b1').replace()(function () { return { block: this.ctx.wtf }; }); 27 | }, { block: 'b1', wtf: 'b2' }, '
replaced
'); 28 | }); 29 | 30 | it('should work as a singular function', function () { 31 | test(function() { 32 | block('b1').content()('ok'); 33 | block('b2').content()('replaced'); 34 | block('b1')(replace()(function () { return { block: 'b2' }; })); 35 | }, { block: 'b1' }, '
replaced
'); 36 | }); 37 | 38 | it('should support inline argument', function () { 39 | test(function() { 40 | block('b1').content()('ok'); 41 | block('b2').content()('replaced'); 42 | block('b1').replace()({ block: 'b2' }); 43 | }, { block: 'b1' }, '
replaced
'); 44 | }); 45 | 46 | it('should not protected from infinite loop', function() { 47 | assert.throws(function() { 48 | block('b1').replace()({ block: 'b2' }); 49 | block('b2').replace()({ block: 'b1' }); 50 | }); 51 | }); 52 | 53 | it('should not match on removed mods', function () { 54 | test(function() { 55 | block('b1').mod('a', 'b').replace()(function() { 56 | return { 57 | block: 'b1', 58 | content: 'content' 59 | }; 60 | }); 61 | }, { 62 | block: 'b1', 63 | mods: { a: 'b' } 64 | }, '
content
'); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /test/bemjson-mods-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var compile = fixtures.compile; 3 | var test = fixtures.test; 4 | 5 | describe('BEMJSON mods', function() { 6 | var tmpls; 7 | beforeEach(function() { 8 | tmpls = compile(function() { 9 | block('button').def()(function() { 10 | return JSON.stringify(this.mods); 11 | }); 12 | block('button').elem('*').def()(function() { 13 | return JSON.stringify(this.mods); 14 | }); 15 | }); 16 | }); 17 | 18 | it('should return empty mods', function() { 19 | tmpls.apply({ block: 'button' }).should.equal('{}'); 20 | }); 21 | 22 | it('should return mods', function() { 23 | tmpls.apply({ block: 'button', mods: { type: 'button' } }) 24 | .should.equal('{"type":"button"}'); 25 | }); 26 | 27 | it('should return boolean mods', function() { 28 | tmpls.apply({ block: 'button', mods: { disabled: true } }) 29 | .should.equal('{"disabled":true}'); 30 | }); 31 | 32 | it('should not treat mods as elemMods', function() { 33 | test(function() {}, { 34 | block: 'b1', 35 | content: { 36 | elem: 'e1', 37 | mods: { m1: 'v1' } 38 | } 39 | }, '
'); 40 | }); 41 | 42 | it('should not treat mods as elemMods even if block exist', function() { 43 | test(function() {}, { 44 | block: 'b1', 45 | content: { 46 | block: 'b1', 47 | elem: 'e1', 48 | mods: { m1: 'v1' } 49 | } 50 | }, '
'); 51 | }); 52 | 53 | it('should not treat mods as elemMods in mixes', function() { 54 | test(function() {}, { 55 | block: 'b1', 56 | mix: { 57 | elem: 'e1', 58 | mods: { m1: 'v1' } 59 | } 60 | }, '
'); 61 | }); 62 | 63 | it('should not inherit mods from namesake parent block', function() { 64 | test(function() {}, { 65 | block: 'b1', 66 | mods: { a: 1 }, 67 | content: { block: 'b1' } 68 | }, '
'); 69 | }); 70 | 71 | it('should not render epmty modName mods', function() { 72 | test(function() {}, { 73 | block: 'b', 74 | mods: { a: 1, '': 2 } 75 | }, '
'); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /bin/bem-xjst: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var fs = require('fs'); 3 | var bem_xjst = require('../'); 4 | 5 | require('coa').Cmd() 6 | .name(process.argv[1]) 7 | .helpful() 8 | .opt() 9 | .name('version') 10 | .title('Version') 11 | .short('v') 12 | .long('version') 13 | .only() 14 | .flag() 15 | .act(function(opts) { 16 | // return message as result of action 17 | var package = fs.readFileSync(__dirname + 18 | '/../package.json'); 19 | return JSON.parse(package).version; 20 | }) 21 | .end() 22 | .opt() 23 | .name('input').title('File with user templates (default: stdin)') 24 | .short('i').long('input') 25 | .input() 26 | .end() 27 | .opt() 28 | .name('output').title('Output bundle file (default: stdout)') 29 | .short('o').long('output') 30 | .output() 31 | .end() 32 | .opt() 33 | .name('engine').title('Engine name (default: bemhtml, supported: bemhtml | bemtree)') 34 | .short('e').long('engine') 35 | .def('bemhtml') 36 | .end() 37 | .opt() 38 | .name('sourcemap').title('Generate source map (default: false)') 39 | .short('s').long('sourcemap') 40 | .def(false) 41 | .flag() 42 | .end() 43 | .act(function(options) { 44 | return new Promise(function(resolve) { 45 | var input = []; 46 | 47 | options.input.on('data', function(chunk) { 48 | input.push(chunk); 49 | }); 50 | 51 | options.input.once('end', function() { 52 | finish(input.join('')); 53 | }); 54 | 55 | options.input.resume(); 56 | 57 | function finish(source) { 58 | var out = bem_xjst[options.engine].generate(source, { 59 | 'no-opt': options['no-opt'], 60 | from: options.input.path, 61 | to: options.output.path, 62 | sourceMap: options.sourceMap 63 | }); 64 | 65 | options.output.write(out); 66 | 67 | if (options.output === process.stdout) { 68 | options.output.write('\n'); 69 | } else { 70 | options.output.end(); 71 | } 72 | 73 | resolve(); 74 | } 75 | }); 76 | }) 77 | .run(); 78 | -------------------------------------------------------------------------------- /test/bemjson-cls-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var compile = fixtures.compile; 3 | 4 | describe('BEMJSON cls', function() { 5 | it('should not return undefined as cls value', function() { 6 | compile('') 7 | .apply({ cls: undefined }) 8 | .should.equal('
'); 9 | }); 10 | 11 | it('should not return null as cls value', function() { 12 | compile('') 13 | .apply({ cls: null }) 14 | .should.equal('
'); 15 | }); 16 | 17 | it('should return Number as cls value', function() { 18 | compile('') 19 | .apply({ cls: -100 }) 20 | .should.equal('
'); 21 | }); 22 | 23 | it('should not return zero as cls value', function() { 24 | compile('') 25 | .apply({ cls: 0 }) 26 | .should.equal('
'); 27 | }); 28 | 29 | it('should not return NaN as cls value', function() { 30 | compile('') 31 | .apply({ cls: NaN }) 32 | .should.equal('
'); 33 | }); 34 | 35 | it('should return String as cls value', function() { 36 | compile('') 37 | .apply({ cls: 'name' }) 38 | .should.equal('
'); 39 | }); 40 | 41 | it('should not return empty string as cls value', function() { 42 | compile('') 43 | .apply({ cls: '' }) 44 | .should.equal('
'); 45 | }); 46 | 47 | it('should return true as cls value', function() { 48 | compile('') 49 | .apply({ cls: true }) 50 | .should.equal('
'); 51 | }); 52 | 53 | it('should not return false as cls value', function() { 54 | compile('') 55 | .apply({ cls: false }) 56 | .should.equal('
'); 57 | }); 58 | 59 | it('should return Array as cls value', function() { 60 | compile('') 61 | .apply({ cls: [] }) 62 | .should.equal('
'); 63 | }); 64 | 65 | it('should not return Object as cls value', function() { 66 | compile('') 67 | .apply({ cls: { a: 1, b: 2 } }) 68 | .should.equal('
'); 69 | }); 70 | 71 | it('should trim cls', function() { 72 | compile('') 73 | .apply({ cls: ' hello ' }) 74 | .should.equal('
'); 75 | }); 76 | 77 | it('should escape cls', function() { 78 | compile('') 79 | .apply({ block: 'b', cls: '">' }) 80 | .should.equal('
'); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/modes-addjs-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('Modes addJs', function() { 5 | it('should support addJs', function() { 6 | test(function() { 7 | block('button')( 8 | addJs()(function() { 9 | return { test: 1 }; 10 | }) 11 | ); 12 | }, 13 | { block: 'button' }, 14 | '
'); 15 | }); 16 | 17 | it('should support addJs with object literal', function() { 18 | test(function() { 19 | block('button').addJs()({ test: 1 }); 20 | }, 21 | { block: 'button' }, 22 | '
'); 23 | }); 24 | 25 | it('should rewrite js from bemjson if no other js templates', 26 | function() { 27 | test(function() { 28 | block('button')( 29 | addJs()(function() { 30 | return { test: 2 }; 31 | }) 32 | ); 33 | }, 34 | { block: 'button', js: { test: 1 } }, 35 | '
'); 36 | }); 37 | 38 | it('should accumulate result', function() { 39 | test(function() { 40 | block('button')( 41 | addJs()(function() { 42 | return { one: 1 }; 43 | }), 44 | addJs()(function() { 45 | return { two: 42 }; 46 | }) 47 | ); 48 | }, 49 | { block: 'button' }, 50 | '
'); 52 | }); 53 | 54 | it('should extend js from BEMJSON', function() { 55 | test(function() { 56 | block('button')( 57 | addJs()(function() { 58 | return { type: 'serial' }; 59 | }) 60 | ); 61 | }, 62 | { block: 'button', js: { film: 'lost' } }, 63 | '
'); 65 | }); 66 | 67 | it('should extend js from js mode', function() { 68 | test(function() { 69 | block('button')( 70 | js()(function() { 71 | return { film: 'lost' }; 72 | }), 73 | addJs()(function() { 74 | return { type: 'serial' }; 75 | }) 76 | ); 77 | }, 78 | { block: 'button' }, 79 | '
'); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/bemjson-bem-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('BEMJSON bem', function() { 5 | it('should not render class if bem equals false', function () { 6 | test(function() {}, 7 | { block: 'b', bem: false }, 8 | '
'); 9 | }); 10 | 11 | it('should render class if bem equals false and cls field is set', 12 | function () { 13 | test(function() {}, 14 | { cls: 'test', bem: false }, 15 | '
'); 16 | }); 17 | 18 | it('should render class if bem equals true', function () { 19 | test(function() {}, 20 | { block: 'b', bem: true }, 21 | '
'); 22 | }); 23 | 24 | it('should render class if bem equals undefined', function() { 25 | test(function() {}, 26 | { block: 'b', bem: undefined }, 27 | '
'); 28 | }); 29 | 30 | it('should render class if bem equals null', function() { 31 | test(function() {}, 32 | { block: 'b', bem: null }, 33 | '
'); 34 | }); 35 | 36 | it('should render class if bem equals Number', function() { 37 | test(function() {}, 38 | { block: 'b', bem: 100 }, 39 | '
'); 40 | }); 41 | 42 | it('should render class if bem equals zero', function() { 43 | test(function() {}, 44 | { block: 'b', bem: 0 }, 45 | '
'); 46 | }); 47 | 48 | it('should render class if bem equals NaN', function() { 49 | test(function() {}, 50 | { block: 'b', bem: NaN }, 51 | '
'); 52 | }); 53 | 54 | it('should render class if bem equals String', function() { 55 | test(function() {}, 56 | { block: 'b', bem: 'skipme' }, 57 | '
'); 58 | }); 59 | 60 | it('should render class if bem equals empty string', function() { 61 | test(function() {}, 62 | { block: 'b', bem: '' }, 63 | '
'); 64 | }); 65 | 66 | it('should not return Array as attrs value', function() { 67 | test(function() {}, 68 | { block: 'b', bem: [ 1, 2 ] }, 69 | '
'); 70 | }); 71 | 72 | it('should output cls value if bem:false', function() { 73 | test(function() { 74 | block('b').js()(true); 75 | }, 76 | [ 77 | { block: 'b', bem: false, cls: 'anything' }, 78 | { block: 'b', bem: false } 79 | ], 80 | '
'); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/runtime-escaping-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('Content escaping', function() { 5 | it('should escape content if escapeContent option flag is set', function() { 6 | test(function() {}, 7 | { block: 'b', content: '' 72 | }, 73 | ''); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/modes-mods-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('Modes mods', function() { 5 | it('should support mods', function() { 6 | test(function() { 7 | block('button').mods()(function() { 8 | return { size: 'l' }; 9 | }); 10 | }, 11 | { block: 'button' }, 12 | '
'); 13 | }); 14 | 15 | it('should support mods with object literal', function() { 16 | test(function() { 17 | block('button').mods()({ size: 'l' }); 18 | }, 19 | { block: 'button' }, 20 | '
'); 21 | }); 22 | 23 | it('should rewrite mods from bemjson if no other mods templates', 24 | function() { 25 | test(function() { 26 | block('button').mods()(function() { 27 | return { size: 'l' }; 28 | }); 29 | }, 30 | { block: 'button', mods: { size: 's' } }, 31 | '
'); 32 | }); 33 | 34 | it('should rewrite previous mods', function() { 35 | test(function() { 36 | block('button')( 37 | mods()(function() { 38 | return { theme: 'dark' }; 39 | }), 40 | mods()(function() { 41 | return { size: 'xl' }; 42 | }) 43 | ); 44 | }, 45 | { block: 'button', mods: { type: 'promo' } }, 46 | '
'); 47 | }); 48 | 49 | it('should return this.mods by default', function() { 50 | test(function() { 51 | block('a').def()(function() { return JSON.stringify(apply('mods')); }); 52 | block('b').def()(function() { return JSON.stringify(apply('mods')); }); 53 | }, 54 | [ { block: 'a' }, { block: 'b', mods: { type: 'promo' } } ], 55 | '{}{"type":"promo"}'); 56 | }); 57 | 58 | it('should apply templates for mods after mods changes', function() { 59 | test(function() { 60 | block('a')( 61 | mod('test', 'ok').tag()('span'), 62 | mods()({ test: 'ok' }) 63 | ); 64 | }, 65 | { block: 'a' }, 66 | ''); 67 | }); 68 | 69 | it('should support applyNext()', function() { 70 | test(function() { 71 | block('b')( 72 | mods()(function() { 73 | return this.extend(applyNext(), { theme: 'dark' }); 74 | }), 75 | mods()(function() { 76 | return this.extend(applyNext(), { size: 'xl' }); 77 | }) 78 | ); 79 | }, 80 | { block: 'b', mods: { type: 'promo' } }, 81 | '
'); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /test/bemcontext-xmlescape-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var compile = fixtures.compile; 3 | var test = fixtures.test; 4 | 5 | describe('BEMContext this.xmlEscape()', function() { 6 | it('should escape lower than, greater than and ampersands', function() { 7 | test(function() { 8 | block('button').def()(function() { 9 | return this.xmlEscape('&'); 10 | }); 11 | }, 12 | { block: 'button' }, 13 | '<b>&</b>'); 14 | }); 15 | 16 | describe('Type of argument', function() { 17 | var bemhtml; 18 | 19 | before(function() { 20 | bemhtml = compile(function() { 21 | block('b').def()(function(n, ctx) { 22 | return n.xmlEscape(ctx.val); 23 | }); 24 | }); 25 | }); 26 | 27 | it('should return \'\' for undefined', function() { 28 | bemhtml.apply({ block: 'b', val: undefined }).should.equal(''); 29 | }); 30 | 31 | it('should return \'\' for null', function() { 32 | bemhtml.apply({ block: 'b', val: null }).should.equal(''); 33 | }); 34 | 35 | it('should return String for zero', function() { 36 | bemhtml.apply({ block: 'b', val: 0 }).should.equal('0'); 37 | }); 38 | 39 | it('should return String for Number', function() { 40 | bemhtml.apply({ block: 'b', val: 42 }).should.equal('42'); 41 | }); 42 | 43 | it('should return \'\' for NaN', function() { 44 | bemhtml.apply({ block: 'b', val: NaN }).should.equal(''); 45 | }); 46 | 47 | it('should return String for String', function() { 48 | bemhtml.apply({ block: 'b', val: '' }).should.equal(''); 49 | }); 50 | 51 | it('should return String for String', function() { 52 | bemhtml.apply({ block: 'b', val: 'test' }).should.equal('test'); 53 | }); 54 | 55 | it('should return String for Boolean', function() { 56 | bemhtml.apply({ block: 'b', val: false }).should.equal('false'); 57 | }); 58 | 59 | it('should return String for Array', function() { 60 | bemhtml.apply({ block: 'b', val: [] }).should.equal(''); 61 | }); 62 | 63 | it('should return String for Array', function() { 64 | bemhtml.apply({ block: 'b', val: [ 'a', 'b' ] }).should.equal('a,b'); 65 | }); 66 | 67 | it('should return \'\' for Object', function() { 68 | bemhtml.apply({ block: 'b', val: { 69 | toString: function() { return ''; } 70 | } }).should.equal(''); 71 | }); 72 | 73 | it('should return \'\' for Function', function() { 74 | bemhtml.apply({ block: 'b', val: function() {} }) 75 | .should.equal('function() {}'); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /runtime-lint/stand.js: -------------------------------------------------------------------------------- 1 | var bemxjst = require('../'); 2 | var bemhtml = bemxjst.bemhtml; 3 | 4 | var templates = bemhtml.compile(function() { 5 | block('b').content()('yay'); 6 | 7 | block('class-attr-tmpl').attrs()(function() { 8 | return { class: 'wrong' }; 9 | }); 10 | 11 | block('databem-attr-tmpl').attrs()(function() { 12 | return { 'data-bem': 'wrong' }; 13 | }); 14 | 15 | block('mix-mod-tmpl').mix()(function() { 16 | return [ applyNext(), { mods: { color: 'green' } } ]; 17 | }); 18 | 19 | block('mix-elemmod-tmpl').elem('e').mix()(function() { 20 | return [ applyNext(), { elemMods: { color: 'green' } } ]; 21 | }); 22 | }, { runtimeLint: true }); 23 | 24 | var html = templates.apply([ 25 | { block: 'b' }, 26 | 27 | // boolean attributes 28 | { block: 'b', attrs: { one: true, two: 'true' } }, 29 | 30 | // mods for elem 31 | { block: 'c', elem: 'e', mods: { test: 'opa' } }, 32 | 33 | // class in attrs 34 | { block: 'class-attr-bemjson', attrs: { id: 'test', class: 'jquery' } }, 35 | { block: 'class-attr-tmpl' }, 36 | 37 | // 'data-bem' in attrs 38 | { block: 'databem-attr-bemjson', attrs: { 'data-bem': { block: 'a', js: true } } }, 39 | { block: 'databem-attr-tmpl' }, 40 | 41 | // mix the same mod 42 | { block: 'mix-mod', mods: { m: 1 }, mix: { mods: { m: 2 } } }, 43 | { block: 'mix-mod', mix: [ { mods: { type: 'test' } }, { mods: { type: 'shmest' } } ] }, 44 | // mix the same mod from templates 45 | { block: 'mix-mod-tmpl', mods: { color: 'red' } }, 46 | 47 | { block: 'mix-elemmod', elem: 'e', elemMods: { m: 1 }, mix: { elemMods: { m: 2 } } }, 48 | { block: 'mix-elemmod', elem: 'e', mix: [ { elemMods: { type: 'test' } }, { elemMods: { type: 'shmest' } } ] }, 49 | // mix the same mod from templates 50 | { block: 'mix-elemmod-tmpl', elem: 'e', elemMods: { color: 'red' } }, 51 | 52 | // Wrong names 53 | { block: 'bad__name' }, 54 | { block: 'bad_name' }, 55 | { block: 'b', elem: 'e_e' }, 56 | { block: 'b', elem: 'e__e' }, 57 | { block: 'b', mods: { mod_name: 'bad' } }, 58 | { block: 'b', mods: { mod__name: 'bad' } }, 59 | { block: 'b', mods: { modName: 'very_bad' } }, 60 | { block: 'b', mods: { modName: 'very__bad' } }, 61 | 62 | { block: 'b', elem: 'e', elemMods: { mod_name: 'bad' } }, 63 | { block: 'b', elem: 'e', elemMods: { mod__name: 'bad' } }, 64 | { block: 'b', elem: 'e', elemMods: { modName: 'very_bad' } }, 65 | { block: 'b', elem: 'e', elemMods: { modName: 'very__bad' } }, 66 | 67 | // Wrong mods/elemMods types 68 | { block: 'b', mods: { test: [ 'a', 'b' ] } }, 69 | { block: 'b', elem: 'e', elemMods: { test: [ 'a', 'b' ] } } 70 | ]); 71 | 72 | console.log(html); 73 | -------------------------------------------------------------------------------- /test/runtime-match-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('Runtime Match', function() { 5 | it('should call body function in BEMContext context', function() { 6 | test(function() { 7 | block('b').def()(function() { 8 | return this.constructor.name; 9 | }); 10 | }, { block: 'b' }, 'ContextChild'); 11 | }); 12 | 13 | it('should pass BEMContext as the first argument', function() { 14 | test(function() { 15 | block('b').def()(function(ctx) { 16 | return ctx.constructor.name; 17 | }); 18 | }, { block: 'b' }, 'ContextChild'); 19 | }); 20 | 21 | it('should pass BEMContext instance to custom mode', function() { 22 | test(function() { 23 | block('b').mode('custom')(function(ctx) { 24 | return ctx.constructor.name; 25 | }); 26 | block('b').def()(function() { 27 | return apply('custom'); 28 | }); 29 | }, { block: 'b' }, 'ContextChild'); 30 | }); 31 | 32 | it('should pass bemjson node as the second argument', function() { 33 | test(function() { 34 | block('b').def()(function(_, json) { 35 | return json.foo; 36 | }); 37 | }, { block: 'b', foo: 'bar' }, 'bar'); 38 | }); 39 | 40 | it('should pass json to custom mode', function() { 41 | test(function() { 42 | block('b').mode('custom')(function(_, json) { 43 | return json.foo; 44 | }); 45 | block('b').def()(function() { 46 | return apply('custom'); 47 | }); 48 | }, { block: 'b', foo: 'bar' }, 'bar'); 49 | }); 50 | 51 | it('should pass BEMContext instance and json to match method body', 52 | function() { 53 | test(function() { 54 | block('b').match(function(ctx, json) { 55 | this._what = json.foo + ' ' + ctx.constructor.name; 56 | return true; 57 | }).def()(function() { 58 | return this._what; 59 | }); 60 | }, { block: 'b', foo: 'This is' }, 'This is ContextChild'); 61 | }); 62 | 63 | it('should pass BEMContext instance and json to replace body. #226', 64 | function() { 65 | test(function() { 66 | block('b').replace()(function(ctx, json) { 67 | return json.foo + ' ' + ctx.constructor.name; 68 | }); 69 | }, { block: 'b', foo: 'This is' }, 'This is ContextChild'); 70 | }); 71 | 72 | it('should pass BEMContext instance and json to wrap body', 73 | function() { 74 | test(function() { 75 | block('b').wrap()(function(ctx, json) { 76 | return json.foo + ' ' + ctx.constructor.name; 77 | }); 78 | }, { block: 'b', foo: 'This is' }, 'This is ContextChild'); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/bemcontext-attrescape-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var compile = fixtures.compile; 3 | var test = fixtures.test; 4 | 5 | describe('BEMContext this.attrEscape(str)', function() { 6 | it('should escape xml attr string', function() { 7 | test(function() { 8 | block('button').def()(function() { 9 | return this.attrEscape('a&b&c'); 10 | }); 11 | }, 12 | { block: 'button' }, 13 | '<b id="a" class="bem">a&b&c</b>'); 14 | }); 15 | 16 | describe('Type of argument', function() { 17 | var bemhtml; 18 | 19 | before(function() { 20 | bemhtml = compile(function() { 21 | block('b').def()(function(n, ctx) { 22 | return n.attrEscape(ctx.val); 23 | }); 24 | }); 25 | }); 26 | 27 | it('should return \'\' for undefined', function() { 28 | bemhtml.apply({ block: 'b', val: undefined }).should.equal(''); 29 | }); 30 | 31 | it('should return \'\' for null', function() { 32 | bemhtml.apply({ block: 'b', val: null }).should.equal(''); 33 | }); 34 | 35 | it('should return String for zero', function() { 36 | bemhtml.apply({ block: 'b', val: 0 }).should.equal('0'); 37 | }); 38 | 39 | it('should return String for Number', function() { 40 | bemhtml.apply({ block: 'b', val: 42 }).should.equal('42'); 41 | }); 42 | 43 | it('should return \'\' for NaN', function() { 44 | bemhtml.apply({ block: 'b', val: NaN }).should.equal(''); 45 | }); 46 | 47 | it('should return String for String', function() { 48 | bemhtml.apply({ block: 'b', val: '' }).should.equal(''); 49 | }); 50 | 51 | it('should return String for String', function() { 52 | bemhtml.apply({ block: 'b', val: 'test' }).should.equal('test'); 53 | }); 54 | 55 | it('should return String for Boolean', function() { 56 | bemhtml.apply({ block: 'b', val: false }).should.equal('false'); 57 | }); 58 | 59 | it('should return String for Array', function() { 60 | bemhtml.apply({ block: 'b', val: [] }).should.equal(''); 61 | }); 62 | 63 | it('should return String for Array', function() { 64 | bemhtml.apply({ block: 'b', val: [ 'a', 'b' ] }).should.equal('a,b'); 65 | }); 66 | 67 | it('should return \'\' for Object', function() { 68 | bemhtml.apply({ block: 'b', val: { 69 | toString: function() { return ''; } 70 | } }).should.equal(''); 71 | }); 72 | 73 | it('should return \'\' for Function', function() { 74 | bemhtml.apply({ block: 'b', val: function() {} }) 75 | .should.equal('function() {}'); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/bemcontext-jsattrescape-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var compile = fixtures.compile; 3 | 4 | describe('BEMContext this.jsAttrEscape(str)', function() { 5 | it('should escape quotes and ampersands', function() { 6 | compile(function() { 7 | block('button').def()(function() { 8 | return this.jsAttrEscape('\'&\'\'&\''); 9 | }); 10 | }) 11 | .apply({ block: 'button' }) 12 | .should.equal(''&''&' + 13 | '''); 14 | }); 15 | 16 | describe('Type of argument', function() { 17 | var bemhtml; 18 | 19 | before(function() { 20 | bemhtml = compile(function() { 21 | block('b').def()(function(n, ctx) { 22 | return n.jsAttrEscape(ctx.val); 23 | }); 24 | }); 25 | }); 26 | 27 | it('should return \'\' for undefined', function() { 28 | bemhtml.apply({ block: 'b', val: undefined }).should.equal(''); 29 | }); 30 | 31 | it('should return \'\' for null', function() { 32 | bemhtml.apply({ block: 'b', val: null }).should.equal(''); 33 | }); 34 | 35 | it('should return String for zero', function() { 36 | bemhtml.apply({ block: 'b', val: 0 }).should.equal('0'); 37 | }); 38 | 39 | it('should return String for Number', function() { 40 | bemhtml.apply({ block: 'b', val: 42 }).should.equal('42'); 41 | }); 42 | 43 | it('should return \'\' for NaN', function() { 44 | bemhtml.apply({ block: 'b', val: NaN }).should.equal(''); 45 | }); 46 | 47 | it('should return String for String', function() { 48 | bemhtml.apply({ block: 'b', val: '' }).should.equal(''); 49 | }); 50 | 51 | it('should return String for String', function() { 52 | bemhtml.apply({ block: 'b', val: 'test' }).should.equal('test'); 53 | }); 54 | 55 | it('should return String for Boolean', function() { 56 | bemhtml.apply({ block: 'b', val: false }).should.equal('false'); 57 | }); 58 | 59 | it('should return String for Array', function() { 60 | bemhtml.apply({ block: 'b', val: [] }).should.equal(''); 61 | }); 62 | 63 | it('should return String for Array', function() { 64 | bemhtml.apply({ block: 'b', val: [ 'a', 'b' ] }).should.equal('a,b'); 65 | }); 66 | 67 | it('should return \'\' for Object', function() { 68 | bemhtml.apply({ block: 'b', val: { 69 | toString: function() { return ''; } 70 | } }).should.equal(''); 71 | }); 72 | 73 | it('should return \'\' for Function', function() { 74 | bemhtml.apply({ block: 'b', val: function() {} }) 75 | .should.equal('function() {}'); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/init-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | var bemhtml = require('../').bemhtml; 4 | var assert = require('assert'); 5 | 6 | describe('oninit', function() { 7 | it('should support oninit', function() { 8 | test(function() { 9 | oninit(function(exports) { 10 | exports.apply = function() { 11 | return 'ok'; 12 | }; 13 | }); 14 | }, {}, 'ok'); 15 | }); 16 | 17 | it('should support changing prototype of BEMContext', function() { 18 | test(function() { 19 | oninit(function(exports) { 20 | exports.BEMContext.prototype.yes = 'hah'; 21 | }); 22 | 23 | block('b1').content()(function() { 24 | return this.yes; 25 | }); 26 | }, { 27 | block: 'b1' 28 | }, '
hah
'); 29 | }); 30 | 31 | it('should put BEMContext to sharedContext too', function() { 32 | test(function() { 33 | oninit(function(exports, shared) { 34 | shared.BEMContext.prototype.yes = 'hah'; 35 | }); 36 | 37 | block('b1').content()(function() { 38 | return this.yes; 39 | }); 40 | }, { 41 | block: 'b1' 42 | }, '
hah
'); 43 | }); 44 | 45 | it('should support flushing', function() { 46 | test(function() { 47 | oninit(function(exports) { 48 | exports.BEMContext.prototype._flushIndex = 0; 49 | exports.BEMContext.prototype._flush = function flush(str) { 50 | return '[' + (this._flushIndex++) + '.' + str + ']'; 51 | }; 52 | }); 53 | }, { 54 | block: 'b1', 55 | content: { 56 | block: 'b2' 57 | } 58 | }, '[4.[3.[0.
][2.[1.
]
]
]]'); 59 | }); 60 | 61 | it('should use global object as `this`', function() { 62 | test(function() { 63 | oninit(function() { 64 | this._something = { blah: 42 }; 65 | }); 66 | block('b1').replace()(function() { 67 | /* global _something */ 68 | return _something.blah; 69 | }); 70 | }, { block: 'b1' }, '42'); 71 | }); 72 | 73 | it('should exec new oninit functions on every compile', function() { 74 | var template = bemhtml.compile(function() { 75 | oninit(function(exports) { exports.BEMContext.prototype._one = '1'; }); 76 | block('b').content()(function() { return this._one + this._two; }); 77 | }); 78 | var bemjson = { block: 'b' }; 79 | assert.equal(template.apply(bemjson), '
1undefined
'); 80 | 81 | template.compile(function() { 82 | oninit(function(exports) { 83 | exports.BEMContext.prototype._two = 1; }); 84 | }); 85 | assert.equal(template.apply(bemjson), '
11
'); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /test/modes-extend-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | var assert = require('assert'); 4 | 5 | describe('Modes extend', function() { 6 | it('should throw error when args passed to extend mode', function() { 7 | assert.throws(function() { 8 | fixtures.compile(function() { 9 | block('b1').extend('blah'); 10 | }); 11 | }); 12 | }); 13 | 14 | it('should support basic mode of operation', function () { 15 | test(function() { 16 | block('b')( 17 | extend()(function() { return { test: 'it work’s' }; }), 18 | content()(function() { return this.test; }) 19 | ); 20 | }, { block: 'b' }, '
it work’s
'); 21 | }); 22 | 23 | it('should support inline argument', function () { 24 | test(function() { 25 | block('b')( 26 | extend()({ test: 'it work’s' }), 27 | content()(function() { return this.test; }) 28 | ); 29 | }, { block: 'b' }, '
it work’s
'); 30 | }); 31 | 32 | it('should have proper `this`', function () { 33 | test(function() { 34 | block('b')( 35 | extend()(function() { return { test: this.ctx.wtf }; }), 36 | content()(function() { return this.test; }) 37 | ); 38 | }, { block: 'b', wtf: 'it work’s' }, '
it work’s
'); 39 | }); 40 | 41 | it('should proxy data', function () { 42 | test(function() { 43 | block('b').extend()(function() { return { test: 42 }; }); 44 | block('*').attrs()(function() { return { life: this.test }; }); 45 | }, 46 | { 47 | block: 'b', 48 | content: { block: 'a', content: { block: 'c' } } 49 | }, 50 | '
' + 51 | '
'); 52 | }); 53 | 54 | it('should extend ctx', function () { 55 | test(function() { 56 | block('b').extend()(function() { return { 'ctx.content': 42 }; }); 57 | }, 58 | { block: 'b' }, 59 | '
42
'); 60 | }); 61 | 62 | it('should support applyNext', function () { 63 | test(function() { 64 | block('b').extend()(function() { return { 'ctx.content': 1 }; }); 65 | block('b').extend()(function() { 66 | return { 'ctx.attrs': { id: 'test' } }; }); 67 | }, 68 | { block: 'b' }, 69 | '
1
'); 70 | }); 71 | 72 | it('should pass BEMContext instance and json to extend body', 73 | function() { 74 | test(function() { 75 | block('b')( 76 | extend()(function(ctx, json) { 77 | return { bar: json.foo + ' ' + ctx.constructor.name }; 78 | }), 79 | content()(function() { return this.bar; }) 80 | ); 81 | }, { block: 'b', foo: 'This is' }, 82 | '
This is ContextChild
'); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /migration/README.md: -------------------------------------------------------------------------------- 1 | # Static linter for templates 2 | 3 | Linting allows you see some warnings about template code in STDERR. List of checks you can find in [migration guides](https://github.com/bem/bem-xjst/wiki/Migration-guides). 4 | 5 | `cd migration/ && npm i` 6 | 7 | `cd ../` 8 | 9 | `./migration/lib/index.js --lint --input ./path-to-templates/ --from 7 --to 8` 10 | 11 | where 12 | * `lint` lint option (if not present script will rewrite your templates) 13 | * `input` path to templates (relative to current directory or absolute) 14 | * `7` is major version from. If you specify `0` common check will be included. 15 | * `8` is major to lint. 16 | 17 | Result of linting is console warning like this: 18 | 19 | ``` 20 | BEM-XJST WARNING: 21 | >>>> Function that returns a literal can be replaced with literal itself. 22 | >>>> migration/tmpls/template.js:8:1 23 | >>>> function() { return 42; } 24 | ``` 25 | 26 | # Migration tool for templates 27 | 28 | Migration tool helps you migrate your project to next major version of bem-xjst. 29 | 30 | `cd migration/ && npm i` 31 | 32 | `cd ../` 33 | 34 | `./migration/lib/index.js --input ./path-to-templates/ --from 7 --to 8` 35 | 36 | where 37 | * `7` is current bem-xjst version on your project 38 | * `8` is major version to migrate. 39 | 40 | Notice that templates in `./path-to-templates/` will be overwritten. 41 | 42 | ## Codestyle config 43 | 44 | You can create json file with several 45 | [options](https://github.com/benjamn/recast/blob/52a7ec3eaaa37e78436841ed8afc948033a86252/lib/options.js#L1) that have recast. 46 | 47 | Using `config` option you can pass path to json config: 48 | 49 | `./migration/lib/index.js --input ./path-to-templates-dir --from 4 --to 8 --config ~/my-prj/config/codestyle-config.json` 50 | 51 | Notice: path to json config must be absolute. 52 | 53 | See example of codestyle config `sample-config.json` in this directory. 54 | 55 | # List of transformers (checks) 56 | 57 | * Array to function generator 58 | * Object to function generator 59 | * Don’t check `this.elemMods` 60 | * Don’t check `this.mods` 61 | * If function generator return simple type rewrite this function to it’s return value 62 | * Check for HTML entities. Warning about using UTF-8 symbols. 63 | * def() must return something 64 | * No empty mode call 65 | * No empty mode 66 | * No more `this.`. 67 | * Apply call to apply 68 | * API changed (require('bem-xjst').bemhtml) 69 | * `elemMatch()` to `elem()` with `match()` 70 | * `mods` value 71 | * `once()` to `def()` 72 | * `this.isArray()` to `Array.isArray()` 73 | * `xhtml: true` option for backwards capability (from 6 to 7) 74 | * `attrs()` to `addAttrs()` 75 | * `js()` to `addJs()` 76 | * `mix()` to `addMix()` 77 | 78 | 79 | # Tests 80 | 81 | `npm test` 82 | 83 | 84 | # Migrate from old DSL BEMHTML to JS syntax 85 | 86 | Take a look at https://github.com/bem/bem-templates-converter 87 | -------------------------------------------------------------------------------- /test/modes-elemmods-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('Modes elemMods', function() { 5 | it('should support elemMods', function() { 6 | test(function() { 7 | block('button').elem('e').elemMods()(function() { 8 | return { size: 'l' }; 9 | }); 10 | }, 11 | { block: 'button', elem: 'e' }, 12 | '
'); 13 | }); 14 | 15 | it('should support elemMods with object literal', function() { 16 | test(function() { 17 | block('button').elem('e').elemMods()({ size: 'l' }); 18 | }, 19 | { block: 'button', elem: 'e' }, 20 | '
'); 21 | }); 22 | 23 | it('should rewrite elemMods from bemjson if no other mods templates', 24 | function() { 25 | test(function() { 26 | block('button').elem('e').elemMods()(function() { 27 | return { size: 'l' }; 28 | }); 29 | }, 30 | { block: 'button', elem: 'e', elemMods: { size: 's' } }, 31 | '
'); 32 | }); 33 | 34 | it('should rewrite previous elemMods', function() { 35 | test(function() { 36 | block('button').elem('e')( 37 | elemMods()(function() { 38 | return { theme: 'dark' }; 39 | }), 40 | elemMods()(function() { 41 | return { size: 'xl' }; 42 | }) 43 | ); 44 | }, 45 | { block: 'button', elem: 'e', elemMods: { type: 'promo' } }, 46 | '
'); 47 | }); 48 | 49 | it('should return this.elemMods by default', function() { 50 | test(function() { 51 | block('a').elem('e') 52 | .def()(function() { return JSON.stringify(apply('elemMods')); }); 53 | block('b').elem('e') 54 | .def()(function() { return JSON.stringify(apply('elemMods')); }); 55 | }, 56 | [ 57 | { block: 'a', elem: 'e' }, 58 | { block: 'b', elem: 'e', elemMods: { type: 'promo' } } 59 | ], 60 | '{}{"type":"promo"}'); 61 | }); 62 | 63 | it('should apply templates for mods after mods changes', function() { 64 | test(function() { 65 | block('a').elem('e')( 66 | elemMod('test', 'ok').tag()('span'), 67 | elemMods()({ test: 'ok' }) 68 | ); 69 | }, 70 | { block: 'a', elem: 'e' }, 71 | ''); 72 | }); 73 | 74 | it('should support applyNext()', function() { 75 | test(function() { 76 | block('b').elem('e')( 77 | elemMods()(function() { 78 | return this.extend(applyNext(), { theme: 'dark' }); 79 | }), 80 | elemMods()(function() { 81 | return this.extend(applyNext(), { size: 'xl' }); 82 | }) 83 | ); 84 | }, 85 | { block: 'b', elem: 'e', elemMods: { type: 'promo' } }, 86 | '
'); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /examples/simple-page/bemhtml-templates.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | // 1. Common HTML elements — doctype, html, head, meta, title, body 3 | block('page').wrap()(function() { 4 | var ctx = this.ctx; 5 | var data = ctx.data; 6 | 7 | return [ 8 | '', 9 | { 10 | tag: 'html', 11 | attrs: { lang: ctx.lang || 'en-US' }, 12 | content: [ 13 | { 14 | tag: 'head', 15 | content: [ 16 | { tag: 'meta', attrs: { charset: 'utf-8' } }, 17 | { tag: 'title', content: ctx.title } 18 | ] 19 | }, 20 | { 21 | tag: 'body', 22 | content: ctx 23 | } 24 | ] 25 | } 26 | ]; 27 | }); 28 | 29 | // 2. User block — name, username, position 30 | block('user')( 31 | tag()('h1'), 32 | 33 | // Add to content username only if present 34 | match(function() { return this.ctx.username; }) 35 | .content()(function() { 36 | return [ applyNext(), 'aka', this.ctx.username ].join(' '); 37 | }), 38 | 39 | // Add to content position only if present 40 | match(function() { return this.ctx.position; }) 41 | .content()(function() { 42 | return [ applyNext(), this.ctx.position ].join(', '); 43 | }) 44 | ); 45 | 46 | // 3. User avatar 47 | block('avatar').replace()(function() { 48 | var avatar = this.ctx.avatar; 49 | return [ 50 | { 51 | block: 'link', 52 | url: avatar.big.url, 53 | content: { 54 | block: 'image', 55 | url: this.ctx.avatar.small.url 56 | } 57 | }, 58 | { 59 | block: 'para', 60 | content: 'Photo by ' + avatar.small.author 61 | } 62 | ]; 63 | }); 64 | 65 | block('image')( 66 | tag()('img'), 67 | attrs()(function() { 68 | return { src: this.ctx.url }; 69 | }) 70 | ); 71 | 72 | // 4. List of links — service name, link 73 | block('links')( 74 | tag()('ul'), 75 | elem('item')( 76 | tag()('li'), 77 | content()(function() { 78 | return { 79 | block: 'link', 80 | url: this.ctx.url, 81 | content: applyNext() 82 | }; 83 | }) 84 | ) 85 | ); 86 | 87 | block('link')( 88 | tag()('span'), 89 | // Check if there is url field otherwise it’s a span element 90 | match(function() { return this.ctx.url; })( 91 | tag()('a'), 92 | attrs()(function() { 93 | return { 94 | href: this.ctx.url, 95 | target: '_blank' 96 | }; 97 | }), 98 | 99 | // Check if there is no service name field in data 100 | // and try to render it from link 101 | match(function() { return !this.ctx.content; }) 102 | .content()(function() { 103 | return this.ctx.url.replace(/^https?:\/\//, ''); 104 | }) 105 | ) 106 | ); 107 | }; 108 | -------------------------------------------------------------------------------- /test/bemcontext-isfirst-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('BEMContext this.isFirst()', function() { 5 | it('should preserve position', function() { 6 | test(function() { 7 | block('button') 8 | .match(function() { return this.isFirst(); }) 9 | .addMods()({ first: 'yes' }); 10 | 11 | block('button').def()(function() { 12 | return applyNext(); 13 | }); 14 | }, 15 | [ 16 | { block: 'button' }, 17 | { block: 'button' } 18 | ], 19 | '
' + 20 | '
'); 21 | }); 22 | 23 | it('should calc isFirst/isLast with array mess', function() { 24 | test(function() { 25 | block('button').elem('inner')( 26 | match(function() { return this.isFirst(); }) 27 | .addElemMods()(function() { return { first: 'yes' }; }), 28 | 29 | match(function() { return this.isLast(); }) 30 | .addElemMods()(function() { return { last: 'yes' }; }) 31 | ); 32 | }, 33 | { 34 | block: 'button', 35 | content: [ 36 | [ { elem: 'inner' } ], 37 | [ { elem: 'inner' }, [ { elem: 'inner' } ] ] 38 | ] 39 | }, 40 | '
' + 41 | '
' + 42 | '
' + 43 | '
' + 44 | '
'); 45 | }); 46 | 47 | it('should calc isFirst/isLast for single element', function() { 48 | test(function() { 49 | block('button').elem('inner')( 50 | match(function() { return this.isFirst(); }) 51 | .addElemMods()({ first: 'yes' }), 52 | match(function() { return this.isLast(); }) 53 | .addElemMods()({ last: 'yes' }) 54 | ); 55 | }, 56 | { block: 'button', content: { elem: 'inner' } }, 57 | '
' + 58 | '
' + 60 | '
'); 61 | }); 62 | 63 | // TODO: https://github.com/bem/bem-xjst/issues/174 64 | it.skip('should ignore empty array items', function() { 65 | test(function() { 66 | block('button')( 67 | match(function() { return this.isFirst(); }) 68 | .addMods()({ first: 'yes' }), 69 | match(function() { return this.isLast(); }) 70 | .addMods()({ last: 'yes' }) 71 | ); 72 | }, 73 | [ 74 | false, 75 | { block: 'button' }, 76 | { 77 | content: [ 78 | false, 79 | { block: 'button' }, 80 | { block: 'button' }, 81 | { block: 'button' }, 82 | [ null ] 83 | ] 84 | }, 85 | null 86 | ], 87 | '
' + 88 | '
' + 89 | '
' + 90 | '
' + 91 | '
' + 92 | '
'); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /examples/source-maps/demo2/tmpls-with-sourcemap.bemhtml.js: -------------------------------------------------------------------------------- 1 | block('button')( 2 | def()(function() { 3 | var tag = apply('tag'), 4 | isRealButton = (tag === 'button') && (!this.mods.type || this.mods.type === 'submit'); 5 | 6 | return applyNext({ _isRealButton : isRealButton }); 7 | }), 8 | 9 | tag()(function() { 10 | return this.ctx.tag || 'button'; 11 | }), 12 | 13 | js()(true), 14 | 15 | // NOTE: mix below is to satisfy interface of `control` 16 | mix()({ elem : 'control' }), 17 | 18 | attrs()( 19 | // Common attributes 20 | function() { 21 | var ctx = this.ctx, 22 | attrs = { 23 | role : 'button', 24 | tabindex : ctx.tabIndex, 25 | id : ctx.id, 26 | title : ctx.title 27 | }; 28 | 29 | this.mods.disabled && 30 | !this._isRealButton && (attrs['aria-disabled'] = true); 31 | 32 | return attrs; 33 | }, 34 | 35 | // Attributes for button variant 36 | match(function() { return this._isRealButton; })(function() { 37 | var ctx = this.ctx, 38 | attrs = { 39 | type : this.mods.type || 'button', 40 | name : ctx.name, 41 | value : ctx.val 42 | }; 43 | 44 | this.mods.disabled && (attrs.disabled = 'disabled'); 45 | 46 | return this.extend(applyNext(), attrs); 47 | }) 48 | ), 49 | 50 | content()( 51 | function() { 52 | var ctx = this.ctx, 53 | content = [ctx.icon]; 54 | // NOTE: wasn't moved to separate template for optimization 55 | 'text' in ctx && content.push({ elem : 'text', content : ctx.text }); 56 | return content; 57 | }, 58 | match(function() { return typeof this.ctx.content !== 'undefined'; })(function() { 59 | return this.ctx.content; 60 | }) 61 | ) 62 | ); 63 | 64 | block('input')( 65 | tag()('span'), 66 | js()(true), 67 | def()(function() { 68 | return applyNext({ _input : this.ctx }); 69 | }), 70 | content()({ elem : 'box', content : { elem : 'control' } }) 71 | ); 72 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImxpYnMvYmVtLWNvbXBvbmVudHMvY29tbW9uLmJsb2Nrcy9idXR0b24vYnV0dG9uLmJlbWh0bWwiLCJsaWJzL2JlbS1jb21wb25lbnRzL2NvbW1vbi5ibG9ja3MvaW5wdXQvaW5wdXQuYmVtaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUM5REE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6InRtcGwtd2l0aC1zb3VyY2VtYXAuanMifQ== -------------------------------------------------------------------------------- /test/runtime-apply-test.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures')('bemhtml'); 2 | var test = fixtures.test; 3 | 4 | describe('Runtime apply()', function() { 5 | it('apply(\'content\') from def()', function() { 6 | test(function() { 7 | block('b').def()(function() { 8 | return apply('content'); 9 | }); 10 | }, { block: 'b', content: 'test' }, 11 | 'test'); 12 | }); 13 | 14 | it('apply(\'mix\') from def()', function() { 15 | test(function() { 16 | block('b').def()(function() { 17 | return apply('mix'); 18 | }); 19 | }, { block: 'b', mix: 'test' }, 20 | 'test'); 21 | }); 22 | 23 | it('apply(\'tag\') from tag()', function() { 24 | test(function() { 25 | block('b').tag()(function() { 26 | return 'span'; 27 | }); 28 | block('b').tag()(function() { 29 | return apply('tag'); 30 | }); 31 | }, { block: 'b', tag: 'a' }, 32 | ''); 33 | }); 34 | 35 | it('apply(\'tag\') from def()', function() { 36 | test(function() { 37 | block('b').def()(function() { 38 | return apply('tag'); 39 | }); 40 | }, { block: 'b', tag: 'a' }, 41 | 'a'); 42 | }); 43 | 44 | it('apply(\'bem\') from def()', function() { 45 | test(function() { 46 | block('b').def()(function() { 47 | return apply('bem'); 48 | }); 49 | }, { block: 'b', bem: false }, 50 | false); 51 | }); 52 | 53 | it('apply(\'cls\') from def()', function() { 54 | test(function() { 55 | block('b').def()(function() { 56 | return apply('cls'); 57 | }); 58 | }, { block: 'b', cls: 'test' }, 59 | 'test'); 60 | }); 61 | 62 | it('apply(\'attrs\') from def()', function() { 63 | test(function() { 64 | block('b').def()(function() { 65 | return apply('attrs'); 66 | }); 67 | }, { block: 'b', attrs: { target: '_blank' } }, 68 | { target: '_blank' }); 69 | }); 70 | 71 | it('apply(\'js\') from def()', function() { 72 | test(function() { 73 | block('b').def()(function() { 74 | return apply('js'); 75 | }); 76 | }, { block: 'b', js: { data: 'test' } }, 77 | { data: 'test' }); 78 | }); 79 | 80 | it('apply(\'usermode\') from def()', function() { 81 | test(function() { 82 | block('b').def()(function() { 83 | return apply('usermode'); 84 | }); 85 | }, { block: 'b', usermode: 'test' }, 86 | 'test'); 87 | }); 88 | 89 | it('apply(\'undefusermode\') from def()', function() { 90 | test(function() { 91 | block('b').def()(function() { 92 | return apply('undefusermode'); 93 | }); 94 | }, { block: 'b' }, 95 | undefined); 96 | }); 97 | 98 | it('apply(\'\') should lookup field from bemjson by default', function() { 99 | test(function() { 100 | block('b').def()(function() { 101 | return apply('test'); 102 | }); 103 | }, { block: 'b', test: 'bemjson' }, 104 | 'bemjson'); 105 | }); 106 | }); 107 | --------------------------------------------------------------------------------