Toolkit to work with files based on BEM methodology.
You need NodeJS 0.4.x or later and npm 1.x.
-
Install bem-tools
sudo npm -g install bem -
Use this command bem-tools to install the development version
sudo npm -g install bem@unstable
If you are going to use bem with
bem-bl block library, you should also install
XJST and OmetaJS.
sudo npm -g install xjst ometajs
Get the list of commands with bem --help.
To read about commands and subcommands use bem COMMAND --help or bem COMMAND SUBCOMMAND --help.
To make completions for bem-tools available in your bash, run following command (ensure that you have bash-completion installed, first). Run this
bem completion > /path/to/etc/bash_completion.d/bem
and restart bash.
If you aren't using bash-completion, you can add bem completion to your .bashrc:
bem completion >> ~/.bashrc
If you use zsh, you can add bem completion to your .zshrc:
bem completion >> ~/.zshrc
then restart.
You can create following entities using bem create:
- levels of defenition
- blocks
- elements
- modifiers
Level of defenition is a directory that holds blocks and an utility directiry .bem.
A .bem directory holds configuration of a current level:
- naming convention
- links to the technologies
An example of technologies' links (this is blocks-desktop level of
bem-bl block library):
https://github.com/bem/bem-bl/blob/master/blocks-common/.bem/level.js
bem create level blocks
In bem-tools terms pages are blocks as well and a directory which holds pages is a level of
defenition itself. To create such a directory run this:
bem create level pages
bem create level allows to use an existing level as a prototype for a level it creates.
bem create level -l bem-bl/blocks-desktop blocks
Block is a directory that holds block's implementation, some files with different technologies.
bem create block b-my-block
By default, a block has several techs: (bemhtml, css, js).
Flags -t (-T) are to create files of technologies you need:
bem create block -t deps.js b-my-block
// Creates a block implementation in deps.js technology, ecxept of default techs.
bem create block -T css b-my-block
// Creates only CSS technology for a block
bem create block -T bem-bl/blocks-desktop/i-bem/bem/techs/bemhtml.js b-my-block
// -T flag is useful when you need to add a new tech to the block existed
The value of this flag may be either tech's name (e.g css) or a path to tech module.
Tech names may be listed in .bem/level.js file of a level.
E.g., https://github.com/bem/bem-bl/blob/master/blocks-common/.bem/level.js
You can find the examples of tech modules in the repo:
https://github.com/bem/bem-tools/tree/nodejs/lib/techs
bem build command builds page files in different techs, according to a page declaration.
bem build \
-l bem-bl/blocks-common -l bem-bl/blocks-desktop \
-l blocks -l pages/index/blocks \
-d pages/index/index.bemjson.js -t bemdecl.js \
-o pages/index -n index
You can use either tech's name or a path to its module as a value of -t flag. This module says how to build a final file from a declaration.
E.g., this is a module for deps.js: https://github.com/bem/bem-tools/blob/nodejs/lib/techs/deps.js.js
bem build \
-l bem-bl/blocks-common -l bem-bl/blocks-desktop \
-l blocks -l pages/index/blocks \
-d pages/index/index.bemdecl.js -t deps.js \
-o pages/index -n index
bem build \
-l bem-bl/blocks-common -l bem-bl/blocks-desktop \
-l blocks -l pages/index/blocks \
-d pages/index/index.deps.js -t css \
-o pages/index -n index
bem build \
-l bem-bl/blocks-common -l bem-bl/blocks-desktop \
-l blocks -l pages/index/blocks \
-d pages/index/index.deps.js -t js \
-o pages/index -n index
bem build \
-l bem-bl/blocks-common -l bem-bl/blocks-desktop \
-l blocks -l pages/index/blocks \
-d pages/index/index.bemhtml.js \
-t bem-bl/blocks-desktop/i-bem/bem/techs/bemhtml.js \
-o pages/index -n index
There is an example how pages are built using bem build in our test project that uses
bem-bl block library: https://github.com/toivonen/bem-bl-test/blob/master/GNUmakefile
bem decl is to work with declaration files. Thus,
- to merge two or more decls into one
- «subtract» decls
All subcommands of bem decl can take either bemdecl.js or deps.js as input declaration formats.
as input declaration (via -d flag).
Ouput data (-o flag) is always in deps.js format.
bem decl merge is to merge two or more decls into one. It is useful if you need, for example, to build
one file for several pages.
bem decl merge \
-d pages/index/index.deps.js \
-d pages/about/about.deps.js \
-d pages/search/search.deps.js \
-o pages/common/common.deps.js
bem decl subtract is to «subtract» all next decls from the first one.
You may use it to create a bundle that you request by application.
bem decl subtract \
-d bundles/heavy-block/heavy-block.deps.js \
-d pages/common/common.deps.js \
-o bundles/heavy-block/heavy-block.bundle.js
bem server starts a web server which serves static files, dynamic html generated form the BEMHTML and BEMJSON on the
fly, and pipes js and css files through borschik.
By default document root is the current directory. You can change that with the --project (-r) parameter. So if you have
pages/about/main.css file in the project folder it will be accessible with a browser using
http://localhost:8080/pages/about/main.css URL.
The default TCP port the server is listening to is 8080. You can change it with the --port (-p) parameter.
When the server gets a request for some *.html file it will look for appropriate BEMJOSN and BEMHTML files, apply one
to another and return the result if both files do exist. The contents of the *.html file will be returned otherwise.
When requested URL corresponds to a directory server checks for index.html file in it and returns the content. If file is
not found, index.bemhtml.js and index.bemjson.js are checked for existance and the result of the template application is
returned. Otherwise the directory listing is returned.
Look for a documentation in source lib/tech.js.
There are three ways to write a tech module: very simple, simple and advanced.
Whatever manner you use you can get a tech object from this. Any base class is
available from this.__base(...). Thanks to inherit
module that organizes inheritance here.
You only need to create regular CommonJS module and export some of its
functions to redefine them. By default all functions from the base class are put
in Tech module lib/tech.js.
Besides function, you can also export baseTechPath variable to define an
absolute path to a tech module you are extending. By default you are
extending Tech class.
For example:
exports.baseTechPath = require.resolve('bem/lib/techs/css');If you need a total control, you can create a module that exports
the whole Tech class.
var INHERIT = require('inherit'),
BaseTech = require('bem/lib/tech').Tech;
exports.Tech = INHERIT(BaseTech, {
create: function(prefix, vars, force) {
// do some creation work
},
build: function(prefixes, outputDir, outputName) {
// organize own build process
}
});When you need to base your tech on an existing one written in a simple way use
getTechClass() function from bem/lib/tech module
to get its class.
var INHERIT = require('inherit'),
getTechClass = require('bem/lib/tech').getTechClass,
BaseTech = getTechClass(require.resolve('path/to/tech/module'));
exports.Tech = INHERIT(BaseTech, {
// your overrides go here
});Starting from 0.2.0 version it is possible to use bem-tools from API.
bem module exports the object of a command that has an api property.
It is to use in this way:
var Q = require('q'),
BEM = require('bem').api,
techs = ['css', 'js'],
blocks = ['b-block1', 'b-block2'];
Q.when(BEM.create.block({ forceTech: techs }, { names: blocks }), function() {
console.log('Create blocks: %s', blocks.join(', '));
});The example above shows that you can use all the commands (including subcommands).
A command accepts two args:
- Object
optscommand options - Object
argscommand arguments
It returns an object of Q.promise type.
Commands to create BEM entities.
Creates a level of defenition.
- String
outputDira directory of output (current directory by default) - String
levela «prototype» of the level - Boolean
forcekey to force level's creating if it already exists
- Array
namesNamef of levels you are creating
var PATH = require('path'),
Q = require('q'),
BEM = require('bem').api,
outputDir = PATH.join(__dirname, 'levels'),
levels = ['blocks-common', 'blocks-desktop'];
Q.when(BEM.create.level({ outputDir: outputDir }, { names: levels }), function() {
console.log('Create levels %s at %s', levels.join(', '), outputDir);
});Creates a block.
- String
levelDirA directory of block's level. (Current directory by default) - Array
addTechAdd the techs listed - Array
forceTechUse these techs only - Array
noTechExclude these techs - Boolean
forceForce files creating
- Array
namesList of block names
var Q = require('q'),
BEM = require('bem').api,
addTechs = ['bemhtml'],
blocks = ['b-header'];
Q.when(BEM.create.block({ addTech: addTechs }, { names: blocks }), function() {
console.log('Create blocks: %s', blocks.join(', '));
});Creating an element.
- String
levelDirA directory of level. (Current directory by default) - String
blockNameA name of element's block (required) - Array
addTechAdd the techs listed - Array
forceTechUse only the techs listed - Array
noTechExclude the techs listed - Boolean
forceForce creating element's files (to rewrite them)
- Array
namesList of element names
var Q = require('q'),
BEM = require('bem').api,
addTechs = ['bemhtml', 'title.txt'],
block = 'b-header',
elems = ['logo'];
Q.when(BEM.create.elem({ addTech: addTechs, blockName: block }, { names: elems }), function() {
console.log('Create elems %s of block %s', elems.join(', '), block);
});Creating a modifier for a block or an element.
- String
levelDirLevel directory (current directory by default) - String
blockNameBlock name of this modifier (required) - String
elemNameElement name - Array
modValModifier vaue - Array
addTechAd the techs listed - Array
forceTechUse only the techs listed - Array
noTechExclude the techs listed - Boolean
forceForce creating modifier files (rewrite)
- Array
namesList of modifier
var Q = require('q'),
BEM = require('bem').api,
forceTechs = ['css'],
block = 'b-header',
elem = 'logo',
mods = ['lang'],
vals = ['ru', 'en'];
Q.when(BEM.create.mod({ forceTechs: forceTechs, blockName: block, modVal: vals }, { names: mods }), function() {
console.log('Create mod %s of block %s with vals %s', elems.join(', '), block, vals.join(', '));
});
Q.when(BEM.create.mod({ forceTechs: forceTechs, blockName: block, elemName: elem, modVal: vals }, { names: elems }), function() {
console.log('Create mod %s of elem %s of block %s with vals %s', elems.join(', '), elem, block, vals.join(', '));
});Build files from blocks.
- String
outputDirAn output directory (current directory by default) - String
outputNameA filename (its prefix) for output - String
declarationA filename of input declaration (required) - Array
levelList of levels to use - Array
techList of techs to build
var Q = require('q'),
BEM = require('bem').api,
decl = 'page.deps.js',
outputDir = 'build',
outputName = 'page',
levels = ['blocks-common', 'blocks-desktop'],
techs = ['css', 'js'];
Q.when(
BEM.build({
outputDir: outputDir,
outputName: outputName,
declaration: decl,
level: levels,
tech: techs
}),
function() {
console.log('Finished build of techs %s for levels %s. Result in %s/%s.* files.',
techs.join(', '), levels.join(', '), outputDir, outputName);
}
);Commands to work with declarations.
Merging two or more declarations into one.
- String
outputA file for output result. By default output is in STDOUT - Array
declarationList of filenames for declarations (required)
Subtracting the next declarations from the first one.
- String
outputA file for output result. By default output is in STDOUT - Array
declarationList of filenames for declarations (required)
