Hi, this is Katya from Yandex.Money again. I continue my story about how I stopped making up and started to live. In the
first part, I told how I was brought here and what our front-tenders are doing. Today it’s about the front-end stack, where the React is from and where BEM went.
Spoiler: BEM has not gone anywhere yet ¯ \ _ (ツ) _ / ¯. Let's go!

Attention: high concentration of frontend. A lot of text, pictures and code, as promised.
Part 2. About technology
Faraway 2016. Trying to write on React, it is pretty bearable. I still do not suspect that in a year I will be transferring entire services to React. 2017 starts in Yandex.Money, I have a BEM brain begins, and I still do not suspect.
Backend on Node.js, my first time
To get acquainted with the project, a new developer receives a test task. I was lucky: I had this task from backlog. And on the very first day I encountered Node.js.
The front-end Yandex.Money is responsible not only for the client side, but also for the server stratum in the form of a Node.js application. The application's task is to orchestrate data from a Java backend for preparation in view-oriented form, as well as server-side rendering and routing. I would say this a couple of years ago, I would not understand anything, and everything is quite simple: when a request comes from the browser to the server, Node.js generates HTTP requests for the backend, receives the necessary data and templates the web pages. We use
Express as a server framework, and decided to use
Koa2 for developing internal applications without a legacy
link . The developers loved the framework design, and we decided not to downgrade to Express, so Koa2 remained on the stack. But we are not rolling out Koa2 code to external users: the framework does not have sufficient support, but there are open vulnerabilities.
We
already wrote about the place of Node.js in our front end, but since then something has changed. Node.js 8 has become LTS and is already running on our production servers. We also want to abandon the Nginx servers, which we raise on each host to distribute statics - they will be replaced by separate servers with Nginx, and sometime with CDNs.
To fumble the code between projects, but not to post it in open access, we use a whole set of tools: we store the modules in Bitbucket and assemble them in Jenkins. We also use the local package registry and due to this we do not go to the external network - this speeds up the assembly and increases the security of the entire system. Such an approach was suggested to us by javists, they are great. Love your backenders;)
We also conducted an experiment - we implemented a process manager in one of the applications, which simplified the administration of services on Node.js. He helped with clustering, and also relieved us of one old bash script that ran applications.
And the whole stack is small
We have javascript everywhere in the frontend. And on the server, and on the client, and under the hood of the internal tools. We know other languages, but javascript copes with everything perfectly.
But BEM in our tasks does not cope with everything.
What is BEMBEM is an approach to web development that was invented by Yandex in the days of static HTML pages and CSS life-long cascades. There was no component approach yet, and it was necessary to maintain the uniformity of many services. Yandex was not taken aback and developed its own component approach, which today allows you to create isolated components and write flexible declarative code.
BEM is not only a methodology, but also a large set of technologies and libraries. Some of them are sharpened for the specifics of BEM, and some may well be used in isolation from the BEM architecture. If you need a powerful template engine or a decent example of component abstraction over the DOM, you know where to find them;)
Therefore, we started to transfer services to React. Some of them already live in two applications built on different stacks:
- typical for Yandex BEM platform;
- a young and trendy React ecosystem.
Yandex Technologies
It's time to tell why I fell in love with BEM.
Override levels
Levels, levels, levels ... BEM! Profit!
Override levels are one of the main features of the BEM methodology. To understand how they work, look at the picture:

The picture is formed by overlaying layers. Each layer changes the final image, but does not change the other layers. The layer can be easily pulled out or added on top, and the image will change.
Override levels do the same with code:

The behavior of the component is formed during the assembly of the code. To add additional behavior it is enough to connect the required level to the assembly. The module code from different levels overlaps each other. At the same time, the source code does not change, and we get different behavior, combining different levels.
What are the levelsIn the picture above are several levels of override:
- Base level - library - delivers the source code module;
- The next level - the project - modifies this module for the needs of the project;
- A higher level - a platform - makes the same module specific for different devices;
- The cherry on the cake - the level of experiments - changes the module for A / B testing.
The project level does not depend on the library level, so the library is easy to update. The platform level allows you to use different builds for different devices. And the experiment level is connected for testing on users and also turns off easily when results are obtained.
The developer himself decides what levels he needs: you can create a level with a theme or a level with the same code on another framework.
Levels allow you to write complex modules based on simple ones, it is easy to combine the behavior and fumble the same code between services. And collects this code
ENB - Webpack in the world of BEM.
When I got acquainted with BEM, I was particularly pleased with the UI libraries, which contain ready-made components. We are expanding these components in the framework of new libraries and felting them between projects. This makes life a lot easier: I rarely make up, do not write JS of the same type, and quickly assemble interfaces from ready-made blocks.

