Olá, aqui é a Katya da Yandex.Money novamente. Continuo minha história sobre como parei de fazer as pazes e comecei a viver. Na
primeira parte, contei como cheguei aqui e o que nossos desenvolvedores front-end fazem. Hoje - sobre a pilha da frente, de onde é o React e para onde foi o BEM.
Spoiler: O BEM não foi a lugar algum ¯ \ _ (ツ) _ / ¯. Vamos lá!

Atenção: alta concentração de front-end. Muito texto, fotos e código, como prometido.
Parte 2. Sobre tecnologia
Até 2016. Tentando escrever em React, o resultado é bastante tolerável. Ainda não suspeito que em um ano transferirei serviços inteiros para o React. 2017 começa com o Yandex. Dinheiro, eu tenho um BEM do cérebro e ainda não suspeito.
Backend no Node.js, minha primeira vez
Para se familiarizar com o projeto, um novo desenvolvedor recebe uma tarefa de teste. Eu tive sorte: eu tive essa tarefa do backlog. E no primeiro dia encontrei o Node.js.
O front-end no Yandex.Money é responsável não apenas pelo lado do cliente, mas também pela camada do servidor como um aplicativo Node.js. A tarefa do aplicativo é orquestrar dados do back-end Java para preparação em um formato orientado à exibição, bem como renderização e roteamento do servidor. Você teria dito isso há alguns anos atrás, eu não teria entendido nada e tudo é muito simples: quando uma solicitação é enviada do navegador para o servidor, o Node.js gera solicitações HTTP para o back-end, recebe os dados e as páginas de modelos necessárias. Usamos o
Express como a estrutura do servidor e, para desenvolver aplicativos internos sem um
link herdado
, decidimos usar o
Koa2 . Os desenvolvedores adoraram o design da estrutura e decidimos não fazer o downgrade para o Express, então o Koa2 permaneceu na pilha. Mas não lançamos o código Koa2 para usuários externos: a estrutura não possui suporte suficiente, mas existem vulnerabilidades abertas.
Já escrevemos sobre o lugar do Node.js em nosso front-end, mas desde então algo mudou. O Node.js 8 tornou-se LTS e já está sendo executado em nossos servidores de produção. Também queremos abandonar os servidores Nginx, que criamos em cada host para distribuir estatísticas - eles serão substituídos por servidores separados pelo Nginx e pela CDN algum dia.
Para separar o código entre os projetos, mas não para torná-lo disponível ao público, usamos todo um conjunto de ferramentas: armazene os módulos no Bitbucket e colete-os no Jenkins. Também usamos o registro local de pacotes e, graças a isso, não vamos à rede externa - isso acelera a montagem e aumenta a segurança de todo o sistema. Essa abordagem nos foi sugerida pelos javistas, eles são legais. Ame seus backenders;)
Também conduzimos um experimento - introduzimos um gerenciador de processos em um dos aplicativos, o que simplificou a administração de serviços no Node.js. Ele ajudou no cluster e também nos salvou de um script bash antigo que executava aplicativos.
E toda a pilha não é suficiente
Temos javascript em todos os lugares no frontend. E no servidor, e no cliente, e sob o capô de ferramentas internas. Conhecemos outras línguas, mas o javascript faz um ótimo trabalho.
Mas o BEM, no âmbito de nossas tarefas, não lida com tudo.
O que é o BEM?O BEM é uma abordagem de desenvolvimento da Web inventada pelo Yandex durante a vida útil de páginas HTML estáticas e cascatas CSS. Ainda não havia uma abordagem componente e era necessário manter a uniformidade de muitos serviços. O Yandex não ficou surpreso e desenvolveu sua própria abordagem de componentes, que hoje permite criar componentes isolados e escrever código declarativo flexível.
O BEM não é apenas uma metodologia, mas também um grande conjunto de tecnologias e bibliotecas. Alguns deles são adaptados às especificidades do BEM, e alguns podem muito bem ser usados isoladamente da arquitetura do BEM. Se você precisar de um poderoso mecanismo de modelo ou um exemplo digno de abstração de componente sobre o DOM em seu projeto, você sabe onde encontrá-los;)
Portanto, começamos a transferir serviços para o React. Alguns deles já vivem em dois aplicativos criados em pilhas diferentes:
- uma plataforma específica para o Yandex BEM;
- Reagir ecossistema jovem e elegante.
Yandex Technologies
É hora de lhe dizer por que me apaixonei pelo BEM.
Níveis de redefinição
Níveis, níveis, níveis ... BEM! Lucro!
Os níveis superiores são uma das principais características da metodologia BEM. Para entender como eles funcionam, veja a figura:

A imagem é formada sobrepondo camadas. Cada camada altera a imagem final, mas não altera as outras camadas. A camada pode ser facilmente puxada ou adicionada e a imagem muda.
Os níveis de substituição fazem o mesmo com o código:

O comportamento do componente é formado durante a montagem do código. Para adicionar um comportamento adicional, basta conectar o nível desejado à montagem. O código do módulo de diferentes níveis, como se estivessem em camadas uns sobre os outros. Nesse caso, o código fonte não muda, mas temos um comportamento diferente, combinando níveis diferentes.
Quais são os níveisA imagem acima mostra vários níveis de redefinição:
- O nível básico - a biblioteca - fornece o módulo de código-fonte;
- O próximo nível - o projeto - modifica este módulo para as necessidades do projeto;
- Um nível superior - plataforma - torna o mesmo módulo específico para diferentes dispositivos;
- A cereja no bolo - o nível das experiências - altera o módulo para o teste A / B.
O nível do projeto é independente do nível da biblioteca, portanto, é fácil atualizar a biblioteca. O nível da plataforma permite que você use uma montagem diferente para diferentes dispositivos. E o nível do experimento é conectado para testes em usuários e também é desativado facilmente quando os resultados são obtidos.
O próprio desenvolvedor decide quais níveis ele precisa: você pode criar um nível com um tema ou um nível com o mesmo código em uma estrutura diferente.
Os níveis permitem escrever módulos complexos baseados em simples, combinar facilmente o comportamento e atrapalhar o mesmo código entre os serviços. E esse código é coletado pelo
ENB - Webpack no mundo BEM.
Quando me familiarizei com o BEM, fiquei especialmente satisfeito com as bibliotecas da interface do usuário nas quais os componentes prontos estão. Estamos expandindo esses componentes dentro da estrutura de novas bibliotecas e os compartilhando entre projetos. Isso facilita a vida: raramente faço maquiagem, não escrevo o mesmo tipo de JS e montei rapidamente interfaces a partir de blocos prontos.

Agora, examinaremos mais de perto as ferramentas da plataforma BEM para entender o que o BEM não faz bem o suficiente e por que não se adequou às nossas tarefas.
BEM-XJST
Começarei com meu
mecanismo de modelo
bem-xjst favorito. Antes do Yandex.Money, eu usava Jade, e Bem-xjst ilustrava perfeitamente os pontos negativos de Jade, que eu não via na época. Os modelos bem-xjst são declarativos [1], não possuem o inferno [2] e atendem perfeitamente aos requisitos da abordagem de componentes [3]. Tudo isso é visto claramente no exemplo:

Na
caixa de areia, você pode ver o resultado do modelo e brincar com ele.
Como isso funciona? Dentro é o segredo da arquitetura perfeita;)- escreva BEMJSON. BEMJSON é um JSON que descreve uma árvore BEM. Uma árvore BEM é uma representação da árvore DOM como componentes independentes;
- O bem-xjst aceita BEMJSON como entrada e aplica padrões. Esse processo pode ser comparado à renderização em um navegador. O navegador ignora a árvore do DOM e aplica gradualmente regras CSS aos seus nós do DOM: tamanho, cor do texto, recuo. O Bem-xjst também ignora o BEMJSON, procura por modelos correspondentes aos seus nós e os aplica gradualmente: tag, atributos, conteúdo. "Aplicar um modelo" significa gerar uma string HTML a partir dele. A geração de HTML do BEMJSON é manipulada por um dos mecanismos de modelo - BEMHTML.
Escrever modelos é simples: selecione a entidade e escreva as funções que o mecanismo de modelos chamará para renderizar partes da string HTML. O mais difícil é destacar a essência. Entidades corretas são a chave para uma boa arquitetura!
Quanto mais longa a barba, maior a chance de você já ter notado uma referência no nome do modelo:
XSLT (eXtensible Stylesheet Language Transformations) => XJST (eXtensible JavaScript Transformations). Ele usa os princípios do XSLT e, portanto, é tão declarativo. Se você não sabe o que é XSLT, considere-se com sorte :)
Bem-xjst é isomórfico. Nós renderizamos páginas HTML no servidor e as alteramos dinamicamente no cliente. Para modelagem em tempo de execução, o bem-xjst fornece uma API que usamos ao escrever o código javascript do lado do cliente.
Eu-bem
Com o bem-xjst, descrevemos a visão e a lógica com o
i-bem . I-bem é uma abstração sobre o DOM que fornece uma API de alto nível para trabalhar com componentes. Simplificando, vamos escrever o seguinte:

em vez disso:

Para escrever código, você não precisa saber sobre a implementação interna do componente. Operamos com entidades que descrevemos no modelo: de qualquer maneira, será um seletor jQuery ou um elemento DOM. Podemos criar eventos personalizados adequados para um modelo de objeto específico e trabalhar com eventos e interfaces nativos ficará oculto na implementação interna. A lógica de baixo nível também é descrita aqui, o que significa que não carregamos o código com a lógica principal com verificações desnecessárias. Como resultado, o código é fácil de ler e não depende de uma tecnologia específica.
I-bem permite que você descreva a lógica do componente como um conjunto de estados [1]. Este é um javascript declarativo. O I-bem implementa seu próprio emissor de eventos: quando os estados mudam, os componentes geram automaticamente eventos aos quais outro componente pode se inscrever [2].
É assim que a maioria do código javascript do lado do cliente BEM se parece:

Como isso funciona- pelo evento domReady i-bem, ele encontra componentes (blocos) na árvore DOM e os inicializa - cria um objeto js na memória do navegador que corresponde ao bloco;
- após a ocorrência dos eventos necessários, definimos os marcadores de bloco que refletem o estado. O papel dos marcadores é desempenhado pelas classes CSS. Por exemplo, quando clicamos na entrada, adicionamos a ela a classe “input_focused”, que serve como marcador;
- Ao definir esses marcadores, o i-bem inicia os retornos de chamada especificados na implementação javascript do bloco.
A lógica de escrita é simples: você precisa descrever os possíveis estados do bloco (os mesmos marcadores) e definir manipuladores para alterar esses estados (os mesmos retornos de chamada).
Com o i-bem, podemos redefinir facilmente o comportamento dos componentes, criar uma API bem formada para a interação deles e alterá-los dinamicamente em tempo de execução. Então o que está faltando?
Adoramos o BEM pela declaratividade, fácil escalabilidade e abstrações de alto nível, mas não estamos mais prontos para suportar suas limitações. Abaixo, consideraremos o problema de renderização do cliente, armazenamento de dados e outras limitações da plataforma BEM. Com o tempo, esses problemas podem ser resolvidos pelos colaboradores do BEM, mas não estamos prontos para esperar.
Web moderna com SPA e adaptabilidade para dispositivos móveis também requerem adaptabilidade de nós. Portanto, decidimos mudar para nossa própria pilha. E eles escolheram Reagir.
Nova pilha React Maple
Reagir trouxe para nossas vidas um DOM virtual, recarga quente, CSS em JS e uma grande comunidade da qual nos tornamos parte.
A migração de nossos serviços para o React está em pleno andamento, alguns aplicativos já foram total ou parcialmente reescritos para o React. Conhecemos novas abordagens e ferramentas e melhoramos a arquitetura de nossos aplicativos.
Bibliotecas
O particionamento de entidades da interface em blocos BEM independentes é muito amigável com a abordagem do componente React. Os desenvolvedores do Yandex escreveram
bem-react-core e transferiram a biblioteca de interface do usuário do componente base para o React. Escrevemos uma biblioteca de adaptadores que leva em conta as especificidades desses componentes e as fornece como
HOC :

