Nous sommes fans de Éléments personnalisés dans les alentours. Leur conception les rend particulièrement adapté au chargement paresseux, ce qui peut être une aubaine pour les performances.
Inspiré par celui d'un collègue expériences, j'ai récemment entrepris d'écrire un chargeur automatique simple : chaque fois qu'un élément personnalisé apparaît dans le DOM, nous voulons charger l'implémentation correspondante si elle n'est pas encore disponible. Le navigateur se charge ensuite de mettre à niveau ces éléments à partir de là.
Il y a de fortes chances que vous n'ayez pas réellement besoin de tout cela ; il y a généralement une approche plus simple. Utilisées délibérément, les techniques présentées ici peuvent toujours être un ajout utile à votre ensemble d'outils.
Pour des raisons de cohérence, nous voulons que notre chargeur automatique soit également un élément personnalisé, ce qui signifie également que nous pouvons facilement le configurer via HTML. Mais d'abord, identifions ces éléments personnalisés non résolus, étape par étape :
class AutoLoader extends HTMLElement { connectedCallback() { let scope = this.parentNode; this.discover(scope); }
}
customElements.define("ce-autoloader", AutoLoader);
En supposant que nous ayons chargé ce module à l'avance (en utilisant async
est idéal), nous pouvons déposer un <ce-autoloader>
élément dans le <body>
de notre document. Cela lancera immédiatement le processus de découverte pour tous les éléments enfants de <body>
, qui constitue désormais notre élément racine. Nous pourrions limiter la découverte à une sous-arborescence de notre document en ajoutant <ce-autoloader>
à l'élément conteneur respectif à la place - en effet, nous pourrions même avoir plusieurs instances pour différents sous-arbres.
Bien sûr, nous devons encore mettre en œuvre cela discover
méthode (dans le cadre de la AutoLoader
classe ci-dessus):
discover(scope) { let candidates = [scope, ...scope.querySelectorAll("*")]; for(let el of candidates) { let tag = el.localName; if(tag.includes("-") && !customElements.get(tag)) { this.load(tag); } }
}
Ici, nous vérifions notre élément racine avec chaque descendant (*
). S'il s'agit d'un élément personnalisé, comme indiqué par des balises avec trait d'union, mais pas encore mis à niveau, nous tenterons de charger la définition correspondante. Interroger le DOM de cette façon peut être coûteux, nous devons donc être un peu prudents. Nous pouvons alléger la charge sur le thread principal en reportant ce travail :
connectedCallback() { let scope = this.parentNode; requestIdleCallback(() => { this.discover(scope); });
}
requestIdleCallback
n'est pas encore universellement pris en charge, mais nous pouvons utiliser requestAnimationFrame
comme solution de repli :
let defer = window.requestIdleCallback || requestAnimationFrame; class AutoLoader extends HTMLElement { connectedCallback() { let scope = this.parentNode; defer(() => { this.discover(scope); }); } // ...
}
Nous pouvons maintenant passer à la mise en œuvre des éléments manquants load
méthode pour injecter dynamiquement un <script>
élément:
load(tag) { let el = document.createElement("script"); let res = new Promise((resolve, reject) => { el.addEventListener("load", ev => { resolve(null); }); el.addEventListener("error", ev => { reject(new Error("failed to locate custom-element definition")); }); }); el.src = this.elementURL(tag); document.head.appendChild(el); return res;
} elementURL(tag) { return `${this.rootDir}/${tag}.js`;
}
Notez la convention codée en dur dans elementURL
L’ src
L'URL de l'attribut suppose qu'il existe un répertoire où résident toutes les définitions d'éléments personnalisés (par exemple <my-widget>
→ /components/my-widget.js
). Nous pourrions proposer des stratégies plus élaborées, mais cela suffit pour nos besoins. Reléguer cette URL à une méthode distincte permet une sous-classe spécifique au projet si nécessaire :
class FancyLoader extends AutoLoader { elementURL(tag) { // fancy logic }
}
Quoi qu'il en soit, notez que nous comptons sur this.rootDir
. C'est là qu'intervient la configurabilité susmentionnée. Ajoutons un getter correspondant :
get rootDir() { let uri = this.getAttribute("root-dir"); if(!uri) { throw new Error("cannot auto-load custom elements: missing `root-dir`"); } if(uri.endsWith("/")) { // remove trailing slash return uri.substring(0, uri.length - 1); } return uri;
}
Vous pensez peut-être à observedAttributes
maintenant, mais cela ne facilite pas vraiment les choses. Plus mise à jour root-dir
à l'exécution semble être quelque chose dont nous n'aurons jamais besoin.
Maintenant, nous pouvons — et devons — configurer notre répertoire d'éléments : <ce-autoloader root-dir="/components">
.
Avec cela, notre chargeur automatique peut faire son travail. Sauf que cela ne fonctionne qu'une seule fois, pour les éléments qui existent déjà lorsque le chargeur automatique est initialisé. Nous voudrons probablement également tenir compte des éléments ajoutés dynamiquement. C'est là que MutationObserver
entre en jeu:
connectedCallback() { let scope = this.parentNode; defer(() => { this.discover(scope); }); let observer = this._observer = new MutationObserver(mutations => { for(let { addedNodes } of mutations) { for(let node of addedNodes) { defer(() => { this.discover(node); }); } } }); observer.observe(scope, { subtree: true, childList: true });
} disconnectedCallback() { this._observer.disconnect();
}
De cette façon, le navigateur nous avertit chaque fois qu'un nouvel élément apparaît dans le DOM - ou plutôt, notre sous-arborescence respective - que nous utilisons ensuite pour redémarrer le processus de découverte. (Vous pourriez dire que nous réinventons ici des éléments personnalisés, et vous auriez plutôt raison.)
Notre chargeur automatique est maintenant entièrement fonctionnel. Les futures améliorations pourraient examiner les conditions de concurrence potentielles et étudier les optimisations. Mais il y a de fortes chances que cela soit suffisant pour la plupart des scénarios. Faites-moi savoir dans les commentaires si vous avez une approche différente et nous pourrons comparer les notes !
- Contenu propulsé par le référencement et distribution de relations publiques. Soyez amplifié aujourd'hui.
- Platoblockchain. Intelligence métaverse Web3. Connaissance Amplifiée. Accéder ici.
- La source: https://css-tricks.com/an-approach-to-lazy-loading-custom-elements/
- 1
- a
- Description
- au dessus de
- Compte
- actually
- ajoutée
- ajout
- Tous
- alléger
- permet
- déjà
- ainsi que
- une approche
- argumenter
- autour
- disponibles
- navigateur
- candidats
- ne peut pas
- les soins
- prudent
- chances
- vérifier
- enfant
- classe
- comment
- commentaires
- comparer
- conditions
- Contenant
- Convention
- Correspondant
- pourriez
- cours
- Customiser
- Conception
- différent
- découverte
- document
- Ne fait pas
- DOM
- Goutte
- dynamiquement
- plus facilement
- même
- Élaborer
- éléments
- assez
- erreur
- Ether (ETH)
- EV
- Pourtant, la
- Chaque
- Sauf
- cher
- Échoué
- .fans
- Prénom
- de
- d’étiquettes électroniques entièrement
- fonctionnel
- avenir
- aller
- Bien
- front
- ici
- HTML
- HTTPS
- idéal
- identifier
- immédiatement
- Mettre en oeuvre
- la mise en oeuvre
- la mise en œuvre
- in
- plutôt ;
- enquêter
- IT
- JavaScript
- Emploi
- Genre
- Savoir
- Longueur
- LIMIT
- peu
- charge
- chargement
- Style
- Entrée
- faire
- FAIT DU
- veux dire
- méthode
- pourrait
- manquant
- module
- PLUS
- (en fait, presque toutes)
- Bougez
- Mozilla
- plusieurs
- Besoin
- nécessaire
- Nouveauté
- nœud
- partie
- performant
- Platon
- Intelligence des données Platon
- PlatonDonnées
- Jouez
- plus
- défaillances
- Probablement
- processus
- des fins
- Race
- récemment
- supprimez
- ceux
- retourner
- racine
- scénarios
- portée
- semble
- séparé
- set
- devrait
- montré
- étapes
- unique
- So
- quelque chose
- Commencer
- étapes
- Encore
- les stratégies
- tel
- Appareils
- TAG
- prend
- techniques
- La
- leur
- des choses
- En pensant
- à
- oui
- la mise à jour
- mis à jour
- URI
- URL
- us
- utilisé
- d'habitude
- via
- qui
- sera
- Activités:
- vos contrats
- écriture
- Votre
- zéphyrnet