Fermer

mai 6, 2021

Amélioration des performances des thèmes Shopify (étude de cas)


À propos de l'auteur

Carson est le co-fondateur de Archetype Themes et se concentre sur la création de la meilleure expérience utilisateur dans le commerce électronique.
En savoir plus sur
Carson

Lorsqu'il s'agit de thèmes pour de grandes plates-formes et CMS, les problèmes hérités deviennent souvent un goulot d'étranglement. Dans cet article, Carson Shold explique comment son équipe a amélioré les performances et l'organisation de leurs thèmes Shopify, et amélioré la maintenabilité en cours de route.

Le redoutable refactor de l'ancien code peut être difficile. Le code évolue au fil du temps avec plus de fonctionnalités, des dépendances nouvelles ou changeantes, ou peut-être un objectif d'amélioration des performances. Lorsque vous vous attaquez à un gros refactor, sur quels éléments devez-vous vous concentrer et à quelles améliorations de performances pouvez-vous vous attendre?

Je crée des thèmes Shopify depuis près d'une décennie. Lorsque j'ai travaillé en interne chez Shopify en 2013, les thèmes étaient assez simples en termes de complexité du code. Le plus dur était que Shopify avait besoin de thèmes pour prendre en charge IE8 et jusqu'à fin 2020, IE11. Cela signifiait qu'il y avait beaucoup de JavaScript moderne que nous ne pouvions pas utiliser sans des polyfills parfois importants.

Huit ans plus tard, en 2021, les thèmes sont infiniment plus complexes car Shopify a publié une tonne de nouvelles fonctionnalités (pour aller avec notre in -des idées de maison à Archetype Themes ). Le problème est que la création de nouvelles fonctionnalités performantes n'ira aussi loin que si une partie de votre base de code est si ancienne qu'elle contient d'anciens polyfills IE ou des hacks CSS IE10. Nos thèmes avaient des scores de vitesse assez bons pour ce qu'ils offraient, mais ils étaient sans aucun doute gonflés.

Notre objectif était simple

De meilleures performances à tous les niveaux. Temps plus rapide pour la première peinture. Moins de blocage JS. Moins de complexité de code.

Y arriver était la partie la plus difficile. Il comprenait:

  • Supprimer jQuery et réécrire ~ 6k lignes de JS par thème dans Vanilla JS
  • Supprimer Handlebars.js, car nos besoins en matière de modèles étaient bien trop petits pour un paquet aussi volumineux
  • Standardisation du code partagé entre les thèmes ( supprimer la duplication)

S'éloigner de jQuery était une bénédiction, mais un long processus. Heureusement, Tobias Ahlin a un guide fantastique sur certaines des conversions rapides loin de jQuery . En passant par ces changements, c'était le moment idéal pour repenser certains problèmes plus basiques comme la structure de mon JS et la manière dont les éléments étaient initialisés.

Remove jQuery

Ecrire Vanilla JS a toujours semblé être une chimère. Nous devions prendre en charge l'ancien IE, il était donc si facile d'ignorer toute tentative de le supprimer. Ensuite, le support IE 11 a été abandonné par Shopify et les nuages ​​se sont séparés – c'était notre temps.

Pourquoi supprimer jQuery de toute façon? J’ai entendu beaucoup d’arguments à ce sujet, tels que la taille de son package n’est pas si mauvaise par rapport à un framework comme React. Eh bien, jQuery n’est pas un framework comme React, donc c’est un peu une comparaison non-démarrante. jQuery est un moyen d'utiliser des sélecteurs de type CSS et une syntaxe conviviale pour les développeurs pour des éléments tels que les animations et les requêtes Ajax. Surtout, il a aidé avec les différences entre les navigateurs afin que les développeurs n'aient pas à y penser.

Nous voulions le supprimer pour plusieurs raisons:

Je suis l'un de ces développeurs qui étaient coincés dans le passé. Je connaissais jQuery de fond en comble et je pouvais le faire réussir à peu près tout ce que j'avais essayé. Était-ce parfait? Non bien sûr que non. Mais quand vous regardez le cycle de vie de certains frameworks JS qui ont flambé jQuery a toujours été stable et cela m'était familier et sûr. Supprimer notre dépendance et le démêler d'environ 6 000 lignes de code (pour chaque thème) me semblait insurmontable – surtout quand je ne pouvais pas savoir avec certitude que mes scores de performance en bénéficieraient ou de combien.

Notre approche consistait à commenter chaque module que nous avions, supprimez jQuery et ajoutez lentement chaque module ou fonction un par un pendant qu'il a été réécrit. Nous avons commencé avec le fichier le plus simple, un avec quelques fonctions et quelques sélecteurs. Agréable et facile, aucune erreur dans les outils de développement, il est temps de passer à autre chose.

