##I. BEM Introduction
BEM claims to be a Frontend Development Methodology, providing a set of methods for development phase including naming rules, CSS/JS modularization principles, build tools, etc.
P.S. Emphasizing development phase is to distinguish from things like Yeoman, BEM wants to talk about methodology, not tools
Provides a set of naming rules (BEM naming rules), mainly used for modular CSS, but BEM is also used in JS and file naming and other aspects
Mainly solves these problems in CSS:
- Style naming conflicts in team collaboration (such as class)
Solved with long class names, not using structured selectors (such as child selectors, descendant selectors, etc.), class names carry hierarchical relationships
- Achieve the goal of self-documenting code
Semantic class names, provide more information, such as element name, function, component name, etc.
- Avoid mutual influence between components
Don't rely on structured selectors, rely entirely on class names
- How to assemble various features on the same DOM node while avoiding duplicate code (copy-paste)
Mix, such as div.classA>div.classB combines Block A and Block B
These solutions are explained in [How to Write Good CSS](/articles/如何写好 css/), if only defining a set of long class name formats, it's really meaningless, so BEM also implements frameworks and tools for this set of naming, ensuring it can be used well during development
##II. Terminology Concepts
(Suggest skipping, directly look at red parts, official explanation equals no explanation)
###Block
Page component with independent logic and page function, is a reusable unit, features as follows:
-
Can be freely nested and combined
-
Can be placed anywhere on any page, without affecting function and appearance
-
Reusable, interface can have any number of instances of the same Block
###Element
Part of Block, depends on Block existence (cannot be used outside Block)
###Modifier
[Optional] Defines appearance and behavior of Block and Element, just like HTML attributes, can make the same kind of Block look different
###BEM entity
All three above are
###Mix
An instance composed of various BEM entities on a single DOM node, features as follows:
-
Can combine behavior and style of several BEM entities, avoid code duplication
-
Semantically create new interface components based on existing BEM entities
###BEM tree
Use BEM to describe web page structure
###Block implementation
Many different technologies determine aspects of BEM entity's behavior, appearance, testing, templates, documentation, dependency description, extra data (such as images), etc.
###Implementation technology
Technology used to implement a Block, can be one or multiple
###Block redefinition
Modify Block implementation by adding new features to Block at different levels
###Redefinition level
A series of BEM entities and their partial implementations
Can be viewed as logical levels during use, such as project level, library level, former can rewrite latter's Block function and appearance
P.S. Actually implemented through file directory and sequential introduction for level isolation and rewriting
Points needing attention:
-
B(Block): Represents module, smallest reusable unit, functionally independent, can be nested, combined
-
E(Element): Part of B
-
M(Modifier): Represents E's state (E has different functions and appearance under different states), also part of B
-
BEM tree: Use BEM terminology to describe page/project structure
-
Block implementation: Things needed to implement Block, including all related content, such as JS, CSS, image, etc.
-
Redefinition level: Can be viewed as logical level, can rewrite/extend lower level Block (module) at higher level
##III. BEM Naming Rules
block-name__element-name_mod-name, for example nav-menu__item_active
block-name itself may not correspond to style, only serves as Block's logical name
BEM only provides general method, doesn't limit must use this naming rule
##IV. JavaScript for BEM
###Naming rules also apply to JS
In JS, Modifier is used to express logic of Block or Element (in CSS, Modifier is used to define appearance), JS describes behavior of Block and Element through a series of states
Don't find components through class, but through Block name (in HTML, not only uniquely identified through class, can also be tag, attribute, etc.), for example:
document.querySelector('.button')
.addEventListener('click', function() {
document.querySelector('.popup').classList.toggle('popup_visible');
}, false);
// BEM Style (pseudo code)
block('button').click(function() {
block('popup').toggleMod('visible');
});
Practical application needs to introduce i-bem.js
Modifier can set specific state of Block, achieve Block state switching through adding and removing Modifier, operations on Modifier will trigger events, notify related Block. To ensure this mechanism runs correctly, should not allow随意 switching states or directly modifying class of some DOM node, these operations must be implemented through helpers provided by BEM (this restriction is similar to svn local repository)
Modifier corresponds to state, state associates function. When adding Modifier, state changes, automatically applies corresponding function; when removing Modifier, corresponding function will be automatically removed
Define association between Modifier and state and underlying level:
block('button').onSetMod({
focused: {
true: this.onFocus,
'': this.onBlur
}
});
Shields things under state (may be adding/removing Modifier causes state change, or triggered by user behavior), at programming level the minimum unit needing attention is state (Modifier). Can define appearance of various states by adding styles to Modifier, can also change or completely rewrite Block behavior through Redefinition level
###Separate code into various files, follow file directory structure rules
Rules:
-
Each Block's logic and optional Element and Modifier are placed in separate files
-
Each component's JS file directory structure follows BEM file directory structure rules
Example:
logo/
logo.css # Block's appearance
logo.tmpl # Templates for generating the block's HTML representation
logo.js # Dynamic behavior of the block in the browser
Facilitates management, reuse, transplantation, supports Redefinition level and combined use
###Separate logic into various Redefinition levels
Implement new Block at different Redefinition levels can inherit and extend existing Block, or completely rewrite the Block, for example:
// Completely rewrite
block('button').onSetMod({
focused: {
true: this.someCustomOnFocused
}
});
// Partially rewrite
block('button').onSetMod({
focused: {
true: function() {
this.__base.apply(this, arguments);
this.someCustomOnFocused();
}
}
});
###Principles
Block in JS has no strong correspondence with DOM nodes, for example some Blocks have no DOM representation, just purely logical blocks
Interaction between Blocks is achieved through event subscription, for example:
-
Subscribe to events of other Block instances
-
Subscribe to Modifier changes
-
Directly call public interfaces of other Blocks
-
Any other interaction patterns, such as Event Channel (message mechanism)
Element can only be accessed through API provided by its belonging Block, cannot directly access Element in some Block
##V. File Directory Structure
Code is decomposed into independent small parts, facilitates developing independent Blocks, assemble before release for optimization
###Features
- Decompose Block implementation into various separate files
Facilitates management (during development) and transplantation (implant into other projects)
- Optional Element and Modifier are stored in various separate files
Only introduce related Block implementation (load on demand)
- Files are grouped by semantics, not by file type
For example all Blocks are placed under block directory, to ensure only needed Blocks are introduced
- Project is divided into various Redefinition levels
To eliminate duplicate code, facilitate rewriting/extending existing Library Block, when Library Block updates, won't affect upper level rewriting/extending
###Directory Structure Example
blocks/
input/ # input block directory
_type/ # type modifier directory
input_type_search.css # Implementation of modifier type
# with value search in CSS technology
__box/ # box element directory
input__box.css
input.css
input.js
button/ # button block directory
button.css
button.js
button.png
popup/ # popup block directory
_target/
popup_target.css # Common code of modifier target
popup_target_anchor.css # Modifier target with value anchor
popup_target_position.css # Modifier target with value position
_visible/
popup_visible.css # Boolean modifier visible
popup.css
popup.js
File names also follow BEM naming rules, shared parts between Modifiers can be extracted, as separate file (popup_target.css)
###Redefinition level Example
library.blocks/
button/
button.css # CSS implementation in the linked library (height 20px)
project.blocks/
button/
button.css # Redefinition of CSS implementation (height 24px)
Files are stored at different levels, separated to avoid mutual influence
Or layer by platform:
common.blocks/
button/
button.css # Generic CSS implementation of the button
desktop.blocks/
button/
button.css # Desktop platform-specific button features
mobile.blocks/
button/
button.css # Mobile platform-specific button features
build process will automatically introduce by level, for example:
@import(common.blocks/button/button.css); /* Generic CSS rules */
@import(desktop.blocks/button/button.css); /* Desktop platform-specific */
##VI. build
BEM projects all have multi-level file structure, build completes work:
-
Integrate separate files related to Block
-
Introduce Block, Element and Modifier on demand
-
Ensure introduction in correct order (guarantee Redefinition level)
Of course, need to pre-define some dependency information, such as:
-
List related Block, Element and Modifier when creating page
-
Indicate dependency relationships of Block, Element and Modifier in page
Dependency declaration can be completed through tools, such as can automatically generate BEM Tree according to class applied in HTML, or generate dependency information according to project structure (not on-demand generation, all things will be packaged in, suitable for situations where everything under project directory is determined to be necessary, such as class libraries), for more information about dependency declaration methods, please check Declarations in BEM
Dependency introduction scheme is related to implementation, for example, CSS uses @import, JS uses AMD and other schemes
###build result
// Before build
blocks/ # Directory containing the blocks
bundles/ # Directory containing all build results
hello/ # Directory for the hello page
hello.decl.js # List of BEM entities required for the hello page
An example of a post-build file structure of a BEM project:
// After build
blocks/ # Directory containing the blocks
bundles/ # Directory containing all build results
hello/ # Directory for the hello page
hello.decl.js # List of BEM entities required for the hello page
hello.css # Built CSS file for the hello page
hello.js # Built JavaScript file for thehello page
In above example hello.decl.js defines dependent components and their dependency relationships
###build tool
Official provided build tool is ENB, said to be very powerful:
The BEM platform uses ENB, which is a tool suitable for building BEM projects of any level of complexity.
Want to try please check beginner tutorial: Starting your own BEM project
###Reference Materials
No comments yet. Be the first to share your thoughts.