Now we will take a closer look at the BEM platform tools in order to understand what BEM is not doing well enough and why it was not suitable for solving our problems.
BEM-XJST
I'll start with my beloved,
the bem-xjst template engine. Before Yandex, I used Jade, and Bem-xjst perfectly illustrated the minuses of Jade, which I did not see then. The bem-xjst templates are declarative [1], they are not if hell [2], and they perfectly meet the requirements of the component approach [3]. All this is clearly seen in the example:

In the
sandbox you can see the result of template making and play with it.
How it works. Inside - the secret of ideal architecture;)- write BEMJSON. BEMJSON is a JSON describing a BEM tree. A BEM tree is the representation of a DOM tree as independent components;
- bem-xjst accepts BEMJSON as input and applies templates. This process can be compared to rendering in the browser. The browser bypasses the DOM tree and gradually applies CSS rules to its DOM nodes: size, text color, padding. Also, bem-xjst bypasses BEMJSON, searches for templates matching its nodes and gradually applies them: tag, attributes, content. “Apply a template” means to generate an HTML string from it. HTML generation from BEMJSON is done by one of the templating engines - BEMHTML.
Writing templates is simple: select the entity and write the functions that the template engine will call to render parts of the HTML string. The most difficult thing is to isolate the essence. The right entities - the key to good architecture!
The longer your beard, the higher the chance that you have already noticed the reference in the template name:
XSLT (eXtensible Stylesheet Language Transformations) => XJST (eXtensible JavaScript Transformations). It uses principles from XSLT and therefore is so declarative. If you don't know what XSLT is, consider yourself lucky :)
Bem-xjst is isomorphic. We render the HTML pages on the server and dynamically change it on the client. For templating in runtime, bem-xjst provides the API that we use when writing client-side javascript code.
I-bem
With the help of bem-xjst we describe the presentation, and the logic with the help of
i-bem . I-bem is an abstraction over DOM that provides a high-level API for working with components. Simply put, lets write this:

instead of this:

To write code, you do not need to know about the internal implementation of the component. We operate on entities that are described in the template: no matter if it is a jQuery selector or a DOM element. We can create custom events that are suitable for a particular object model, and working with native events and interfaces will be hidden in the internal implementation. The low-level logic is also described there, which means that we do not load the code with the main logic with unnecessary checks. As a result, the code is easy to read and does not depend on a specific technology.
I-bem allows you to describe the logic of the component as a set of states [1]. This is declarative javascript. I-bem implements its own Event Emitter: when state changes, components automatically generate events for which another component can subscribe [2].
This is how most of the client-side javascript code on BEM looks like:

How it works- upon the domReady event, i-bem finds components (blocks) in the DOM tree and initializes them - creates a js-object in memory of the browser corresponding to the block;
- upon the occurrence of the necessary events, we set the block markers, reflecting the state. The role of markers perform CSS-classes. For example, when you click on input, we add the class “input_focused” to it, which serves as a marker;
- when setting such markers, i-bem launches callbacks specified in the javascript block implementation.
It is simple to write logic: you need to describe the possible states of the block (the same markers) and specify the handlers for changing these states (the same callbacks).
With i-bem, we can easily override the behavior of the components, create a harmonious API of their interaction and dynamically change them in runtime. So what's missing?
We love BEM for its declarative, easy scalability and high-level abstractions, but we are not ready to put up with its limitations anymore. Below we consider the problem of client rendering, data storage and other limitations of the BEM platform. Over time, these problems may be resolved by BEM distributors, but we are not ready to wait.
Modern web c SPA and adaptability for mobile devices requires adaptability from us. Therefore, we decided to switch to our own stack. And chose React.
New Maple Stack at React
React brought virtual DOM, hot reload, CSS in JS and a large community into our lives, of which we have become a part.
Migration of our services to React is in full swing, some applications are already partially or completely rewritten to React. We get acquainted with new approaches and tools and improve the architecture of our applications.
Libraries
The partitioning of the interface entities into independent BEM blocks is perfectly friendly with the component approach of React. The Yandex developers wrote
bem-react-core and transferred the core UI library of components to React. We wrote an adapter library above it that takes into account the specifics of these components and delivers them as
HOC :

