Frontend de jet. L'histoire de la façon dont nous avons tout réécrit

Salut, c'est encore Katya de Yandex.Money. Je continue mon histoire sur la façon dont j'ai arrêté de me maquiller et commencé à vivre. Dans la première partie, j'ai expliqué comment je suis arrivé ici et ce que font nos développeurs front-end. Aujourd'hui - à propos de la pile avant, d'où vient React et où est parti BEM.

Spoiler: BEM n'est allé nulle part ¯ \ _ (ツ) _ / ¯. C'est parti!



Attention: forte concentration de frontend. Beaucoup de texte, d'images et de code, comme promis.

Partie 2. À propos de la technologie


Far 2016. En essayant d'écrire dans React, cela s'avère assez tolérable. Je ne soupçonne toujours pas que dans un an, je transférerai des services entiers à React. 2017 commence sur Yandex.Money, j'ai un BEM du cerveau, et je ne le soupçonne toujours pas.

Backend sur Node.js, ma première fois


Pour se familiariser avec le projet, un nouveau développeur reçoit une tâche de test. J'ai eu de la chance: j'avais cette tâche dans l'arriéré. Et le tout premier jour, je suis tombé sur Node.js.

Le front-end dans Yandex.Money est responsable non seulement du côté client, mais également de la couche serveur en tant qu'application Node.js. La tâche de l'application est d'orchestrer les données du backend Java pour la préparation sous une forme orientée vue, ainsi que le rendu et le routage du serveur. On vous l'aurait dit il y a quelques années, je n'aurais rien compris, et tout est assez simple: lorsqu'une requête vient du navigateur vers le serveur, Node.js génère des requêtes HTTP pour le backend, reçoit les données nécessaires et des modèles de pages Web. Nous utilisons Express comme infrastructure de serveur et pour développer des applications internes sans lien hérité , nous avons décidé d'utiliser Koa2 . Les développeurs ont adoré la conception du framework, et nous avons décidé de ne pas passer à Express, donc Koa2 est resté sur la pile. Mais nous ne déployons pas le code Koa2 aux utilisateurs externes: le framework n'a pas assez de support, mais il existe des vulnérabilités ouvertes.

Nous avons déjà écrit sur la place de Node.js dans notre frontend, mais depuis lors, quelque chose a changé. Node.js 8 est devenu LTS et fonctionne déjà sur nos serveurs de production. Nous voulons également abandonner les serveurs Nginx, que nous élevons sur chaque hôte pour distribuer la statique - ils seront remplacés par des serveurs séparés avec Nginx, et un jour CDN.

Pour tâtonner le code entre les projets, mais pas pour le rendre public, nous utilisons tout un ensemble d'outils: stocker les modules dans Bitbucket et les collecter dans Jenkins. Nous utilisons également le registre local des packages et grâce à cela, nous ne nous rendons pas sur le réseau externe - cela accélère l'assemblage et augmente la sécurité de l'ensemble du système. Cette approche nous a été suggérée par les javistes, ils sont cool. Aimez vos backenders;)

Nous avons également mené une expérience - nous avons introduit un gestionnaire de processus dans l'une des applications, ce qui a simplifié l'administration des services sur Node.js. Il a aidé au clustering et nous a également sauvés d'un ancien script bash qui exécutait des applications.

Et toute la pile ne suffit pas


Nous avons javascript partout dans le frontend. Et sur le serveur, et sur le client, et sous le capot des outils internes. Nous connaissons d'autres langues, mais javascript fait un excellent travail.

Mais BEM dans le cadre de nos missions ne fait pas face à tout.

Qu'est-ce que le BEM?
BEM est une approche de développement Web inventée par Yandex pendant la durée de vie des pages HTML statiques et des cascades CSS. Il n'y avait pas encore d'approche par composante et il était nécessaire de maintenir l'uniformité de nombreux services. Yandex n'a pas été surpris et a développé sa propre approche de composants, qui vous permet aujourd'hui de créer des composants isolés et d'écrire du code déclaratif flexible.

BEM n'est pas seulement une méthodologie, mais aussi un large éventail de technologies et de bibliothèques. Certains d'entre eux sont adaptés aux spécificités de BEM, et certains peuvent très bien être utilisés indépendamment de l'architecture BEM. Si vous avez besoin d'un moteur de modèle puissant ou d'un exemple digne d'abstraction de composants sur le DOM dans votre projet, vous savez où les trouver;)

