Sidecar pour un fractionnement de code


Fractionnement de code. Le fractionnement de code est partout. Mais pourquoi? Tout simplement parce qu'il y a trop de javascript de nos jours, et que tous ne sont pas utilisés en même temps.


JS est une chose très lourde . Pas pour votre iPhone Xs ou tout nouvel ordinateur portable i9, mais pour des millions (probablement des milliards) de propriétaires d'appareils plus lents . Ou, au moins, pour vos montres.


Donc - JS est mauvais, mais que se passerait-il si nous le désactivions simplement - le problème disparaîtrait ... pour certains sites, et disparaîtrait "avec les sites" pour ceux basés sur React. Mais de toute façon - il y a des sites qui pourraient fonctionner sans JS ... et il y a quelque chose que nous devrions apprendre d'eux ...


Fractionnement de code


Aujourd'hui, nous avons deux façons de procéder, deux façons de l'améliorer ou de ne pas l'aggraver:


1. Écrivez moins de code


C'est la meilleure chose que vous puissiez faire. Alors que React Hooks vous permet de livrer un peu moins de code et que des solutions comme Svelte vous permettent de générer juste moins de code que d' habitude , ce n'est pas si facile à faire.


Il ne s'agit pas seulement du code, mais aussi de la fonctionnalité - pour garder le code "compact", vous devez le garder "compact". Il n'y a aucun moyen de garder un petit ensemble d'applications s'il fait tant de choses (et a été livré en 20 langues).


Il existe des moyens d'écrire du code court et sain , et il existe des moyens d'écrire l'implémentation opposée - l'entreprise sanglante . Et, vous savez, les deux sont légitimes.



Mais le principal problème - le code lui-même. Une simple application React pourrait facilement contourner 250 Ko "recommandés". Et vous pourriez passer un mois à l'optimiser et à le réduire. Les "petites" optimisations sont bien documentées et très utiles - il suffit de disposer d' bundle-analyzer avec une size-limit et de se remettre en forme.
Il existe de nombreuses bibliothèques, qui se battent pour chaque octet, essayant de vous garder dans vos limites - pré - réagir et stocker , pour n'en nommer que quelques-unes.


Mais notre application dépasse un peu 200kb. Il est plus proche de 100 Mo. Supprimer des kilo-octets n'a aucun sens. Même supprimer des mégaoctets n'a aucun sens.


Après un moment, il est impossible de garder votre application petite. Il grossira avec le temps.

2. Expédier moins de code


Alternativement, le code split . En d'autres termes - se rendre . Prenez votre paquet de 100 Mo et faites-en vingt paquets de 5 Mo. Honnêtement - c'est le seul moyen possible de gérer votre application si elle est grande - créez-en un pack de petites applications.


Mais il y a une chose que vous devez savoir dès maintenant: quelle que soit l'option que vous choisissez, c'est un détail d'implémentation, alors que nous recherchons quelque chose de plus fiable.


La vérité sur le fractionnement de code


La vérité sur le fractionnement de code est que sa nature est la SÉPARATION DU TEMPS . Vous ne divisez pas simplement votre code, vous le divisez d'une manière où vous en utiliserez le moins possible en un seul instant.


N'expédiez pas le code dont vous n'avez pas besoin pour le moment. Débarrassez-vous-en.



Facile à dire, difficile à faire. J'ai quelques applications lourdes, mais pas suffisamment réparties, où n'importe quelle page se charge comme 50% de tout. Parfois code splitting devient code separation , je veux dire - vous pouvez déplacer le code vers les différents morceaux, mais tout de même, utilisez-le tout. Rappelez-vous que " n'expédiez pas le code dont vous n'avez pas besoin en ce moment" , - j'avais besoin de 50% du code, et c'était le vrai problème.


Parfois, simplement ajouter des import ici et là n'est pas suffisant. Jusqu'à ce que ce ne soit pas une séparation temporelle , mais seulement une séparation spatiale - cela n'a pas d'importance du tout.