Nous l'avons fait un par un, en nous souvenant des correctifs faciles des premiers fichiers lorsque nous en sommes arrivés aux plus complexes comme la refactorisation de toutes les fonctionnalités potentielles associées à un produit et sa forme d'ajout au panier (j'ai compté, c'est 24 choses uniques). En fin de compte, nous avons obtenu le produit JS de 1600 lignes de code à 1000. En cours de route, nous avons trouvé de meilleures façons de faire certaines choses et nous reviendrions et refactoriserons si nécessaire.

Nous avons réalisé que Vanilla JS n'était pas effrayant, c'est juste un peu plus une manière intentionnelle d'écrire du code que jQuery. Nous avons également réalisé qu'un code ancien était un désordre – nous devions organiser le JS pour qu'il soit plus modulaire et supprimer le code en double (plus à ce sujet ci-dessous). Mais avant cela, nous voulions jouer avec certains des JS amusants que nous n'avions utilisés que dans d'autres projets.

API Intersection Observer

Les thèmes Shopify sont puissants en ce sens qu'ils permettent aux marchands de déplacer des éléments sur la page comme ils le souhaitent. Cela signifie qu'en tant que développeur, vous ne savez pas où se trouve l'élément, s'il existe ou combien il en existe.

Pour initialiser ces éléments, nous avions utilisé des événements de défilement qui vérifiaient en permanence si un élément était visible sur le page avec cette fonction:

 theme.isElementVisible = function ($ el, seuil) {
  var rect = $ el [0] .getBoundingClientRect ();
  var windowHeight = window.innerHeight || document.documentElement.clientHeight;
  seuil = seuil? seuil: 0;

  // Si offsetParent est nul, cela signifie que l'élément est entièrement masqué
  if ($ el [0] .offsetParent === null) {
    retourne faux;
  }

  revenir (
    rect.bottom> = (0 - (seuil / 1,5)) &&
    rect.right> = 0 &&
    rect.top <= (windowHeight + seuil) &&
    rect.left <= (window.innerWidth || document.documentElement.clientWidth)
  );
};

Même si ces événements de défilement étaient limités, le navigateur faisait constamment beaucoup de calculs. Cela n'a jamais vraiment semblé trop lent, mais il a pris une place dans la pile d'appels ce qui a eu un impact sur d'autres JS en compétition pour la priorité. J'aurais aimé faire plus de recherche sur les performances de cette mise à jour, en particulier parce que je pense qu'elle est responsable de nombreuses améliorations dans Time to interactive et Total blocking time que vous verrez ci-dessous. [19659006] En vient le API Intersection Observer . Maintenant que le support IE11 n’était plus nécessaire, j’étais ravi de pouvoir l’utiliser pleinement. En bref, c'est un moyen asynchrone de savoir quand un élément est visible dans la fenêtre. Fini les mesures lentes et les événements de défilement.

Pour initialiser un élément lorsqu'il est visible, nous utilisons quelque chose d'aussi simple que ceci:

 theme.initWhenVisible ({
  élément: document.querySelector ('div'),
  rappel: myCallback
});

Tous les JS requis pour l'élément seront traités à l'intérieur de myCallback l'empêchant de faire quoi que ce soit jusqu'à ce qu'il soit visible.

Cela configure un observateur pour cet élément, puis supprime l'observateur une fois c'est visible. Il est toujours bon de nettoyer après vous-même, même si vous pensez qu'il n'y aura pas beaucoup d'impact sans cela. S'il y a un rappel, nous l'exécutons et notre module est prêt à fonctionner.

 theme.initWhenVisible = function (options) {
  var seuil = options.threshold? options.seuil: 0;

  var observer = new IntersectionObserver ((entrées, observateur) => {
    entries.forEach (entrée => {
      if (entry.isIntersecting) {
        if (typeof options.callback === 'fonction') {
          options.callback ();
          observer.unobserve (entrée.target);
        }
      }
    });
  }, {rootMargin: '0px 0px' + seuil + 'px 0px'});

  observer.observe (options.element);
};

Vous pouvez passer un seuil pour initialiser l'élément avant qu'il ne s'affiche également à l'écran, ce qui peut être pratique si vous souhaitez précharger quelque chose comme l'API Map de Google un peu avant que l'élément ne soit visible afin qu'il soit prêt quand il

Layzloading Images Et object-fit

Nous utilisons lazysizes pour le chargement paresseux de nos images. Il contient des plugins utiles pour charger également des images d'arrière-plan, mais nécessite beaucoup plus de balisage sur votre élément. Bien que les plugins soient assez petits, c'est encore une chose qui peut être facilement supprimée avec du CSS pur.

