@bem/sdk.decl

Manage declaration of BEM entities

Stats

stars šŸŒŸissues āš ļøupdated šŸ› created šŸ£size šŸ‹ļøā€ā™€ļø
8055Jul 24, 2020May 12, 2014Minified + gzip package size for @bem/sdk.decl in KB

Readme

decl

A tool for working with declarations in BEM.

NPM Status

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:

  1. Load declarations from files
  2. Subtract declarations
  3. Intersect declarations
  4. Merge declarations
  5. Save declarations to a file

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();

RunKit live example.

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 as exports.decl = [ /* ... */ ]. You can also specify the declaration in the deps 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)

RunKit live example.

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)

RunKit live example.

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, ...)

RunKit live example.

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, ...)

RunKit live example.

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, ...)

RunKit live example.

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:

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)

RunKit live example.

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)

RunKit live example.

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'}}}
//  ]

RunKit live example.

License

Ā© 2019 Yandex. Code released under Mozilla Public License 2.0.

If you find any bugs or have a feature request, please open an issue on github!

The npm package download data comes from npm's download counts api and package details come from npms.io.