Il existe 3 façons courantes de coder la division:


  1. import juste dynamique. À peine utilisé seul de nos jours. Il s'agit davantage de problèmes de suivi d'un état .
  2. Composant Lazy , lorsque vous pouvez différer le rendu et le chargement d'un composant React. Probablement 90% de la "réaction au fractionnement du code" de nos jours.
  3. Lazy Library , qui est en fait .1 , mais vous recevrez un code de bibliothèque via les accessoires de rendu React. Implémenté dans les composants importés et les composants chargeables . Assez utile, mais peu connu.

Fractionnement du code au niveau des composants


Celui-ci est le plus populaire. En tant que fractionnement de code par route ou fractionnement de code par composant. Ce n'est pas si facile de le faire et de maintenir ainsi de bons résultats perceptifs . C'est la mort de Flash of Loading Content .


Les bonnes techniques sont:


  • charger le js chunk et les data pour un itinéraire en parallèle.
  • utilisez un skeleton pour afficher quelque chose de similaire à la page avant le chargement de la page (comme Facebook).
  • prefetch chunks, vous pouvez même utiliser guess-js pour une meilleure prédiction.
  • utilisez quelques retards, indicateurs de chargement, animations et Suspense (à l'avenir) pour adoucir les transitions.

Et, vous savez, c'est une question de performance perceptuelle .



Image d' UX améliorée avec Ghost Elements

Ça ne sonne pas bien


Vous savez, je pourrais m'appeler un expert en fractionnement de code - mais j'ai mes propres échecs.


Parfois, je ne pouvais pas réduire la taille du paquet. Parfois, je ne pouvais pas améliorer les performances résultantes, tant que the _more_ code splitting you are introducing - the more you spatially split your page - the more time you need to _reassemble_ your page back *. Cela s'appelle une vague de chargement .


  • sans SSR ni pré-rendu. La bonne SSR change la donne en ce moment.


La semaine dernière, j'ai eu deux échecs:


  • J'ai perdu dans une comparaison de bibliothèques , tant que ma bibliothèque était meilleure, mais BEAUCOUP plus grande qu'une autre. J'ai échoué à «1. Écrivez moins de code» .
  • optimiser un petit site, réalisé en React par ma femme. Il utilisait la division des composants basée sur l'itinéraire, mais l'en- header et le footer étaient conservés dans le bundle principal pour rendre les transitions plus "acceptables". Juste quelques trucs, étroitement couplés les uns avec les autres côté bundle monté en flèche jusqu'à 320 Ko (avant gzip). Il n'y avait rien d'important et rien que je pouvais vraiment supprimer. Une mort de mille coupures . Je n'ai pas envoyé moins de code .

React-Dom était de 20%, core-js était de 10%, react-router, jsLingui, react-powerplug ... 20% de son propre code ... Nous avons déjà terminé.


La solution


J'ai commencé à réfléchir à la façon de résoudre mon problème et aux raisons pour lesquelles les solutions communes ne fonctionnent pas correctement pour mon cas d'utilisation.


Qu'est-ce que j'ai fait? J'ai répertorié tous les endroits cruciaux, sans lesquels l'application ne fonctionnerait pas du tout, et j'ai essayé de comprendre pourquoi j'avais le reste.

Ce fut une surprise. Mais mon problème était en CSS. En transition CSS vanille.


Voici le code


  • une variable de contrôle - componentControl , serait éventuellement définie sur quelque chose que DisplayData devrait afficher.
  • une fois la valeur définie - DisplayData devient visible, modifiant className , déclenchant ainsi une transition fantaisiste. Simultaneusly FocusLock devient actif faisant de DisplayData un modal .
     <FocusLock enabled={componentControl.value} // ^ it's "disabled". When it's disabled - it's dead. > {componentControl.value && <PageTitle title={componentControl.value.title}/>} // ^ it's does not exists. Also dead <DisplayData data={componentControl.value} visible={componentControl.value !== null} // ^ would change a className basing on visible state /> // ^ that is just not visible, but NOT dead </FocusLock> 

Je voudrais coder fractionner cette pièce dans son ensemble, mais c'est quelque chose que je ne pouvais pas faire, pour deux raisons:


  1. l'information doit être visible immédiatement, une fois requise, sans délai. Une exigence commerciale.
  2. les informations "chrome" doivent exister avant, pour gérer la transition de la propriété.

Ce problème pourrait être partiellement résolu en utilisant CSSTransitionGroup ou reconditionné . Mais, vous savez, corriger un code en ajoutant un autre code semble bizarre, même si cela suffit . Je veux dire que l'ajout de plus de code pourrait aider à supprimer encore plus de code. Mais ... mais ...


Il devrait y avoir une meilleure façon!

TL; DR - il y a deux points clés ici:


  • DisplayData doit être monté et existe dans le DOM avant.
  • FocusLock devrait également exister avant, pour ne pas provoquer de DisplayData , mais ses cerveaux ne sont pas nécessaires au début.



Alors changeons notre modèle mental


Batman et Robin


Supposons que notre code soit Batman et Robin. Batman peut gérer la plupart des méchants, mais quand il ne le peut pas, son acolyte Robin vient à la rescousse ..


Batman s'engageait à nouveau dans la bataille, Robin arrivera plus tard.

Voici Batman:


 +<FocusLock - enabled={componentControl.value} +> - {componentControl.value && <PageTitle title={componentControl.value.title}/>} + <DisplayData + data={componentControl.value} + visible={componentControl.value !== null} + /> +</FocusLock> 

Voici son acolyte, Robin ::


 -<FocusLock + enabled={componentControl.value} -> + {componentControl.value && <PageTitle title={componentControl.value.title}/>} - <DisplayData - data={componentControl.value} - visible={componentControl.value !== null} - /> -</FocusLock> 

Batman et Robin pourraient former une ÉQUIPE , mais ils sont en réalité deux personnes différentes.


Et n'oubliez pas - nous parlons toujours de fractionnement de code . Et, en termes de fractionnement de code, où est l'acolyte? Où est Robin?



dans un side-car. Robin attend dans un morceau de side - car .

Sidecar


  • Batman est ici tout ce que votre client doit voir le plus tôt possible. Idéalement instantanément.
  • Robin est ici toute logique et fonctionnalités interactives de fantaisie, qui peuvent être disponibles une seconde après, mais pas au tout début.

Il serait préférable d'appeler cela un fractionnement de code vertical où les branches de code existent en parallèle, contrairement à un fractionnement de code horizontal commun où les branches de code sont coupées .


Dans certains pays , ce trio était connu comme replace reducer ou d'autres façons de charger la logique de redux paresseux et les effets secondaires.


Dans certains autres pays , il est connu sous le nom de "3 Phased" code splitting .


C'est juste une autre séparation des préoccupations, applicable uniquement aux cas, où vous pouvez différer le chargement d'une partie d'un composant, mais pas d'une autre partie.

phase 3


image de Building the New facebook.com avec React, GraphQL et Relay , où importForInteractions ou importAfter sont le sidecar .

Et il y a une observation intéressante - alors que Batman est plus précieux pour un client, tant que c'est quelque chose que le client peut voir , il est toujours en forme ... Alors que Robin , vous savez, il pourrait être un peu en surpoids et nécessiter beaucoup plus d'octets pour vivant.


En conséquence - Batman seul est quelque chose de bien supportable pour un client - il offre plus de valeur à moindre coût. Tu es mon héros Bat!


Ce qui pourrait être déplacé vers un side-car:


  • majorité de useEffect , componentDidMount et amis.
  • comme tous les effets Modal . C'est à dire les verrous de focus et de scroll . Vous pouvez d'abord afficher un modal, puis seulement faire du modal modal , c'est-à-dire "verrouiller" l'attention du client.
  • Formulaires Déplacez toutes les logiques et validations vers un side-car et bloquez la soumission du formulaire jusqu'à ce que cette logique soit chargée. Le client pourrait commencer à remplir le formulaire, ne sachant pas que ce n'est que Batman .
  • Quelques animations. Tout un react-spring dans mon cas.
  • Quelques trucs visuels. Comme les barres de défilement personnalisées , qui peuvent afficher des barres de défilement sophistiquées une seconde plus tard.

N'oubliez pas non plus - Chaque morceau de code, déchargé sur un sidecar, décharge également des éléments comme les poly-core et ponyfills core-js, utilisés par le code supprimé.


Le fractionnement de code peut être plus intelligent qu'il ne l'est aujourd'hui dans nos applications. Nous devons réaliser qu'il y a 2 types de code à diviser: 1) les aspects visuels 2) les aspects interactifs. Ce dernier peut arriver quelques instants plus tard. Sidecar facilite la séparation des deux tâches, donnant l' impression que tout se charge plus rapidement . Et ce sera le cas.