Essas bibliotecas estão conectadas não no aplicativo, mas na biblioteca principal do componente:

O aplicativo depende apenas da biblioteca principal e obtém todos os componentes:

Isso reduz o número de dependências de aplicativos e as bibliotecas não entram no pacote configurável duas vezes em versões diferentes.
Tecnologia
O React não está vinculado a tecnologias específicas e nós escolhemos ferramentas e bibliotecas. Existem axios, redux, redux form, redux thunk, componentes estilizados, TypeScript, flow, gracejo e outras coisas legais no meu armamento. Para evitar o zoológico, coordenamos o uso de novas tecnologias com outros desenvolvedores - enviamos uma solicitação de recebimento a um repositório especial com uma análise de quão útil é a tecnologia e por que ela foi escolhida.
O front-end entra no bar e o barman diz a ele
Para aplicativos no React, estamos criando uma plataforma que reunirá bibliotecas e processos para criar e dar suporte a eles. O coração desta plataforma é o utilitário do console Frontend Bar. Bar pode cozinhar muitos pedaços deliciosos.
No menu:
- Configurar com ice: bar mistura e sacode suas variáveis yml e prepara um modelo de configuração para ansible.
- Suco com o aroma dos configuradores: bar criará uma nova aplicação baseada em um espaço em branco modular - Suco.
- Conjunto de configurações básicas da biblioteca. Em breve.
Agora, é fácil criar um aplicativo interessante - o frontend-bar faz suco. Faça suco, não guerra! Quando o Bar implementa um novo aplicativo, ele executa um conjunto de configurações do Juice: package.json, .babelrc é gerado, middleware chave e código de rotas, código do componente raiz. O Frontend Bar facilitará a alocação de novos microsserviços e ajudará a cumprir regras uniformes para escrever código.
Ao mudar para uma nova pilha, começamos a melhorar a arquitetura de aplicativos do servidor - escrevemos um novo criador de logs para o cliente e uma biblioteca com um conjunto de abstrações para implementar o
MVC . Hoje decidimos qual será a nova arquitetura de servidor.

Spoiler: escolha cebola.
O que aconteceu e ficou melhor? Vamos entender
Interfaces dinâmicas
Was
Eu escrevi acima que o bem-xjst fornece uma API para modelagem em tempo de execução. O I-bem, por sua vez, pode trabalhar com a árvore DOM. Nós os tornaremos amigos e poderemos gerar e modificar dinamicamente HTML. Vamos tentar mudar o botão por evento:


Neste exemplo, o lado fraco do BEM é visível: o i-bem não quer ser amigo de bem-xjst e não quer saber nada sobre modelos. Ele adiciona uma classe ao bloco, mas não aplica o modelo [1]. Re-renderizamos o componente manualmente [2]:
- descrever um novo pedaço da árvore BEM [3];
- em seguida, aplique um novo modelo [4];
- e inicialize outro componente no nó DOM atual [5].
Além disso, o i-bem não cria diff de árvores BEM; portanto, todo o componente é renderizado, não as partes que foram alteradas. Considere um exemplo simples: renderize novamente o conteúdo de uma janela modal sob demanda. É composto por três elementos:

Para simplificar, assumimos que apenas um elemento pode mudar.