Par conséquent, nous avons commencé à transférer des services vers React. Certains d'entre eux vivent déjà dans deux applications construites sur des piles différentes:

- une plateforme spécifique à Yandex BEM;
- L'écosystème jeune et à la mode de React.

Yandex Technologies


Il est temps de vous dire pourquoi je suis tombé amoureux de BEM.

Niveaux de redéfinition


Niveaux, niveaux, niveaux ... BEM! Profit!
Les niveaux supérieurs sont l'une des principales caractéristiques de la méthodologie BEM. Pour comprendre comment ils fonctionnent, regardez l'image:


L'image est formée par superposition de couches. Chaque calque modifie l'image finale, mais ne modifie pas les autres calques. Le calque peut être facilement retiré ou ajouté, et l'image changera.
Les niveaux de remplacement font de même avec le code:


Le comportement du composant est formé lors de l'assemblage du code. Pour ajouter un comportement supplémentaire, connectez simplement le niveau souhaité à l'assemblage. Le code du module de différents niveaux comme s'il était superposé. Dans ce cas, le code source ne change pas, mais nous obtenons un comportement différent, combinant différents niveaux.

Quels sont les niveaux
L'image ci-dessus montre plusieurs niveaux de redéfinition:
  • Le niveau de base - la bibliothèque - fournit le module de code source;
  • Le niveau suivant - le projet - modifie ce module aux besoins du projet;
  • Un niveau supérieur - plate-forme - rend le même module spécifique pour différents appareils;
  • La cerise sur le gâteau - le niveau des expériences - change le module pour les tests A / B.


Le niveau du projet est indépendant du niveau de la bibliothèque, la bibliothèque est donc facile à mettre à jour. Le niveau de plate-forme vous permet d'utiliser un assemblage différent pour différents appareils. Et le niveau de l'expérience est connecté pour les tests sur les utilisateurs et s'éteint également facilement lorsque les résultats sont obtenus.

Le développeur décide lui-même des niveaux dont il a besoin: vous pouvez créer un niveau avec un thème ou un niveau avec le même code sur un framework différent.

Les niveaux vous permettent d'écrire des modules complexes basés sur des modules simples, de combiner facilement les comportements et de tâtonner le même code entre les services. Et ce code est collecté par ENB - Webpack dans le monde BEM.

Lorsque j'ai fait connaissance avec BEM, j'ai été particulièrement satisfait des bibliothèques d'interface utilisateur dans lesquelles se trouvent les composants prêts à l'emploi. Nous développons ces composants dans le cadre de nouvelles bibliothèques et les partageons entre projets. Cela facilite la vie: je maquille rarement, je n'écris pas le même type de JS et j'assemble rapidement des interfaces à partir de blocs prêts à l'emploi.



Nous allons maintenant examiner de plus près les outils de la plate-forme BEM afin de comprendre ce que BEM ne fait pas assez bien et pourquoi il ne convenait pas à nos tâches.

BEM-XJST


Je vais commencer avec mon moteur de modèle bem-xjst préféré. Avant Yandex.Money, j'utilisais Jade, et Bem-xjst illustrait parfaitement les inconvénients de Jade, que je ne voyais pas alors. Les modèles bem-xjst sont déclaratifs [1], ils n'en ont pas si hell [2], et ils répondent parfaitement aux exigences de l'approche par composants [3]. Tout cela est clairement visible dans l'exemple:



Dans le bac à sable, vous pouvez voir le résultat du modèle et jouer avec.

Comment ça marche? L'intérieur est le secret d'une architecture parfaite;)
  • écrivez BEMJSON. BEMJSON est un JSON décrivant une arborescence BEM. Un arbre BEM est une représentation de l'arbre DOM sous forme de composants indépendants;
  • bem-xjst accepte BEMJSON en entrée et applique des modèles. Ce processus peut être comparé au rendu dans un navigateur. Le navigateur contourne l'arborescence DOM et applique progressivement des règles CSS à ses nœuds DOM: taille, couleur du texte, indentation. Bem-xjst contourne également BEMJSON, recherche les modèles correspondant à ses nœuds et les applique progressivement: balise, attributs, contenu. «Appliquer un modèle» signifie générer une chaîne HTML à partir de celui-ci. La génération HTML à partir de BEMJSON est gérée par l'un des moteurs de modèle - BEMHTML.


