/bem-react-core

Core library for develop with React and BEM methodology

Primary LanguageJavaScriptOtherNOASSERTION

BEM React Core

What is this?

It is a library for declaration React components as BEM entities. It works on top of usual React-components and provides API for declaration of blocks, elements and their modifiers. Blocks and elements created with this library are fully compatible with any React components: blocks and elements can use any other React components inside and can be used inside other React components.

Why?

If you already use i-bem.js and you want to get benefits from React approach and not to lose usual BEM terms and declarative style.

If you already use React and you want to get benefits from BEM methodology.

CSS classes generation

Your code will look better without unnecessary syntax noise.

Before

import React from 'react';

export default class MyBlock extends React.Component {
    render() {
        const { myMod1, myMod2, children } = this.props;
        return (
            <div className={`MyBlock MyBlock_myMod1_${myMod1} MyBlock_myMod2_${myMod2}`}>
                {children}
            </div>
        );
    }
};

Afrer

import { decl } from 'bem-react-core';

export default decl({
    block : 'MyBlock',
    mods({ myMod1, myMod2 }) {
        return { myMod1, myMod2 };
    }
});

NB You can use other libraries for CSS classes generation:

Declarative modifiers definition

Modifier is the one of key-concept of BEM methodology. Modifiers are supposed to help you make variations of the same component. bem-react-core enables you to declare additional behaviour for modifiers easily (see more in documentation).

Before

import React from 'react';

export default class MyBlock extends React.Component {
    render() {
        const { myMod1, myMod2, children } = this.props;
        let className = 'MyBlock',
            content = [children];

        if(myMod1 === 'myVal1') {
            className += `MyBlock_myMod1_${myMod1}`;
            content.unshift('Modification for myMod1 with value myVal1.');
        }

        if(myMod2 === 'myVal2') {
            className += `MyBlock_myMod1_${myMod2}`;
            content.unshift('Modification for myMod2 with value myVal2.');
        }

        return (
            <div className={className}>
                {content}
            </div>
        );
    }
};

Usually declaration of additional behaviour requires extra conditions in main code. As a different way you could use inheritance, but it's awkward to compose many modifiers of one component at the same time.

Before

// MyBlock.js

import { decl } from 'bem-react-core';

export default decl({
    block : 'MyBlock',
    mods({ myMod1, myMod2 }) {
        return { myMod1, myMod2 };
    }
});

// MyBlock_myMod1_myVal1.js

import { declMod } from 'bem-react-core';

export default declMod(({ myMod1 }) => myMod1 === 'myVal1', {
    block : 'MyBlock',
    content() {
        return [
            'Modification for myMod1 with value myVal1.',
            this.__base.apply(this, arguments)
        ];
    }
});

// MyBlock_myMod2_myVal2.js

import { declMod } from 'bem-react-core';

export default declMod(({ myMod2 }) => myMod2 === 'myVal2', {
    block : 'MyBlock',
    content() {
        return [
            'Modification for myMod2 with value myVal2.',
            this.__base.apply(this, arguments)
        ];
    }
});

NB bem-react-core uses Inherit library for JS classes declaration. It helps to make super-call (this.__base.apply(this, arguments)) without method name (super.content.apply(this, arguments)).

Redefinition levels

Redefinition levels it's a part of BEM methodology which helps you to separate and reuse your code. For example you can separate your code by platforms. bem-react-core helps to declare React components on the different levels.

// common.blocks/MyBlock/MyBlock.js

import { decl } from 'bem-react-core';

export default decl({
    block : 'MyBlock',
    tag : 'a',
    attrs({ url }) {
        return { href : url };
    }
});

// decktop.blocks/MyBlock/MyBlock.js

import { decl } from 'bem-react-core';

export default decl({
    block : 'MyBlock',
    willInit() {
        this.state = {};
        this.onMouseEnter = this.onMouseEnter.bind(this);
        this.onMouseLeave = this.onMouseLeave.bind(this);
    },
    mods() {
        return { hovered : this.state.hovered };
    }
    attrs({ url }) {
        return {
            ...this.__base.apply(this, arguments),
            onMouseEnter : this.onMouseEnter,
            onMouseLeave : this.onMouseLeave
        };
    },
    onMouseEnter() {
        this.setState({ hovered : true });
    },
    onMouseLeave() {
        this.setState({ hovered : false });
    }
});

Due to this you can configure build process for bundles split by platforms. Files from desktop.blocks are going to common.blocks + desktop.blocks for desktop browsers and not to common.blocks + touch.blocks for mobile.

How to use?

Installation

npm i -S bem-react-core

yarn add bem-react-core

Build

webpack

Using loader for webpack.

npm i -D webpack-bem-loader babel-core

webpack.config.js

// ...
module : {
    loaders : [
        {
            test : /\.js$/,
            exclude : /node_modules/,
            loaders : ['webpack-bem', 'babel']
        },
        // ...
    ],
    // ...
},
bemLoader : {
    techs : ['js', 'css'],
    levels : [
        `${__dirname}/common.blocks`,
        `${__dirname}/desktop.blocks`,
        // ...
    ]
}
// ...

Babel

Using plugin for Babel.

npm i -D babel-plugin-bem-import

.babelrc

{
  "plugins" : [
    ["bem-import", {
      "levels" : [
        "./common.blocks",
        "./desktop.blocks"
      ],
      "techs" : ["js", "css"]
    }]
  ]
}

License

Code and documentation copyright 2017 YANDEX LLC. Code released under the Mozilla Public License 2.0.