L'utilisation de object-fit en CSS signifiait que nous pouvions positionner une image comme une image d'arrière-plan, mais comme un [19659040] et bénéficiez de tous les avantages du chargement paresseux normal sans JS supplémentaire. Le véritable avantage est que nous sommes sur le point d'utiliser le chargement paresseux du navigateur natif (qui ne prend pas en charge les images d'arrière-plan). Nous devrons toujours charger des lazysizes comme solution de secours lorsque l'approche native n'est pas prise en charge mais cela signifie supprimer une dépendance entière.

  

API MatchMedia

Dans le passé, nous utilisions enquire.js pour savoir quand les points d'arrêt changeaient. Ceci est utilisé lors du redimensionnement des éléments, de la modification des arguments d'un module pour le bureau par rapport au mobile, ou simplement pour afficher / masquer des éléments que vous ne pouvez pas avec CSS.

Au lieu de compter sur un autre package, nous pouvons une fois de plus opter pour une solution native dans matchMedia .

 var query = 'screen and (max-width: 769px)';
var isSmall = matchMedia (requête) .matches;

matchMedia (requête) .addListener (fonction (mql) {
    if (mql.matches) {
      isSmall = vrai;
      document.dispatchEvent (new CustomEvent ('matchSmall'));
    }
    autre {
      isSmall = vrai;
      document.dispatchEvent (new CustomEvent ('unmatchSmall'));
    }
  });

Avec seulement quelques lignes de code, nous pouvons écouter les changements de point d'arrêt et changer une variable utile qui est utilisée ailleurs et déclencher un événement personnalisé que des modules spécifiques peuvent écouter.

 document.addEventListener ('matchSmall', function ( ) {
  // détruit les fonctionnalités uniquement pour le bureau
  // initialiser JS adapté aux mobiles
});

Chasse au code en double

Comme je l'ai mentionné au début, nous avions lentement intégré des fonctionnalités dans nos thèmes pendant des années. Il n'a pas fallu longtemps pour créer des éléments qui ressemblaient à d'autres, comme une vidéo de page d'accueil pleine largeur et des vidéos ultérieures sur votre liste de produits ou une vidéo modale contextuelle.

L'API de YouTube, par exemple, a été initialisée différemment trois fois et avait des rappels et des fonctionnalités d'accessibilité presque identiques construits par module. C'était un peu gênant que nous ne l'ayons pas construit plus intelligemment au départ, mais c'est ainsi que vous savez que vous évoluez en tant que développeur.

Nous avons pris ce temps pour consolider un grand nombre de nos modules afin qu'ils deviennent des assistants autonomes. YouTube est devenu sa propre méthode que toutes les sections de tous nos thèmes pouvaient utiliser. Cela signifiait la refactorisation en la décomposant en parties les plus élémentaires:

  • Arguments API par défaut (remplaçables par le module d'initialisation)
  • Un ID div pour initialiser la vidéo sur
  • ID de la vidéo YouTube à charger
  • Événements (l'API est prête, l'état de la vidéo a changé, etc.)
  • Lecture / pause lorsqu'elle n'est pas visible
  • Gérer le mode basse consommation iOS lorsque la lecture automatique n'est pas prise en charge

Mon approche consistait à tout faire sur papier avant le codage, ce qui est quelque chose qui m'aide toujours à trier ce qui fait partie intégrante du module que je construis par rapport à ce qui est personnalisé par le parent qui l'initialise – une division du travail si vous voulez.

Maintenant, nos trois thèmes qui initialisent les vidéos YouTube, un total de neuf différentes manières d'utiliser un seul fichier. C'est une grande complexité du code qui gagne pour nous et qui facilite grandement les futures mises à jour pour moi et pour les autres développeurs susceptibles de toucher au code. En utilisant cette même approche pour d'autres modules lors de la conversion vers Vanilla JS, cela nous a permis de déplacer près de la moitié du JS de chaque thème vers un seul module partagé sur tous.

C'est quelque chose qui a été inestimable pour notre équipe et notre multi- configuration du projet et peut ne pas être utile à vos projets exactement, mais je pense que le processus l'est. Penser la simplicité et éviter la duplication profitera toujours à votre projet.

Nous avons fait de même pour les modules de diaporama (diaporamas d'images, témoignages, images de pages produits, barres d'annonces), les tiroirs et les modaux (menus mobiles, tiroirs de chariot, popups de newsletter), et beaucoup plus. Un module a un seul objectif et ne partagera avec le parent que ce qui est requis. Cela signifiait moins de code expédié et un code plus propre à développer.

Statistiques de performances

Enfin, les bonnes choses. Tout cela en valait-il la peine? La plupart de cela a été fait aveuglément avec l'hypothèse que moins de JS, une initialisation plus intelligente et des approches plus modernes entraîneraient des thèmes plus rapides. Nous n'avons pas été déçus.

Nous avons commencé tout ce travail avec Motion notre premier thème. Il avait le JS le plus gonflé et la plus grande marge d'amélioration.

  • 52% de JS en moins
  • Vitesses de la page d'accueil du bureau (avec des éléments lourds comme plusieurs vidéos, des produits en vedette, des diaporamas avec de grandes images)
Page d'accueil du bureau Avant Après Changement
Score phare 57 76 +33
Temps total de blocage 310ms 50ms -83,8%
Temps d'interactivité 2,4 s 2,0 s -16%
La plus grande peinture contenant du contenu 3,8 s 2,6 s -31,5%
Page produit mobile Avant Après Changement
Score phare 26 65 + 150%
Durée totale de blocage 1440ms 310ms -78 %
Temps pour interactif 11,3 s 6,1 s -46%
La plus grande peinture pleine de contenu 13 s 4,2 s -67,6%

Puis nous avons déménagé à Impulse , notre deuxième thème, le plus riche en fonctionnalités.

  • 40% de JS en moins
  • Vitesse de page d'accueil mobile 28% plus rapide
Page d'accueil du bureau Avant Après Changement
Phare score 58 81 + 39,6%
Temps de blocage total 470 ms 290 ms -38%
Temps d'interactivité 6,1 s 5,6 s -8%
La peinture la plus riche en contenu 6s 2.9s -51.6%
  • Vitesse de la page d'accueil mobile et de la page produit 30% plus rapide
Page produit mobile Avant Après Changement
Score phare 32 45 + 40,6%
Durée totale de blocage 1490 ms 780 ms -47,6%
] Temps d’interactivité 10,1 s 8,3 s -17,8%
La plus grande peinture pleine de contenu 10,4 s 8,6 s -17,3%