L'écriture de modèles est simple: sélectionnez l'entité et écrivez les fonctions que le moteur de modèle appellera pour rendre des parties de la chaîne HTML. La chose la plus difficile est de mettre en évidence l'essence. Des entités correctes sont la clé d'une bonne architecture!

Plus votre barbe est longue, plus vous avez de chances de remarquer une référence dans le nom du modèle: XSLT (eXtensible Stylesheet Language Transformations) => XJST (eXtensible JavaScript Transformations). Il utilise les principes de XSLT et est donc si déclaratif. Si vous ne savez pas ce qu'est le XSLT, considérez-vous chanceux :)

Bem-xjst est isomorphe. Nous rendons les pages HTML sur le serveur et les modifions dynamiquement sur le client. Pour les modèles lors de l'exécution, bem-xjst fournit une API que nous utilisons lors de l'écriture de code javascript côté client.

I-bem


Avec bem-xjst, nous décrivons la vue et la logique avec i-bem . I-bem est une abstraction sur le DOM qui fournit une API de haut niveau pour travailler avec des composants. Autrement dit, écrivons ceci:



à la place:



Pour écrire du code, vous n'avez pas besoin de connaître l'implémentation interne du composant. Nous fonctionnons avec des entités que nous avons décrites dans le modèle: de toute façon, ce sera un sélecteur jQuery ou un élément DOM. Nous pouvons créer des événements personnalisés adaptés à un modèle d'objet particulier et travailler avec des événements et des interfaces natifs seront masqués dans l'implémentation interne. La logique de bas niveau y est également décrite, ce qui signifie que nous ne chargeons pas le code avec la logique principale avec des vérifications inutiles. Par conséquent, le code est facile à lire et ne dépend pas d'une technologie spécifique.

I-bem vous permet de décrire la logique du composant comme un ensemble d'états [1]. Ceci est du javascript déclaratif. I-bem implémente son propre émetteur d'événements: lorsque les états changent, les composants génèrent automatiquement des événements auxquels un autre composant peut s'abonner [2].

Voici à quoi ressemble la plupart du code javascript côté client BEM:



Comment ça marche
  • par l'événement domReady i-bem, il trouve des composants (blocs) dans l'arborescence DOM et les initialise - crée un objet js dans la mémoire du navigateur qui correspond au bloc;
  • à l'apparition des événements nécessaires, nous définissons les marqueurs de bloc qui reflètent l'état. Le rôle des marqueurs est assuré par les classes CSS. Par exemple, lorsque nous cliquons sur entrée, nous y ajoutons la classe «input_focused», qui sert de marqueur;
  • lors de la définition de tels marqueurs, i-bem démarre les rappels spécifiés dans l'implémentation javascript du bloc.

La logique d'écriture est simple: vous devez décrire les états possibles du bloc (les mêmes marqueurs) et définir des gestionnaires pour changer ces états (les mêmes rappels).

Avec i-bem, nous pouvons facilement redéfinir le comportement des composants, créer une API bien formée pour leur interaction et les modifier dynamiquement lors de l'exécution. Alors qu'est-ce qui manque?
Nous aimons BEM pour la déclarativité, l'évolutivité facile et les abstractions de haut niveau, mais nous ne sommes plus prêts à accepter ses limites. Ci-dessous, nous examinerons le problème du rendu client, du stockage des données et d'autres limitations de la plate-forme BEM. Au fil du temps, ces problèmes peuvent être résolus par les contributeurs BEM, mais nous ne sommes pas prêts à attendre.

Le Web moderne avec SPA et l'adaptabilité pour les appareils mobiles nécessitent également une adaptabilité de notre part. Par conséquent, nous avons décidé de passer à notre propre pile. Et ils ont choisi React.

Nouvelle pile d'érable React


React a apporté dans nos vies un DOM virtuel, un rechargement à chaud, des CSS dans JS et une grande communauté dont nous sommes devenus partie.

La migration de nos services vers React bat son plein, certaines applications ont déjà été entièrement ou partiellement réécrites vers React. Nous découvrons de nouvelles approches et de nouveaux outils et améliorons l'architecture de nos applications.