Eu quero fazer [1] e relaxar. Mas o i-bem não entenderá o que mudou, renderiza completamente todo o componente e também relaxa. Neste exemplo, não haverá conseqüências sérias, mas e se todos os formulários forem renderizados incorretamente dessa maneira? Isso piora o desempenho e causa efeitos colaterais desagradáveis: em algum lugar a entrada pisca, a dica de ferramenta sem dono fica em algum lugar. Por isso, ficamos tristes e controlamos manualmente as partes dos componentes para criar um renderizador de pontos [2]. Isso complica o desenvolvimento, e novamente estamos tristes.
Tornou-se
Reagir veio e arruinou tudo. Ele próprio monitora o estado dos componentes, não gerenciamos mais a renderização manual e não pensamos em interagir com o DOM. React contém uma implementação
virtual do DOM . Chamar React.createElement cria um objeto js do nó DOM com suas propriedades e descendentes - o DOM virtual desse componente, armazenado dentro do React. Quando um componente é alterado, o React calcula o novo DOM virtual e, em seguida, o diff do novo e salvo, além de atualizar apenas a parte do DOM que foi alterada. Tudo voa, e só podemos otimizar lógica complexa usando shouldComponentUpdate. É um sucesso!
Armazenamento de dados
Was
No BEM, preparamos todos os dados no servidor e os transferimos para os componentes da página:

Os componentes são isolados e não compartilham dados entre si, o que significa que os mesmos dados deverão ser lançados em diferentes componentes [1]. Não poderemos obtê-los no cliente; portanto, cada componente aceita antecipadamente um conjunto de dados necessários para todos os cenários possíveis de sua operação. Isso significa que carregamos o componente com dados que podem não ser necessários [2].
Às vezes, uma entidade global nos resgata, na qual parte dos dados comuns é armazenada, mas o armazenamento global de variáveis não se encaixa bem no conceito BEM. Portanto, escrevemos um
bem-redux que adapta o Redux ao BEM.
Redux é um gerente de estado que gerencia o fluxo de dados. Ele gerencia perfeitamente nossos dados em interfaces simples, mas ao desenvolver um componente complexo, encontramos o problema de renderização que descrevi acima. Redux não é amigável com o i-bem, corrigimos bugs e estamos tristes.
Tornou-se
Redux + React = <3O Redux armazena dados para todo o aplicativo em um único local [1]:

O componente em si decide quando e de quais dados ele precisa [2]:

Precisamos apenas descrever os cenários do componente [3] e indicar onde obter os dados para sua execução [4]:

E o React fará o resto [5]:

Essa abordagem permite que você siga o princípio da responsabilidade única e encapsule a lógica do componente no próprio componente, em vez de espalhá-lo no código da página. É um sucesso!
Você tem que pagar por tudo
Para ter sucesso, pagamos uma grande quantidade de legado no React. É doloroso ver como seu código, escrito apenas alguns meses atrás, se transforma em obsoleto.
O fato é que o React é uma biblioteca de camadas de exibição, não uma estrutura completa. Você pode selecionar todas as ferramentas, mas
precisará selecionar todas as ferramentas. E também para organizar você mesmo o código, formular abordagens para resolver problemas típicos, desenvolver um conjunto de acordos e escrever os plugins ausentes. Escrevemos nossos próprios validadores para formulários redux e ainda não aprendemos a trabalhar com animações complexas. E tentamos jogar fora, escrever e reescrever. E não estamos reescrevendo sempre, e é por isso que nossa carteira de pedidos está crescendo.
O React é jovem o suficiente e não está pronto para o desenvolvimento da empresa, diferentemente do BEM. E enquanto aprendíamos a cozinhá-lo, estragamos toda a cozinha e nós mesmos bagunçamos até o cotovelo. E ainda estamos debatendo se precisamos de fluxo ou não, e ainda não entendemos completamente o que armazenar na loja e o que está na loja local. Escrevemos conforme necessário e vamos a conferências para descobrir como. Vencemos os cones, mas avançamos com confiança.
Pãezinhos inesperados
A nova pilha nos permitiu dar uma nova olhada em várias tarefas e forneceu maneiras simples de resolvê-las.
CSS em JS
Was
Considere um caso simples da vida: colorir e animar um ícone por um evento, algo como isto:

O código não é nada:

É verdade que, de acordo com as regras do BEM, você terá que distribuí-lo já em três diretórios:

Sobrecarga? Um ponto discutível. Mais importante, em js, adicionamos essas classes manualmente quando os eventos necessários ocorrem. A situação usual, mas quanto mais personalizada ou complexa a interface, mais frequentemente você precisará adicionar e remover classes. E se você precisar alterar não apenas o ícone, mas também o texto? Não é exatamente a lógica que você deseja ver no código js:

Mas e se a duração da animação depender de algo e for definida dinamicamente? Em seguida, reescreveremos a animação CSS no jQuery e ficaremos um pouco tristes.
Tornou-se
Componentes estilizados , eu te amo! CSS em JS - um amor!
Meu compositor interno se alegra: A
modularidade é preservada, a animação CSS funciona e nenhum trabalho manual com classes. Um bom bônus para a nova pilha.Digitação
Was
Costumávamos escrever toneladas de jsDoc. Vamos ver se é útil:
Este exemplo é retirado do código de produção. O que o estado contém? Eu não tenho ideia. Sim, existe um leia-me, mas, infelizmente, está um pouco desatualizado. Sim, temos vergonha, mas com documentação e comentários isso geralmente acontece, eles não são confiáveis. Terá que investigar o código. Ou não vá fundo e acidentalmente quebre tudo. Estamos com pressa, não se aprofundem, quebre e se sintam tristes.Tornou-se
A digitação veio em socorro. "Tyk" no tipo, e todos os detalhes do método diante dos meus olhos. Com preguiça de entender? O verificador Precommit começará o fluxo e você ainda precisará descobrir.Não gostei do fluxo à primeira vista. As datas estão ativadas, o gerente faz ping e você "não pode obter propriedade" e aqui "falta a propriedade". Mas recentemente me disseram que os tipos podem ser projetados por O_o. Como projetar por tipos? Algo assim:
meu mundo virou de cabeça para baixo. O fluxo não é mais um pesadelo. Era conveniente e útil descrever os módulos da API com tipos antes de escrever o código. Código confiável - um ótimo bônus!Então não há mais BEM?
Não.
O BEM está ativo e continuamos a oferecer suporte a aplicativos na pilha do BEM. Com o tempo, eles também mudarão para o React, mas por enquanto estamos preparando o caminho para isso: traduzimos as bibliotecas de componentes, formamos um conjunto de ferramentas e acordos e aprendemos a planejar datas de migração.No BEM, nosso mecanismo de modelo de boletim informativo por email é implementado. Preparamos cartas no servidor e as limitações da plataforma BEM descritas acima não afetam esse aplicativo. Usar o BEM para desenvolvê-lo é uma solução elegante e apropriada.Além disso, nossos designers prototipam usando o BEM e, às vezes, trazem-nos componentes pré-montados em vez de layouts. E mesmo se pararmos de escrever no BEM, ele ainda nos encontrará :)Eu li a primeira parte. E os codificadores?
Participei da tradução de uma das aplicações do BEM para o React e descobri uma coisa importante.Antes de ingressar no Yandex.Money, eu era um tipógrafo simples e passava mais de um ano, quilômetros de toneladas de HTML e JSX. Não levei a sério a comunidade front-end e seu mundo em transformação. Não entendi por que estudar o primeiro Angular para esquecê-lo amanhã e estudar o segundo. Não entendi por que alterar o jQuery.Ajax para Fetch e, em seguida, substituir Fetch por Axios.Verificou-se que quando você transfere um projeto de uma estrutura para outra, você não está apenas portando o código. Temos que analisar e melhorar a arquitetura do aplicativo, endireitar a lógica, refatorar. Uma mudança constante de ferramentas não é uma tentativa de aproveitar a onda de hype, mas uma busca constante pela melhor solução que atenda aos requisitos da época. Um campo de desenvolvimento dinâmico como nada contribui para o desenvolvimento do seu produto e o desenvolvimento profissional, respectivamente. E o frontend é exatamente essa área. Vamos lutar por isso juntos!Reagir a todos!