La façon la plus ancienne de coder la division


Bien qu'il ne soit toujours pas très clair quand et ce qu'est un sidecar - sidecar , je vais donner une explication simple:


Sidecar est TOUS VOS SCRIPTS . Le Sidecar est la façon dont nous codons avant tout ce que nous avons eu aujourd'hui.

Je parle du rendu côté serveur ( SSR ), ou simplement du HTML , auquel nous étions tous habitués hier. Sidecar rend les choses aussi faciles qu'elles l'étaient lorsque les pages contenaient du HTML et de la logique vécues séparément dans des scripts externes intégrables (séparation des préoccupations).


Nous avions HTML, plus CSS, plus certains scripts en ligne, plus le reste des scripts extraits dans un fichier .js .


HTML + CSS + inlined-js étaient Batman , tandis que les scripts externes étaient Robin , et le site a pu fonctionner sans Robin, et, honnêtement, partiellement sans Batman (il continuera le combat avec les deux jambes (scripts intégrés) cassés). C'était hier, et de nombreux sites "non modernes et sympas" sont les mêmes aujourd'hui.




Si votre application prend en charge SSR - essayez de désactiver js et de le faire fonctionner sans lui. Ensuite, il serait clair ce qui pourrait être déplacé vers un side-car.
Si votre application est un SPA côté client uniquement - essayez d'imaginer comment cela fonctionnerait, si la SSR existait.