Bibliothèques


Le partitionnement des entités d'interface en blocs BEM indépendants est très convivial avec l'approche du composant React. Les développeurs de Yandex ont écrit bem-react-core et ont transféré la bibliothèque d'interface utilisateur du composant de base à React. Nous avons écrit une bibliothèque d'adaptateurs dessus qui prend en compte les spécificités de ces composants et les fournit en tant que HOC :



Ces bibliothèques ne sont pas connectées dans l'application, mais dans la bibliothèque principale du composant:



L'application ne dépend que de la bibliothèque principale et en obtient tous les composants:



Cela réduit le nombre de dépendances des applications et les bibliothèques n'entrent pas deux fois dans le bundle sous différentes versions.

La technologie


React n'est pas lié à des technologies spécifiques et nous choisissons nous-mêmes des outils et des bibliothèques. Il y a des axios, redux, redux form, redux thunk, styled-components, TypeScript, flow, jest et d'autres trucs sympas dans mon armement. Afin d'empêcher le zoo, nous coordonnons l'utilisation des nouvelles technologies avec d'autres développeurs - nous envoyons une pull-request à un référentiel spécial avec une analyse de l'utilité de la technologie et pourquoi elle a été choisie.

L'avant entre dans le bar, et le barman lui dit


Pour les applications sur React, nous créons une plate-forme qui réunira des bibliothèques et des processus pour les créer et les prendre en charge. Le cœur de cette plateforme est l'utilitaire de console Frontend Bar. Le bar peut cuisiner beaucoup de délicieux morceaux.

Dans le menu:

  1. Config with ice: bar mélange et secoue vos variables yml et prépare un modèle de configuration pour ansible.
  2. Juice avec l'arôme des configurateurs: bar créera une nouvelle application basée sur un blanc modulaire - Juice.
  3. Ensemble de paramètres de bibliothèque de base. À venir bientôt.

La création d'une application juteuse est maintenant facile - la barre frontale fait du jus. Faites du jus, pas la guerre! Lorsque Bar déploie une nouvelle application, il exécute un ensemble de configurations à partir de Juice: package.json, .babelrc est généré, le middleware clé et le code de routage, le code du composant racine. Frontend Bar facilitera l'allocation de nouveaux microservices et aidera à se conformer aux règles uniformes d'écriture de code.

Lors du passage à une nouvelle pile, nous avons commencé à améliorer l'architecture serveur des applications - nous avons écrit un nouvel enregistreur pour le client et une bibliothèque avec un ensemble d'abstractions pour implémenter MVC . Aujourd'hui, nous décidons de la nouvelle architecture de serveur.



Spoiler: choisissez l'oignon.

Que s'est-il passé et est-ce que ça s'est amélioré? Comprenons


Interfaces dynamiques


Était


J'ai écrit ci-dessus que bem-xjst fournit une API pour les modèles lors de l'exécution. I-bem, à son tour, peut fonctionner avec l'arborescence DOM. Nous nous ferons des amis et nous pourrons générer et modifier dynamiquement du HTML. Essayons de changer le bouton par événement:




Dans cet exemple, le côté faible de BEM est visible: i-bem ne veut pas être ami avec bem-xjst et ne veut rien savoir des modèles. Il ajoute une classe au bloc, mais n'applique pas le modèle [1]. Nous restituons le composant manuellement [2]:

  • décrire un nouveau morceau d'arbre BEM [3];
  • puis appliquez un nouveau modèle [4];
  • et initialiser un autre composant sur le nœud DOM actuel [5].

De plus, i-bem ne crée pas de diff d'arbres BEM, par conséquent, le composant entier est rendu, pas les parties qui ont changé. Prenons un exemple simple: restituez le contenu d'une fenêtre modale à la demande. Il se compose de trois éléments:



Pour simplifier, nous supposons qu'un seul élément peut changer.



Je veux faire [1] et me détendre. Mais i-bem ne comprendra pas ce qui a changé, restituera complètement le composant entier et se détendra également. Dans cet exemple, il n'y aura pas de conséquences graves, mais que se passe-t-il si les formulaires entiers sont rendus de manière inexacte comme ceci? Cela détériore les performances et provoque des effets secondaires désagréables: quelque part l'entrée scintille, l'info-bulle sans propriétaire se bloque quelque part. Pour cette raison, nous sommes tristes et contrôlons manuellement les parties des composants pour créer un rendu de points [2]. Cela complique le développement et, encore une fois, nous sommes tristes.