Bien que vous puissiez les remarquer les chiffres se sont beaucoup améliorés, ils ne sont toujours pas excellents. Les thèmes Shopify sont menottés par la plate-forme, donc notre point de départ est déjà difficile. Cela pourrait être un article entièrement distinct, mais voici un aperçu:

  • Shopify a beaucoup de frais généraux : détection de fonctionnalités, suivi et boutons de paiement (Apple Pay, Google Pay, ShopPay). Si vous êtes sur une page de produit avec des boutons de paiement dynamiques, vous pouvez consulter environ 187 Ko de scripts Shopify contre 24,5 Ko de fichiers de thème. La plupart des sites auront Google Analytics, et peut-être un pixel Facebook ou d'autres scripts de suivi chargés en plus de tout cela.
( Grand aperçu )

La bonne nouvelle est que ces scripts sont chargés assez efficacement et la plupart ne bloquent pas beaucoup le rendu de la page. La mauvaise nouvelle est qu'il y a encore beaucoup de chargement de JavaScript sur les pages qui sont hors du contrôle du thème et provoquent des indicateurs sur les partitions de Lighthouse.

( Grand aperçu )
  • Les applications sont énormes goulot d'étranglement et les propriétaires de magasins, en général, n'en ont aucune idée. Nous voyons régulièrement des magasins avec plus de 20 applications installées, et même une simple application peut faire baisser votre score de vitesse Shopify de plus de 10 points. Voici la répartition de notre thème Impulse avec trois applications installées.
( Grand aperçu )

Voici une excellente étude de cas sur les applications et leur effet sur les performances .

Nous sommes toujours en train de terminer ces mises à jour de notre troisième thème, Streamline . Streamline a également d'autres fonctionnalités de performance intégrées que nous explorons en ajoutant à nos autres thèmes, comme loadCSS by Filament Group pour empêcher le CSS d'être une ressource bloquant le rendu.

Ces nombres ne sont pas disponibles. C'est pas insignifiant. Il est largement rapporté que la vitesse compte et même de petits changements peuvent avoir un impact important . Alors que nous sommes satisfaits de tous ces progrès, ce n’est pas la fin. Les performances continueront d'être une partie dominante de nos versions et nous ne cesserons pas de chercher d'autres moyens de simplifier le code.

Et maintenant?

La performance est un défi permanent, celui que nous sommes excité de continuer à avancer. Voici quelques éléments de notre liste:

Ressources pour les développeurs Shopify

Si vous construisez sur Shopify ou souhaitez commencer, voici quelques ressources utiles pour vous:

 Smashing Editorial "width =" 35 "height =" 46 "loading =" lazy "decoding =" async (vf, il)




Source link