Forum

Methodology

Toolbox

Platform

Community

bem-walk

Tool for traversing a BEM project's file system.

It returns the following information about found files:

Quick start

Note To use bem-walk, you must install Node.js 4.0+.

1. Install bem-walk

$ npm install --save @bem/walk

2. Enable bem-walk

Create a JavaScript file with any name and add the following string:

const walk = require('@bem/walk');

Note You will use this JavaScript file for all the following steps.

3. Define file system levels

Define the project's file system levels in the config object.

const config = {
    // project levels
    levels: {
        'lib/bem-core/common.blocks': {
            // `naming` — file naming scheme
            naming: 'two-dashes'
        },
        'common.blocks': {
            // `scheme` — file system scheme
            scheme: 'nested'
        }
    }
};

Specify either the file naming scheme or the file system type for each level. This lets you get information about BEM entities using their names or using the names of files and directories.

The table shows the possible values that can be set for each of the schemes.

Key Scheme Default value Possible values
naming File naming. origin origin, two-dashes
scheme File system. nested nested, flat

More information:

Note Instead of defining the project's levels manually, use the bem-config tool.

const config = require('bem-config')();
const levelMap = config.levelMapSync();
const stream = walk(levels, levelMap);

4. Define the paths to traverse

Specify the paths to walk in the levels object.

Path options:

  • Relative to the root directory.

    const levels = [
        'libs/bem-core/common.blocks',
        'common.blocks'
    ];
    
  • Absolute.

    const levels = [
        '/path/to/project/lib/bem-core/common.blocks',
        '/path/to/project/common.blocks'
    ];
    

5. Get information about found files

Pass the walk() method the levels and config objects.

Streaming is used for getting data about found files. When a portion of data is received, the data event is generated and information about the found file is added to the files array. If an error occurs, bem-walk stops processing the request and returns a response containing the error ID and description. The end event occurs when all the data has been received from the stream.

const files = [];

const stream = walk(levels, config);
// adds information about a found file to the end of the "files" array
stream.on('data', file => files.push(file));

stream.on('error', console.error);

stream.on('end', () => console.log(files));

Complete code sample

When all these steps have been completed, the full code of the JavaScript file should look like this:

const walk = require('@bem/walk');
const config = require('bem-config')();
const levels = [
    'libs/bem-components/common.blocks',
    'common.blocks'
];
const files = [];

const stream = walk(levels, {
        levels: config.levelMapSync()
    })
    .on('data', file => files.push(file))
    .on('error', console.error)
    .on('end', () => console.log(files));

Note This sample uses the bem-config package.

API

walk method

walk(levels, config);

Description

Traverses the directories described in the levels parameter and returns stream.Readable.

Input parameters

Requirements for the search are defined in the levels and config parameters.

Parameter Type Description
levels string[] Paths to traverse
config object Project levels

Output data

Readable stream (stream.Readable) that has the following events:

Event Description
'data' Returns a JavaScript object with information about a found file.

The example below shows a JSON interface with elements that are in the response for the walk method. Objects and keys have sample values.

Example

{
"cell": {
"entity": { "block": "page" },
"layer": "libs/bem-core/desktop.blocks",
"tech": "bemhtml"
},
"path": "libs/bem-core/desktop.blocks/page/page.bemhtml.js"
}

cell — BEM cell instance.
entity — BEM entity name instance.
layer — Directory path.
tech — Implementation technology.
path — Relative path to the file.
'error' Generated if an error occurred while traversing the levels. Returns an object with the error description.
'end' Generated when bem-walk finishes traversing the levels defined in the levels object.

Usage examples

Typical tasks that use the resulting JavaScript objects:

Grouping

Grouping found files by block name.

const walk = require('@bem/walk');
const config = require('bem-config')();
const util = require('util');
const levels = [
    'libs/bem-components/common.blocks',
    'common.blocks'
];
const groups = {};

const stream = walk(levels, {
        levels: config.levelMapSync()
    })
    .on('data', file => {
        // Getting the block name for a found file.
        const block = file.entity.block;

        // Adding information about the found file.
        (groups[block] = []).push(file);
    })
    .on('error', console.error)
    .on('end', () => console.log(util.inspect(groups, {
        depth: null
    })));

/*
{ button:
   [ BemFile {
      cell: BemCell {
         entity: BemEntityName { block: 'button', mod: { name: 'togglable', val: 'radio' } },
         tech: 'spec.js',
         layer: 'libs/bem-components/common.blocks'
      },
      path: 'libs/bem-components/common.blocks/button/_togglable/
        button_togglable_radio.spec.js' } ] },
 ...
}
*/

Filtering

Finding files for the popup block.

const walk = require('@bem/walk');
const config = require('bem-config')();
const levels = [
    'libs/bem-components/common.blocks',
    'common.blocks'
];
const files = [];

const stream = walk(levels, {
        levels: config.levelMapSync()
    })
    .on('data', file => {
        // Getting the block name for a found file.
        const block = file.entity.block;

        // Adding information about the found file.
        if (block == 'popup') {
            files.push(file);
        }
    })
    .on('error', console.error)
    .on('end', () => console.log(files));

/*
[BemFile {
   cell: BemCell {
     entity: BemEntityName { block: 'popup', mod: { name: 'target', val: true } },
     tech: 'js',
     layer: 'libs/bem-components/common.blocks'
   },
   path: 'libs/bem-components/common.blocks/popup/_target/popup_target.js'
},
...
]
*/

Transformation

Finding BEM files, reading the contents, and creating the new source property.

const fs = require('fs');
const walk = require('@bem/walk');
const config = require('bem-config')();
const stringify = require('JSONStream').stringify;
const through2 = require('through2');
const levels = [
    'libs/bem-components/common.blocks',
    'common.blocks'
];

const stream = walk(levels, {
        levels: config.levelMapSync()
    })
    .pipe(through2.obj(function(file, enc, callback) {
        try {
            // Certain technologies (for example, `i18n`) might be directories instead of files.
            if (fs.statSync(file.path).isFile()) {
                // Writing the file content to the `source` field.
                file.source = fs.readFileSync(file.path, 'utf-8');
                this.push(file);
            }

        } catch (err) {
            callback(err);
        }
    }))
    .pipe(stringify())
    .pipe(process.stdout);

/*
[{"cell":{
    "entity":{"block":"search","elem":"header"},
    "tech":"css",
    "layer":"common.blocks"
  },
  "path":"common.blocks/search/__header/search__header.css",
  "source":".search__header {\n\tdisplay: block;\n\tfont-size: 20px;\n\tcolor:
  rgba(0,0,0,0.84);\n\tmargin: 0;\n\tpadding: 0 0 16px;\n\n}\n\n"},
...
]
*/
If you notice a mistake or want something to supplement the article, you can always write to us at GitHub, or correct an article using prose.io.