decl
A tool for working with declarations in BEM.
Introduction
A declaration is a list of BEM entities (blocks, elements and modifiers) and their technologies that are used on a page.
A build tool uses declaration data to narrow down a list of entities that end up in the final project.
This tool contains a number of methods to work with declarations:
Load a declaration from a file and convert it to a set of BEM cells.
Modify sets of BEM cells:
Save a set of BEM cells in a file.
This tool also contains the assign()
method. You can use this method to populate empty BEM cell fields with the fields from the scope.
Note. If you don't have any BEM projects available to try out the
@bem/sdk.decl
package, the quickest way to create one is to use bem-express.
Installation
To install the @bem/sdk.decl
package, run the following command:
npm install --save @bem/sdk.decl
Quick start
Attention. To use
@bem/sdk.decl
, you must install Node.js 8.0+.
Use the following steps after installing the package.
To run the @bem/sdk.decl
package:
Loading declarations from files
Create two files with declarations and insert the following code into them:
set1.bemdecl.js:
exports.blocks = [
{name: 'a'},
{name: 'b'},
{name: 'c'}
];
set2.bemdecl.js:
exports.blocks = [
{name: 'b'},
{name: 'e'}
];
In the same directory, create a JavaScript file with any name (for example, app.js), so your work directory will look like:
app/
├── app.js — your application file.
├── set1.bemdecl.js — the first declaration file.
└── set2.bemdecl.js — the second declaration file.
To get the declarations from the created files, use the load()
method. Insert the following code into your app.js file:
const bemDecl = require('@bem/sdk.decl');
// Since we are using sets stored in files, we need to load them asynchronously.
async function testDecl() {
// Wait for the file to load and set the `set1` variable.
const set1 = await bemDecl.load('set1.bemdecl.js');
// `set1` is an array of BemCell objects.
// Convert them to strings using the `map()` method and special `id` property:
console.log(set1.map(c => c.id));
// => ['a', 'b', 'c']
// Load the second set.
const set2 = await bemDecl.load('set2.bemdecl.js');
console.log(set2.map(c => c.id));
// => ['b', 'e']
}
testDecl();
Subtracting declarations
To subtract one set from another, use the subtract()
method. Insert this code into your async function in your app.js file:
console.log(bemDecl.subtract(set1, set2).map(c => c.id));
// => ['a', 'c']
The result will be different if we swap arguments:
console.log(bemDecl.subtract(set2, set1).map(c => c.id));
// => ['e']
Intersecting declarations
To calculate the intersection between two sets, use the intersect()
method:
console.log(bemDecl.intersect(set1, set2).map(c => c.id));
// => ['b']
Merging declarations
To add elements from one set to another set, use the merge()
method:
console.log(bemDecl.merge(set1, set2).map(c => c.id));
// => ['a', 'b', 'c', 'e']
Saving declarations to a file
To save the merged set, use the save()
method. Normalize the set before saving:
const mergedSet = bemDecl.normalize(bemDecl.merge(set1, set2));
bemDecl.save('mergedSet.bemdecl.js', mergedSet, { format: 'v1', exportType: 'commonjs' })
The full code of the app.js file will look like this:
const bemDecl = require('@bem/sdk.decl');
// Since we are using sets stored in files, we need to load them asynchronously.
async function testDecl() {
// Wait for the file to load and set the `set1` variable.
const set1 = await bemDecl.load('set1.bemdecl.js');
// `set1` is an array of BemCell objects.
// Convert them to strings using the `map()` method and special `id` property:
console.log(set1.map(c => c.id));
// => ['a', 'b', 'c']
// Load the second set.
const set2 = await bemDecl.load('set2.bemdecl.js');
console.log(set2.map(c => c.id));
// => ['b', 'e']
console.log(bemDecl.subtract(set1, set2).map(c => c.id));
// => ['a', 'c']
console.log(bemDecl.subtract(set2, set1).map(c => c.id));
// => ['e']
console.log(bemDecl.intersect(set1, set2).map(c => c.id));
// => ['b']
console.log(bemDecl.merge(set1, set2).map(c => c.id));
// => ['a', 'b', 'c', 'e']
const mergedSet = bemDecl.normalize(bemDecl.merge(set1, set2));
bemDecl.save('mergedSet.bemdecl.js', mergedSet, { format: 'v1', exportType: 'commonjs' })
}
testDecl();
Run the app.js file. The mergedSet.bemdecl.js
file will be created in the same directory with the following code:
module.exports = {
format: 'v1',
blocks: [
{
name: 'a'
},
{
name: 'b'
},
{
name: 'c'
},
{
name: 'e'
}
]
};
BEMDECL formats
There are several formats:
'v1' — The old BEMDECL format, also known as
exports.blocks = [ /* ... */ ]
.'v2' — The format based on
deps.js
files, also known asexports.decl = [ /* ... */ ]
. You can also specify the declaration in thedeps
field:exports.deps = [ /* ... */ ]
like in the 'enb' format.'enb' — The legacy format for the widely used enb deps reader, also known as
exports.deps = [ /* ... */ ]
. This format looks like the 'v2' format, but doesn't support syntactic sugar from this format.
Note.
bem-decl
controls all of them.
API reference
load()
Loads a declaration from the specified file.
This method reads the file and calls the parse() function on its content.
/**
* @param {string} filePath — Path to file.
* @param {Object|string} opts — Additional options.
* @return {Promise} — A promise that represents `BemCell[]`.
*/
format(filePath, opts)
You can pass additional options that are used in the readFile()
method from the Node.js File System.
The declaration in the file can be described in any format.
parse()
Parses the declaration from a string or JS object to a set of BEM cells.
This method automatically detects the format of the declaration and calls a parse()
function for the detected format. Then it normalizes the declaration and converts it to a set of BEM cells.
/**
* @param {string|Object} bemdecl — String of bemdecl or object.
* @returns {BemCell[]} — Set of BEM cells.
*/
parse(bemdecl)
normalize()
Normalizes the array of entities from a declaration for the specified format. If successful, this method returns the list of BEM cells which represents the declaration.
This method is an alternative to the parse()
method. In this method, you pass a format and the declaration contents separately.
/**
* @param {Array|Object} decl — Declaration.
* @param {Object} [opts] — Additional options.
* @param {string} [opts.format='v2'] — Format of the declaration (v1, v2, enb).
* @param {BemCell} [opts.scope] — A BEM cell to use as the scope to populate the fields of normalized entites. Only for 'v2' format.
* @returns {BemCell[]}
*/
normalize(decl, opts)
subtract()
Calculates the set of BEM cells that occur only in the first passed set and do not exist in the rest. Read more.
/**
* @param {BemCell[]} set — Original set of BEM cells.
* @param {...(BemCell[])} removingSet — Set (or sets) with cells that should be removed.
* @returns {BemCell[]} — Resulting set of cells.
*/
subtract(set, removingSet, ...)
intersect()
Calculates the set of BEM cells that exists in each passed set. Read more.
/**
* @param {BemCell[]} set — Original set of BEM cells.
* @param {...(BemCell[])} otherSet — Set (or sets) that should be merged into the original one.
* @returns {BemCell[]} — Resulting set of cells.
*/
intersect(set, otherSet, ...)
merge()
Merges multiple sets of BEM cells into one set. Read more
/**
* @param {BemCell[]} set — Original set of cells.
* @param {...(BemCell[])} otherSet — Set (or sets) that should be merged into the original one.
* @returns {BemCell[]} — Resulting set of cells.
*/
merge(set, otherSet, ...)
save()
Formats and saves a file with BEM cells from a file in any format.
/**
* @param {string} filename — File path to save the declaration.
* @param {BemCell[]} cells — Set of BEM cells to save.
* @param {Object} [opts] — Additional options.
* @param {string} [opts.format='v2'] — The desired format (v1, v2, enb).
* @param {string} [opts.exportType='cjs'] — The desired type for export.
* @returns {Promise.<undefined>} — A promise resolved when the file is stored.
*/
You can pass additional options that are used in the methods:
stringify() method from this package.
writeFile() method from the Node.js File System.
Read more about additional options for the writeFile()
method in the Node.js File System documentation.
Example:
const decl = [
new BemCell({ entity: new BemEntityName({ block: 'a' }) })
];
bemDecl.save('set.bemdecl.js', decl, { format: 'enb' })
.then(() => {
console.log('saved');
});
stringify()
Stringifies a set of BEM cells to a specific format.
/**
* @param {BemCell|BemCell[]} decl — Source declaration.
* @param {Object} opts — Additional options.
* @param {string} opts.format — Format of the output declaration (v1, v2, enb).
* @param {string} [opts.exportType=json5] — Defines how to wrap the result (commonjs, json5, json, es6|es2015).
* @param {string|Number} [opts.space] — Number of space characters or string to use as white space (exactly as in JSON.stringify).
* @returns {string} — String representation of the declaration.
*/
stringify(decl, options)
format()
Formats a normalized declaration to the target format.
/**
* @param {Array|Object} decl — Normalized declaration.
* @param {string} opts.format — Target format (v1, v2, enb).
* @return {Array} — Array with converted declaration.
*/
format(decl, opts)
assign()
Populates empty BEM cell fields with the fields from the scope, except the layer
field.
/**
* @typedef BemEntityNameFields
* @property {string} [block] — Block name.
* @property {string} [elem] — Element name.
* @property {string|Object} [mod] — Modifier name or object with name and value.
* @property {string} [mod.name] — Modifier name.
* @property {string} [mod.val=true] — Modifier value.
*/
/**
* @param {Object} cell - BEM cell fields, except the `layer` field.
* @param {BemEntityNameFields} [cell.entity] — Object with fields that specify the BEM entity name.
* This object has the same structure as `BemEntityName`,
* but all properties inside are optional.
* @param {string} [cell.tech] — BEM cell technology.
* @param {BemCell} scope — Context (usually the processing entity).
* @returns {BemCell} — Filled BEM cell with `entity` and `tech` fields.
*/
assign(cell, scope)
See another example of assign()
usage in the Select all checkboxes section.
Usage examples
Select all checkboxes
Let's say you have a list of checkboxes and you want to implement the "Select all" button, which will mark all checkboxes as checked
.
Each checkbox is an element of the checkbox
block, and checked
is the value of the state
modifier.
const bemDecl = require('@bem/sdk.decl');
const bemCell = require('@bem/sdk.cell');
// Sets the 'state' modifier for the entity.
function select(entity) {
const selectedState = {
entity: { mod: { name: 'state', val: 'checked'}}
};
return bemDecl.assign(selectedState, entity);
};
// Sets the 'state' modifier for the array of entities.
function selectAll(entities) {
return entities.map(e => select(e));
};
// Let's define BEM cells that represent checkbox entities.
const checkboxes = [
bemCell.create({ block: 'checkbox', elem: '1', mod: { name: 'state', val: 'unchecked'}}),
bemCell.create({ block: 'checkbox', elem: '2', mod: { name: 'state', val: 'checked'}}),
bemCell.create({ block: 'checkbox', elem: '3', mod: { name: 'state'}}),
bemCell.create({ block: 'checkbox', elem: '4'}),
];
// Select all checkboxes.
selectAll(checkboxes).map(e => e.valueOf());
// => [
// { entity: { block: 'checkbox', elem: '1', mod: { name: 'state', val: 'checked'}}}
// { entity: { block: 'checkbox', elem: '2', mod: { name: 'state', val: 'checked'}}}
// { entity: { block: 'checkbox', elem: '3', mod: { name: 'state', val: 'checked'}}}
// { entity: { block: 'checkbox', elem: '4', mod: { name: 'state', val: 'checked'}}}
// ]
License
© 2019 Yandex. Code released under Mozilla Public License 2.0.