Such libraries are not connected in the application, but in the main library component:

The application depends only on the main library and gets all the components from it:

This reduces the number of application dependencies, and the libraries do not fall into the bundle twice under different versions.
Technology
React is not tied to specific technologies and we ourselves choose the tools and libraries. I have axios, redux, redux form, redux thunk, styled-components, typeScript, flow, jest and other cool stuff in my armament. In order to prevent the zoo, we coordinate the use of new technologies with other developers - we send a pull-request to a special repository with an analysis of what the technology is useful for and why we chose it.
The front-fender enters the bar, and the barman tells him
For React applications, we create a platform that integrates libraries and processes for their creation and maintenance. The heart of this platform is the console utility Frontend Bar. Bar knows how to cook a lot of tasty pieces.
On the menu:
- Config with ice: bar will mix and shake your yml variables and prepare a config template for ansible.
- Juice with the scent of configurators: bar will create a new application based on the modular preform - Juice.
- Set of basic library settings. Expected soon.
Creating a juicy app is now easy - “frontend-bar make juice”. Make juice, not war! When Bar deploys a new application, it performs a set of configurations from Juice: package.json, .babelrc, middleware code and routs code, root component code is generated. Frontend Bar will facilitate the allocation of new microservices and help you follow uniform rules for writing code.
When switching to the new stack, we began to improve the server architecture of the applications — we wrote a new logger for the client and a library with a set of abstractions for implementing
MVC . Today we decide what the new server architecture will be.

Spoiler: choose onion.
What happened and was it better? Let's understand
Dynamic interfaces
It was
Above, I wrote that bem-xjst provides an API for templating in runtime. I-bem, in turn, is able to work with the DOM-tree. Make friends with them and we can dynamically generate and modify HTML. Let's try to change the button by event:


This example shows the weak side of BEM: i-bem does not want to be friends with bem-xjst and does not want to know anything about the templates. It adds a class to the block, but does not apply the pattern [1]. We re-render the component manually [2]:
- describe a new piece of BEM tree [3];
- then use the new template [4];
- and initialize another component on the current DOM node [5].
In addition, i-bem does not create diff BEM-trees, so the re-render of the entire component, and not the changed parts. Consider a simple example: re-render the contents of a modal window on demand. It consists of three elements:

For simplicity, we assume that only one element can change.

I want to do [1] and relax. But i-bem will not understand what has changed, completely re-render the entire component and also relax. In this example, there will be no serious consequences, but what if it is so careless to re-render whole forms? This degrades performance and causes unpleasant side effects: an input is flashed somewhere, an ownerless tooltip hangs somewhere. Because of this, we are sad and manually manage parts of the component to make a point re-peer [2]. This complicates the development, and we are again sad.
It became
React came and ruined everything. It monitors the state of the components, we no longer manage the manual rendering and do not think about interaction with the DOM. React contains the
virtual DOM implementation. When calling React.createElement, a js-object of the DOM node is created with its properties and successors - the virtual DOM of this component, which is stored inside React. When the component changes, the React computes the new virtual DOM, and then the diff saved and the new, and updates only the part of the DOM that has changed. Everything flies, and we can only optimize complex logic using shouldComponentUpdate. This is a success!
Data storage
It was
In BEM, we prepare all the data on the server and transfer it to the page components:

Components are isolated and will not share data with each other, which means that the same data will have to be pushed into different components [1]. We will not be able to get them on the client; therefore, each component receives in advance a set of data that is needed for all possible scenarios of its operation. This means that we load the component with data that it may not need [2].
Sometimes we are rescued by a global entity in which part of the common data is stored, but global storage of variables does not fit well into the concept of BEM. Therefore, we wrote
bem-redux , which adapts Redux for BEM.
Redux is the state manager that manages the flow of data. It perfectly manages our data within simple interfaces, but when developing a complex component we run into the rendering problem I described above. Redux is not friendly with i-bem, we fix bugs and feel sad.
It became
Redux + React = <3Redux stores data for the entire application in one place [1]:

The component itself decides when and what data it needs [2]:

We only need to describe the scenarios of the work of the component [3] and indicate where to get the data for its execution [4]:

And React does the rest [5]:

This approach allows you to follow the principle of common responsibility and encapsulate the logic of the component in the component itself, rather than spread it out in the page code. This is a success!
You have to pay for everything
For success, we paid off with a fair amount of legacy on React. It hurts to see how your code, written just a couple of months ago, smoothly turns into deprecated.
The fact is that React is a view-layer library, not a full-fledged framework. You can choose all the tools, but you
have to choose all the tools. And also organize the code yourself, formulate approaches to solving typical tasks, work out a set of agreements and write the missing plugins. We write our own validators for redux forms and have not yet learned how to work with complex animations. And we try and throw out, write and rewrite. And we do not always rewrite, why our backlog is growing.
React is young enough and not ready for enterprise development, unlike BEM. And while we were learning how to cook it, they stuffed all their kitchen and messed themselves up to their elbows. And we are still arguing about whether we need a flow or not, and still do not fully understand what to store in the stack, and what is in the local stete. We write as it is necessary and we go to conferences to find out how to. We beat the bumps, but we are confidently moving forward.
Unexpected Buns
The new stack made it possible to take a fresh look at a number of tasks and provided simple ways to solve them.
CSS in JS
It was
Consider a simple case from life: color and animate an icon for an event, like this:

Code of nothing at all:

True, according to the BEM rules, you will have to spread it in as many as three directories:

Overhead? Controversial issue. More importantly, in js we add these classes manually when the necessary events occur. The usual situation, but the more custom or more complex the interface, the more often you'll have to add and remove classes. And if you need to change not only the icon, but also the text? Not exactly the logic that you want to see in the js-code:

And what if the duration of the animation depends on something and is set dynamically? Then we will rewrite the CSS animation on jQuery and slightly a bit sad.
It became
Styled-components , I love you! SS in JS - one love! My inner layout designer rejoices:

Modularity is preserved, CSS-animation works and no manual work with classes. A nice bonus is the new stack.
Typing
It was
We used to write tons of jsDoc. Let's see if it is useful:

This example is taken from production code. What does the state contain? I have no idea. Yes, there is a readme, but alas, it is a bit outdated. Yes, we are ashamed, but with documentation and comments it happens so often, they are unreliable. We'll have to delve into the code. Or do not go deep and inadvertently break everything. We hurry, do not go deep, break and sad.
It became
Typing came to the rescue. "Tyk" on the type, and all the ins and outs of the method before his eyes. Too lazy to understand? Precommit checker will start
flow , and you still have to figure it out.
I disliked flow at first sight. The terms are burning, the manager pings, and you and there “cannot get property”, and then “property is missing”. But recently I was told that types can be designed O_o How to design types? Like this:

My world turned upside down. Flow has ceased to be a nightmare. Describing the API of modules by type before writing code turned out to be convenient and useful. Reliable code - a nice bonus!
So no more BEM?
Not. BEM is alive, and we continue to support applications on the BEM stack. Over time, they will move to React, but for now we are preparing the ground for this: we are translating component libraries, forming a set of tools and agreements, and learning how to plan migration dates.
BEM implemented our e-mail mailing template. We are preparing letters on the server, and the above limitations of the BEM platform do not affect this application. Using BEM to design it is an appropriately elegant solution.
In addition, our designers
prototype using BEM and sometimes bring us overlaid components instead of mockups. And even if we stop writing on BEM, he will still find us :)
I read the first part. What is it about web designers?
I participated in the translation of one of the applications from BEM to React and clarified an important thing.
Before joining Yandex.Money, I was a simple typesetter and spent more than one year, tons of HTML and JSX. I did not take seriously the frontend community and its changing world. I did not understand why to study the first Angular, to forget about it tomorrow and to study the second. I did not understand why change jQuery.Ajax to Fetch, to replace Fetch with Axios later.
It turned out that when you translate a project from one framework to another, you do not just transfer the code. We have to analyze and improve the architecture of the application, straighten logic, refactor. And the constant change of tools is not an attempt to ride the wave of HYIP, but the constant search for the best solution that meets the requirements of the time. A dynamic field like nothing else contributes to the development of your product and your professional development, respectively. And the frontend is just such an area. Let's fight for it together!
React everyone!