Est devenu


React est venu et a tout gâché. Lui-même surveille l'état des composants, nous ne gérons plus le rendu manuel et ne pensons pas à interagir avec le DOM. React contient une implémentation DOM virtuelle . L'appel de React.createElement crée un objet js du nœud DOM avec ses propriétés et ses descendants - le DOM virtuel de ce composant, qui est stocké dans React. Lorsqu'un composant change, React calcule le nouveau DOM virtuel, puis le diff des nouveaux et des nouveaux enregistrés, et met à jour uniquement la partie du DOM qui a changé. Tout vole et nous ne pouvons optimiser la logique complexe qu'en utilisant shouldComponentUpdate. C'est un succès!

Stockage de données


Était


Dans BEM, nous préparons toutes les données sur le serveur et les transférons aux composants de la page:



Les composants sont isolés et ne partageront pas de données entre eux, ce qui signifie que les mêmes données devront être jetées dans différents composants [1]. Nous ne pourrons pas les obtenir sur le client, donc chaque composant accepte à l'avance un ensemble de données qui est nécessaire pour tous les scénarios possibles de son fonctionnement. Cela signifie que nous chargeons le composant avec des données dont il peut ne pas avoir besoin [2].

Parfois, une entité globale nous sauve, dans laquelle une partie des données communes est stockée, mais le stockage global des variables ne correspond pas bien au concept BEM. Par conséquent, nous avons écrit un bem-redux qui adapte Redux pour BEM. Redux est un gestionnaire d'état qui gère le flux de données. Il gère parfaitement nos données au sein d'interfaces simples, mais lors du développement d'un composant complexe, nous rencontrons le problème de rendu que j'ai décrit ci-dessus. Redux n'est pas amical avec i-bem, nous corrigeons les bugs et sommes tristes.

Est devenu


Redux + React = <3
Redux stocke les données de l'ensemble de l'application en un seul endroit [1]:



Le composant décide lui-même du moment et des données dont il a besoin [2]:



Il suffit de décrire les scénarios du composant [3] et d'indiquer où trouver les données pour son exécution [4]:



Et React fera le reste [5]:



Cette approche vous permet de suivre le principe de la responsabilité unique et d'encapsuler la logique du composant dans le composant lui-même, plutôt que de le diffuser dans le code de la page. C'est un succès!

Vous devez tout payer


Pour réussir, nous avons payé un lourd héritage à React. Il est douloureux de voir comment votre code, écrit il y a quelques mois à peine, se transforme en obsolète.

Le fait est que React est une bibliothèque de couche de vue, pas un framework à part entière. Vous pouvez sélectionner tous les outils, mais vous devrez sélectionner tous les outils. Et aussi d'organiser le code vous-même, de formuler des approches pour résoudre des problèmes typiques, de développer un ensemble d'accords et d'écrire les plugins manquants. Nous écrivons nos propres validateurs pour les formulaires redux et n'avons toujours pas appris à travailler avec des animations complexes. Et nous essayons de jeter, d'écrire et de réécrire. Et nous ne le réécrivons pas toujours, c'est pourquoi notre arriéré augmente.

React est assez jeune et n'est pas prêt pour le développement d'entreprise, contrairement à BEM. Et pendant que nous apprenions à le cuisiner, nous avons foiré toute notre cuisine et nous avons foiré jusqu'au coude. Et nous débattons toujours si nous avons besoin de flux ou non, et nous ne comprenons toujours pas complètement quoi stocker dans le magasin et ce qui est dans le magasin local. Nous écrivons au besoin et allons à des conférences pour savoir comment. Nous avons battu les cônes, mais en avançant avec confiance.

Petits pains inattendus


La nouvelle pile nous a permis de jeter un nouveau regard sur un certain nombre de tâches et a fourni des moyens simples de les résoudre.

CSS dans JS


Était


Prenons un cas simple de la vie: coloriser et animer une icône par un événement, quelque chose comme ceci:



Le code n'est rien:



Certes, selon les règles de BEM, vous devrez le distribuer déjà dans trois répertoires:



Frais généraux? Un point discutable. Plus important encore, dans js, nous ajoutons ces classes manuellement lorsque les événements nécessaires se produisent. La situation habituelle, mais plus l'interface est personnalisée ou complexe, plus vous devrez souvent ajouter et supprimer des classes. Et si vous devez changer non seulement l'icône, mais aussi le texte? Pas tout à fait la logique que vous voulez voir dans le code js:



Mais que se passe-t-il si la durée de l'animation dépend de quelque chose et est définie dynamiquement? Ensuite, nous réécrirons l'animation CSS dans jQuery et serons un peu tristes.

Est devenu


Composants stylés , je vous aime! CSS dans JS - un amour! Mon codeur intérieur se réjouit:



La modularité est préservée, l'animation CSS fonctionne et aucun travail manuel avec les classes. Un joli bonus pour la nouvelle pile.

Dactylographie


Était


Nous écrivions des tonnes de jsDoc. Voyons si c'est utile:



cet exemple est tiré du code de production. Que contient l'état? Je n'en ai aucune idée. Oui, il y a un readme, mais hélas, il est un peu dépassé. Oui, nous avons honte, mais avec la documentation et les commentaires, cela arrive souvent, ils ne sont pas fiables. Devra se plonger dans le code. Ou ne va pas en profondeur et ne casse pas tout accidentellement. Nous sommes pressés, n'allons pas en profondeur, ne nous brisons pas et ne nous sentons pas tristes.

Est devenu


La dactylographie est venue à la rescousse. "Tyk" sur le type, et tous les tenants et aboutissants de la méthode devant mes yeux. Trop paresseux pour comprendre? Le vérificateur de pré-engagement démarrera le flux , et vous devez toujours le comprendre.

J'ai détesté l'écoulement à première vue. Les dates sont allumées, le gestionnaire cingle, et vous avez «ne peut pas obtenir de propriété», et ici «la propriété manque». Mais récemment, on m'a dit que les types peuvent être conçus par O_o. Comment concevoir par types? Quelque chose comme ça:



Mon monde a basculé. Flow n'est plus un cauchemar. Il était pratique et utile de décrire les modules API avec des types avant d'écrire le code. Code fiable - un joli bonus!

Alors plus de BEM?


Non.BEM est vivant et nous continuons à prendre en charge les applications sur la pile BEM. Au fil du temps, ils passeront également à React, mais pour l'instant nous préparons la voie à cela: nous traduisons les bibliothèques de composants, formons un ensemble d'outils et d'accords et apprenons à planifier les dates de migration.

Chez BEM, notre moteur de modèle de newsletter par e-mail est implémenté. Nous préparons des lettres sur le serveur, et les limitations de la plateforme BEM décrites ci-dessus n'affectent pas cette application. Utiliser BEM pour le développer est une solution élégante et appropriée.

De plus, nos concepteurs protègent à l'aide de BEM et nous apportent parfois des composants pré-assemblés au lieu de mises en page. Et même si on arrête d'écrire sur BEM, il nous trouvera quand même :)

J'ai lu la première partie. Et les codeurs?


J'ai participé à la traduction d'une des applications de BEM vers React et compris une chose importante.

Avant de rejoindre Yandex.Money, j'étais un simple typographe et j'ai passé plus d'un an, des kilomètres de tonnes de HTML et de JSX. Je n'ai pas pris au sérieux la communauté front-end et son monde en mutation. Je ne comprenais pas pourquoi étudier le premier angulaire pour l'oublier demain et étudier le second. Je n'ai pas compris pourquoi changer jQuery.Ajax en Fetch, puis remplacer Fetch par Axios.

Il s'est avéré que lorsque vous transférez un projet d'un framework à un autre, vous ne portez pas seulement le code. Nous devons analyser et améliorer l'architecture de l'application, redresser la logique, refactoriser. Un changement constant d'outils n'est pas une tentative de surfer sur la vague de battage médiatique, mais une recherche constante de la meilleure solution qui répond aux exigences de l'époque. Un domaine en développement dynamique comme rien d'autre contribue au développement de votre produit et de votre développement professionnel, respectivement. Et le frontend est juste un tel domaine. Combattons-le ensemble!

Réagissez tout le monde!

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


All Articles