📜 ⬆️ ⬇️

19 principles of development on BEM, or what every library developer should know

BEM is gaining popularity and becoming more relevant - for example, Google recently released a new block library called Material Design Lite , implemented using the BEM methodology . The BEM team also didn’t sit idle - we released a new version of the bem-components library, on the basis of which the websites and projects of not only Yandex, but also other developers were built.

These events gave us the idea to once again recall and tell you how the principles of the development of libraries in the BEM methodology were formed. We hope that many will be interesting and useful. So let's go.

image
')
For a long time we created libraries of blocks, using mainly intuition and, as it seemed to us then, a sense of beauty. On this thorny path many cones were filled and many lessons were learned. As a result, we have a new document , containing principles for the development of libraries, which we now use as a checklist for the development of each new unit. In our opinion, these simple and obvious principles enable us to get thoughtful, convenient, scalable and easy-to-support code.

If you want to learn from examples how we came to our principles of development, welcome under cat.

A bit of history, or how we walked around the rake


When we started working on libraries, we, like most developers, wanted to do it quickly, beautifully and for everyone. We had a lot of ideas and a lot of projects that wanted to use the library.

But in practice it does not always work out the way you want ...

Lose yourself by pleasing everyone


Those who worked in large and young companies know how they like to “invent bicycles” here: it was the same with us. Each group of developers created blocks for themselves. In fact, all the teams did the same thing, but in different ways. The first time was pretty fast. But the number of implementations of the same block grew too fast. When it became clear that all projects needed a common set of blocks (for example, everyone needed the same caps and cellars), we decided to move the common blocks to a separate library. It seemed to us a competent and balanced decision, which was supposed to restore order among a multitude of identical blocks.

image

We began to develop a universal library. We wanted to create common blocks for all projects so thoughtful that they fit the requirements of each individual project. We took into account all cases of using the block (to the rarest and hardly possible) and tried to make the blocks as user friendly as possible. So the code of each block was inflated to indecent sizes, and 90% of the cases that we provided were not used by anyone. Despite this, we still had to maintain all of this code. The correction of any error was delayed.

Speed ​​in priority


Since the code of our blocks was used inside Yandex, the developers of individual projects depended on our releases. Therefore, the speed with which we developed the blocks was important to us.
We did not write unit tests, as it seemed to us that there are not so many projects and we manage to catch all the errors. But the number of blocks grew, the number of projects - too. But the number of developers, though increased, but not at the same fast pace. We were overwhelmed with bug reports.

More rights to users, less - to developers


We wanted to make a very convenient library. To do this, we have allocated a wide public API, giving users plenty of room for maneuver. However, we did not take into account that we would tie our hands together. We could not make almost any changes without affecting the external API. The errors were corrected, we released new versions. But the developers increasingly refused to switch from the old versions of the library, because such a transition mercilessly broke everything in the projects and took a large amount of time.

Nuances of styling


So all the sites that used our library, had the same design, we did not make the style of the library core. To realize our theme, we dumped common styles and superimposed our own.

So it was possible to live exactly until the moment when one of the developers needed to change the usual type of controls (for example, to make the promotional page “brighter”). Adding new styles caused a number of problems: the styles conflicted with each other, and when they were canceled, the controls did not want to work correctly. I had to admit that the method of implementing the theme directly in the general code of the block did not pay off. We came to the conclusion that by thinking through the architecture, it is necessary to lay the possibility of adding new themes to the project.

Abuse of Default Values


We loved using default values ​​for modifiers. On the one hand, this is convenient, since you can omit the value and get the worker control. On the other hand, the default value still makes it impossible for the developer to influence the modifier. That is, there is a limited range of values. For example, we have a button and the dimensions for it are set through modifiers (s, m, l, xl). Without specifying the modifier, we received the default value - m.

But the moment always comes when it is necessary to use a custom value. When we needed to set a size that did not coincide with any of the proposed modifier values, an extra code about dimensions would still fit into the assembly. Library developers had to spend a lot of time refactoring, since they had to maintain all default settings.

We used a general reset of styles, so the basic implementation of the block without the theme did not work. Thus, we ourselves were driving ourselves into a rigid framework, which was very difficult to change.

Of course, there were other mistakes about which you can talk. And we do not regret that we encountered all the difficulties. This helped us shape our approach to library development, determine priorities, and formulate simple, understandable principles for developing BEM libraries.

