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
opts
command options - Object
args
command arguments
It returns an object of Q.promise
type.
Commands to create BEM entities.
Creates a level of defenition.
- String
outputDir
a directory of output (current directory by default) - String
level
a «prototype» of the level - Boolean
force
key to force level's creating if it already exists
- Array
names
Namef 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
levelDir
A directory of block's level. (Current directory by default) - Array
addTech
Add the techs listed - Array
forceTech
Use these techs only - Array
noTech
Exclude these techs - Boolean
force
Force files creating
- Array
names
List 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
levelDir
A directory of level. (Current directory by default) - String
blockName
A name of element's block (required) - Array
addTech
Add the techs listed - Array
forceTech
Use only the techs listed - Array
noTech
Exclude the techs listed - Boolean
force
Force creating element's files (to rewrite them)
- Array
names
List 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
levelDir
Level directory (current directory by default) - String
blockName
Block name of this modifier (required) - String
elemName
Element name - Array
modVal
Modifier vaue - Array
addTech
Ad the techs listed - Array
forceTech
Use only the techs listed - Array
noTech
Exclude the techs listed - Boolean
force
Force creating modifier files (rewrite)
- Array
names
List 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
outputDir
An output directory (current directory by default) - String
outputName
A filename (its prefix) for output - String
declaration
A filename of input declaration (required) - Array
level
List of levels to use - Array
tech
List 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
output
A file for output result. By default output is in STDOUT - Array
declaration
List of filenames for declarations (required)
Subtracting the next declarations from the first one.
- String
output
A file for output result. By default output is in STDOUT - Array
declaration
List of filenames for declarations (required)