Par exemple - theurge.com , écrit en React, est entièrement fonctionnel sans aucun js activé .

Il y a beaucoup de choses que vous pouvez décharger sur un side-car. Par exemple:


  • commentaires. Vous pouvez envoyer du code pour display commentaires, mais pas answer , tant qu'il peut nécessiter plus de code (y compris l'éditeur WYSIWYG), ce qui n'est pas nécessaire au départ. Il est préférable de retarder une zone de commentaires , ou même de masquer le chargement du code derrière l'animation, plutôt que de retarder une page entière.
  • lecteur vidéo. Expédiez la "vidéo" sans "commandes". Chargez-les une seconde plus tard, le client pourrait essayer d'interagir avec lui.
  • galerie d'images, comme slick . Ce n'est pas un gros problème pour le dessiner , mais beaucoup plus difficile à animer et à gérer. Il est clair ce qui pourrait être déplacé vers un side-car.

Pensez simplement à ce qui est essentiel pour votre application et à ce qui ne l'est pas tout à fait ...

Détails d'implémentation


(DI) Fractionnement du code composant


La forme la plus simple de sidecar est facile à implémenter - déplacez simplement tout vers un sous-composant, vous pouvez coder le fractionnement en utilisant une "ancienne" méthode. C'est presque une séparation entre les composants Smart et Dumb, mais cette fois, Smart ne contient pas un composant Dumb - c'est l'opposé.


 const SmartComponent = React.lazy( () => import('./SmartComponent')); class DumbComponent extends React.Component { render() { return ( <React.Fragment> <SmartComponent ref={this} /> // <-- move smart one inside <TheActualMarkup /> // <-- the "real" stuff is here </React.Fragment> } } 

Cela nécessite également de déplacer le code d' initialisation vers un code muet, mais vous pouvez toujours diviser en code la partie la plus lourde d'un code.


Pouvez-vous voir un modèle de fractionnement de code parallel ou vertical maintenant?

useSidecar


Construire le nouveau facebook.com avec React, GraphQL et Relay , je l'ai déjà mentionné ici, avait un concept de loadAfter ou importForInteractivity , qui est assez similaire au concept de sidecar.


Dans le même temps, je ne recommanderais pas de créer quelque chose comme useSidecar tant que vous pourriez intentionnellement essayer d'utiliser des hooks intérieur, mais le fractionnement de code sous cette forme violerait la règle des hooks .


Veuillez préférer une méthode de composants plus déclarative. Et vous pouvez utiliser des hooks intérieur SideCar composant SideCar .


 const Controller = React.lazy( () => import('./Controller')); const DumbComponent = () => { const ref = useRef(); const state = useState(); return ( <> <Controller componentRef={ref} state={state} /> <TheRealStuff ref={ref} state={state[0]} /> </> ) } 

Prélecture


N'oubliez pas - vous pouvez utiliser des indications de priorité de chargement pour précharger ou précharger le sidecar - sidecar et le rendre plus transparent et invisible.


Choses importantes - la prélecture des scripts la chargerait via le réseau , mais ne s'exécuterait pas (et ne dépenserait pas de CPU) à moins que cela ne soit réellement nécessaire.


SSR


Contrairement au fractionnement de code normal , aucune action spéciale n'est requise pour SSR. Sidecar peut ne pas faire partie du processus SSR et n'est pas requis avant l'étape d' hydration . Il pourrait être reporté "par conception".


Ainsi - n'hésitez pas à utiliser React.lazy (idéalement quelque chose sans Suspense , vous n'avez pas besoin d'indicateurs de rétablissement (chargement) ici), ou toute autre bibliothèque, avec, mais mieux sans le support SSR pour ignorer les morceaux de side-car pendant le processus SSR.


Les mauvaises parties


Mais il y a quelques mauvaises parties de cette idée


Batman n'est pas un nom de production


Alors que Batman / Robin pourrait être un bon concept d'esprit, et que le sidecar - sidecar est parfaitement adapté à la technologie elle-même - il n'y a pas de "bon" nom pour la maincar . Il n'y a rien de tel qu'une maincar , et évidemment Batman , Lonely Wolf , Solitude , Driver et Solo ne doivent pas être utilisés pour nommer une partie autre qu'un side-car.


Facebook a utilisé l' display et l' interactivity , et cela pourrait être la meilleure option pour nous tous.


Si vous avez un bon nom pour moi - laissez-le dans les commentaires

Tremblement d'arbre


Il s'agit davantage de la séparation des préoccupations du point de vue du bundler . Imaginons que vous ayez Batman et Robin . Et stuff.js


  • stuff.js
     export * from `./batman.js` export * from `./robin.js` 

Ensuite, vous pouvez essayer de fractionner le code du composant pour implémenter un side-car


  • main.js


     import {batman} from './stuff.js' const Robin = React.lazy( () => import('./sidecar.js')); export const Component = () => ( <> <Robin /> // sidecar <Batman /> // main content </> ) 

  • sidecar.js


     // and sidecar.js... that's another chunk as long as we `import` it import {robin} from './stuff.js' ..... 


En bref - le code ci-dessus fonctionnerait, mais ne fera pas "le travail".


  • si vous utilisez uniquement batman de stuff.js - le tremblement d'arbre ne le conservera que.
  • si vous utilisez uniquement robin de stuff.js - le tremblement d'arbre ne le conservera que.
  • mais si vous utilisez les deux, même dans des morceaux différents - les deux seront regroupés dans une première occurrence de stuff.js , c'est-à-dire le regroupement principal .

Le tremblement d'arbre n'est pas convivial pour le fractionnement de code. Vous devez séparer les préoccupations par fichiers.

Désimporter


Une autre chose, oubliée par tout le monde, est le coût du javascript. Il était assez courant à l'ère jQuery, l'ère de la charge utile jsonp de charger le script (avec la charge utile json ), d'obtenir la charge utile et de supprimer le script.


De nos jours, nous import tous du script, et il sera importé pour toujours, même s'il n'est plus nécessaire.

Comme je l'ai déjà dit - il y a trop de JS, et tôt ou tard, avec une navigation continue, vous allez tout charger. Nous devons trouver un moyen de désimporter sans avoir besoin de morceaux, en effaçant tous les caches internes et en libérant de la mémoire pour rendre le Web plus fiable, et ne pas écraser les applications avec des exceptions de mémoire insuffisante.


La possibilité de désimporter (le webpack pourrait le faire ) est probablement l'une des raisons pour lesquelles nous devrions nous en tenir à l'API basée sur les composants , à condition qu'elle nous donne la possibilité de gérer le unmount .


Jusqu'à présent - les normes des modules ESM n'ont rien à voir avec des choses comme ça - ni sur le contrôle du cache, ni sur l'inversion de l'action d'importation.


Création d'une bibliothèque compatible side-car


À l'heure actuelle, il n'y a qu'une seule façon de créer une bibliothèque compatible avec les sidecar cars:


  • divisez votre composant en parties
  • exposer une partie main et une partie connected (pour ne pas casser l'API) via un index
  • exposer un sidecar - sidecar via un point d'entrée séparé.
  • dans le code cible - importez la partie main et le sidecar - la secousse de l'arbre devrait couper une partie connected .

Cette fois, le tremblement de l'arbre devrait fonctionner correctement, et le seul problème - est de savoir comment nommer la partie main .


  • main.js

 export const Main = ({sidecar, ...props}) => ( <div> {sidecar} .... </div> ); 

  • connected.js

 import Main from './Component'; import Sidecar from './Sidecar'; export const Connected = props => ( <Main sidecar={<Sidecar />} {...props} /> ); 

  • index.js

 export * from './Main'; export * from './Connected'; 

  • sidecar.js

 import * from './Sidecar'; 

En bref, le changement pourrait être représenté par une petite comparaison


 //your app BEFORE import {Connected} from 'library'; // // ------------------------- //your app AFTER, compare this core to `connected.js` import {Main} from 'library'; const Sidecar = React.lazy(import( () => import('library/sidecar'))); // ^ all the difference ^ export SideConnected = props => ( <Main sidecar={<Sidecar />} {...props} /> ); // ^ you will load only Main, Sidecar will arrive later. 

L' dynamic import théoriquement dynamic import pourrait être utilisée à l'intérieur de node_modules, rendant le processus d'assemblage plus transparent.


Quoi qu'il en soit - ce n'est rien de plus que le modèle children / slot , si commun dans React.

Le futur


Facebook prouvé que l'idée était bonne. Si vous n'avez pas vu cette vidéo, faites-le maintenant. Je viens d'expliquer la même idée sous un angle un peu différent (et j'ai commencé à écrire cet article une semaine avant la conférence F8).


À l'heure actuelle, il nécessite que certains changements de code soient appliqués à votre base de code. Il faut une séparation plus explicite des préoccupations pour les séparer réellement, et laisser les codesplit non pas horizontalement, mais verticalement, expédiant moins de code pour une plus grande expérience utilisateur.


Sidecar , probablement, est le seul moyen, à l'exception du SSR old school, de gérer les bases de code BIG. Dernière chance d'envoyer une quantité minimale de code, lorsque vous en avez beaucoup.


Cela pourrait rendre une grande application plus petite et une petite application encore plus petite.

Il y a 10 ans, le site Web moyen était "prêt" en 300 ms, et était vraiment prêt quelques millisecondes après. Aujourd'hui, les secondes et même plus de 10 secondes sont les nombres communs. Quelle honte.


Prenons une pause et réfléchissons à la façon dont nous pourrions résoudre le problème et rendre à nouveau UX génial ...



Global


  • Le fractionnement du code des composants est un outil très puissant, vous donnant la possibilité de fractionner complètement quelque chose, mais il a un coût - vous n'aurez peut-être rien à afficher, sauf une page vierge ou un squelette pendant un certain temps. C'est une séparation horizontale.
  • La division du code de la bibliothèque pourrait aider lorsque la séparation des composants ne le serait pas. C'est une séparation horizontale.
  • Le code, déchargé sur un sidecar compléterait l'image et pourrait vous permettre de fournir une bien meilleure expérience utilisateur. Mais cela nécessiterait également des efforts d'ingénierie. C'est une séparation verticale.

Ayons une conversation à ce sujet .


Arrête! Qu'en est-il des problèmes que vous avez essayé de résoudre?


Eh bien, ce n'était que la première partie. Nous sommes maintenant dans la phase finale , il faudrait encore quelques semaines pour rédiger la deuxième partie de cette proposition. Pendant ce temps ... montez dans le side-car!

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


All Articles