How the principles were formed


We assessed the scale of past disasters and made conclusions, having gathered all our experience in one universal library - bem-components , the new release of which took place quite recently. Now we will tell about the main transformations of our approach and about the positive experience of creating libraries of blocks.

A universal block and a block that does everything for everyone is not the same


First of all, we decided that we would no longer try to please everyone. Now we begin the development of a block or additional functionality to it only if we see this as a real need. The need is determined from the number of projects in which the block is used, from the amount of necessary revisions in the code for implementation, from the need to change the public API and the time required to switch to the new version.

We try to create universal blocks that can serve as a base, foundation, and be used in any project. All the unique requirements of users can be realized by them independently by means of adding and redefining the base unit. Because of this, we have the opportunity to take responsibility for the product. We try to make the block as simple as possible, as a result, users receive a clear code that is easy to understand and easily redefined for the needs of a specific project.

In addition to reducing the functionality of the blocks, we maintain uniformity at all levels of the library. For example, if in one block the dimensions are specified using a modifier, then the dimensions of the other block must also be expressed through the modifier. There is no need to create a new event or subscribe to it, if it is possible to work with an event to change the modifier.

We refused to use the default values ​​in those places where it was possible. Now the user of the library has more opportunities to influence the code and redefine it.

It should also be noted that we have significantly narrowed the public API, thereby giving ourselves more freedom to change blocks, and users - more confidence that the declared API is stable and will not change often.

Full and unconditional open source


We made the development of blocks open. They carried all the code on Github and began releasing versions of the library according to the rules of semantic versioning . The release of each major version is now accompanied by a detailed description of the change history and migration guide to the new version.

We decided to completely cover the code with tests. This complicates and slows down the creation of the block, but significantly simplifies its support. The release of the new version does not occur until all the code is covered by tests.

In addition to all the above, we believe that if the process can be automated, then this must be done. Robots must perform all repetitive actions, freeing developers time, which, by the way, can be spent on writing tests.

More API clarity


The transition to new versions has become much easier also because we have introduced more clarity in the separation of the private and public API. We have formal signs that clearly show that this is, for example, a private API (the name of the method begins with an underscore), which means that no one guarantees its immutability. But use the public API for health - its change is possible only with the release of a new major version.

The architecture of the block must be thought out in advance.


We do not start the development of the block until we finally decide on its architecture. Of course, we cannot predict that during development we will not need to implement additional functionality that was not taken into account at the very beginning. But a well-thought-out architecture usually makes the block easily expandable and allows you to add something missing without additional rework.

That is what we are doing now with the styling of the blocks - the theme is implemented using a modifier. To add a new topic, just set a new modifier value. Even if only one topic is provided in the library, we still put its implementation into a separate modifier. At the same time, we always create the Simple service theme, which allows us to check that when adding a new theme, the styles do not conflict. Also, all blocks are implemented in such a way that their basic functionality works even if the subject modifier is not specified.

You can talk for a long time how we built, built and finally built. But, as they say, it is better to see once - the bem-components library is the result of our work. And, of course, it remains to list the principles that help us make our product better, simpler and more understandable.

Principles of developing BEM libraries


As a result of the analysis of our many years of work, we formulated short principles for the development of libraries. At first glance, there are many obvious rules in this list, but the evidence does not make them less important.

image

1. Open source


Library development is carried out on GitHub, where all tasks and plans are available. Any developer can take part in the work on the library: create a task with wishes for the team or send a pull request.

2. Ease of use


The library should be as simple as possible. Discard anything that can be regarded or used ambiguously.

3. Minimalism


When designing the necessary functionality, strive to intersect rather than merge needs. In a choice situation, the preferred option is the one that solves the problem with a smaller amount of code, BEM entities, or easier to maintain.

4. Coverage test


Library code should be covered with tests. This ensures fewer errors and less time for future support. The code is not considered complete and stable until it is covered by tests. Pull requests with new features, but without adding or modifying tests are not accepted.

5. Uniformity


All field names and methods must be unified within one or several compatible libraries. If in a block, methods, modifiers, or fields are called in a certain way, then in a similar task you need to follow the same naming logic. The consistency and consistency of all entities must be brought to the absolute. It is important that the naming logic is clear to other developers who will use the library or modify its code.

6. Separation of private and public API


The private block API should not be used outside this block.

The public API should be made minimalist without providing anything extra. Be sure to indicate in the documentation possible ways to use it. The stability of the public API is guaranteed by observing the rules of semantic versioning .

7. Fine tuning by user


During the development process, think in advance about the possibility of flexible additions and redefinitions of templates, styles and scripts on the user's side. For example , the ability to specify an HTML tag in the input BEMJSON, overriding the template.

8. Support multiple themes


The library should support multiple topics. This will allow you to correctly create rules for each style, as well as avoid conflicts with new themes. Now the main theme of the BEM libraries is the islands, it has a new design by Yandex. The implementation of several themes within the same library is presented in bem-components, where, along with the islands, there is an additional simple theme.

9. Explicit is better than implicit (in JavaScript API)


The name of the method should reflect the essence as precisely as possible and immediately make it clear what it is implementing. This approach reduces the entry threshold and makes the API more understandable.

10. Explicit defaults


Defaults must be explicitly declared - this is an external API. Any changes to them interfere with backward compatibility. Implicit and undiscovered defaults create problems in library support.

11. Error handling


The input data should be checked before use, generating an exception in case of an error that can be processed in the calling code. Error handling should occur uniformly throughout the library: it is not necessary to add complex data validation to the block, if in a similar case it does not occur.

12. The subject area of ​​BEM


Everything that can be expressed through the BEM entity should be expressed through it.

13. Modifiers vs. specialized fields


A modifier is a previously known set of positions or states. A specialized field is a previously unknown set of variable values.

Everything that can be expressed as a finite set of values ​​must be implemented by a modifier. Where this is not possible, use specialized fields.

14. Block hierarchy


The interaction between the blocks should be built in a hierarchical order. A block cannot interact with external or adjacent blocks.

15. Block level optimization


Optimization solutions should be implemented during the development of each specific unit. Consider in advance possible ways of optimization in order to make it easier to implement them in already written code.

16. Automation of routine processes


All repetitive actions, such as inserting images, prefixing or copying styles, should be automated. The developer should not be engaged in routine, for this there are robots.

17. Mobile platforms without adaptive layout


The library must support mobile platforms. The use of adaptive layout is not recommended. The library should work in all browsers whose support is claimed: only the styling of the components, but not their functionality, is allowed to degrade.

18. Availability


Library components should be readable by screen access programs, but should not be extended for this public API. Setting all the necessary ARIA attributes is implemented at the level of templates and scripts.

19. Bleeding edge


The library is being developed with a future orientation. Always use the latest versions of browsers and tools to keep it up to date during the development process as long as possible.

These principles help us develop the BEM library: everyone can participate in the development. Having formulated for ourselves the basic principles of development, we also managed to develop internal rules for the contribution to our libraries.

An example of following the principles is the library bem-components



We have created a library, guided by these principles. bem-components is the main opensource-product of the BEM team.

The bem-components library contains 22 ready-made controls. However, it does not limit you from adding missing functionality.

image

We have provided various ways to connect the library to the project. Read about all connection methods in the library description on our website.

Starting from version 2.3.0 , the bem-components library can be used by analogy with Bootstrap - delivery in the form of Dist . Now you can simply copy the library code into the project and see for specific examples how the principles are implemented in each block.

Distributing the library in the form of Dist provides pre-assembled CSS and JavaScript code and templates. The library is connected by links to the page and does not require assembly.

The easiest way to start working with the library is to connect files from a CDN. To do this, add items
    HTML : 

<link rel="stylesheet" href="https://yastatic.net/bem-components/latest/desktop/bem-components.css"> <script src="https://yastatic.net/bem-components/latest/desktop/bem-components.js+bh.js"></script>


CDN: //yastatic.net/-///-.
: https://yastatic.net/bem-components/latest/desktop/bem-components.dev.js+bh.js .

bem-components Dist, .

, — — .

!

PS , , .
HTML :

<link rel="stylesheet" href="https://yastatic.net/bem-components/latest/desktop/bem-components.css"> <script src="https://yastatic.net/bem-components/latest/desktop/bem-components.js+bh.js"></script>


CDN: //yastatic.net/-///-.
: https://yastatic.net/bem-components/latest/desktop/bem-components.dev.js+bh.js .

bem-components Dist, .

, — — .

!

PS , , .

Source: https://habr.com/ru/post/267875/


All Articles