Siamo fan di Elementi personalizzati qui intorno. Il loro design li rende particolarmente suscettibile di carico pigro, che può essere un vantaggio per le prestazioni.
Ispirato da di un collega esperimenti, di recente ho iniziato a scrivere un semplice caricatore automatico: ogni volta che un elemento personalizzato appare nel DOM, vogliamo caricare l'implementazione corrispondente se non è ancora disponibile. Il browser si occupa quindi di aggiornare tali elementi da lì in poi.
È probabile che non avrai davvero bisogno di tutto questo; di solito c'è un approccio più semplice. Usate deliberatamente, le tecniche mostrate qui potrebbero ancora essere un'utile aggiunta al tuo set di strumenti.
Per coerenza, vogliamo che anche il nostro caricatore automatico sia un elemento personalizzato, il che significa anche che possiamo configurarlo facilmente tramite HTML. Ma prima, identifichiamo quegli elementi personalizzati irrisolti, passo dopo passo:
class AutoLoader extends HTMLElement { connectedCallback() { let scope = this.parentNode; this.discover(scope); }
}
customElements.define("ce-autoloader", AutoLoader);
Supponendo di aver caricato questo modulo in anticipo (usando async
è l'ideale), possiamo eliminare a <ce-autoloader>
elemento nella <body>
del nostro documento. Ciò avvierà immediatamente il processo di rilevamento per tutti gli elementi figlio di <body>
, che ora costituisce il nostro elemento radice. Potremmo limitare la scoperta a un sottoalbero del nostro documento aggiungendo <ce-autoloader>
al rispettivo elemento contenitore invece - anzi, potremmo anche avere più istanze per diversi sottoalberi.
Certo, dobbiamo ancora implementarlo discover
metodo (come parte del AutoLoader
classe sopra):
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); } }
}
Qui controlliamo il nostro elemento radice insieme a ogni singolo discendente (*
). Se si tratta di un elemento personalizzato, come indicato dai tag con trattino, ma non ancora aggiornato, tenteremo di caricare la definizione corrispondente. Interrogare il DOM in questo modo potrebbe essere costoso, quindi dovremmo stare un po' attenti. Possiamo alleviare il carico sul thread principale rinviando questo lavoro:
connectedCallback() { let scope = this.parentNode; requestIdleCallback(() => { this.discover(scope); });
}
requestIdleCallback
non è ancora universalmente supportato, ma possiamo usarlo requestAnimationFrame
come ripiego:
let defer = window.requestIdleCallback || requestAnimationFrame; class AutoLoader extends HTMLElement { connectedCallback() { let scope = this.parentNode; defer(() => { this.discover(scope); }); } // ...
}
Ora possiamo passare all'implementazione dei mancanti load
metodo per iniettare dinamicamente a <script>
elemento:
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`;
}
Nota la convenzione codificata in elementURL
. src
l'URL dell'attributo presuppone che esista una directory in cui risiedono tutte le definizioni degli elementi personalizzati (ad es <my-widget>
→ /components/my-widget.js
). Potremmo escogitare strategie più elaborate, ma questo è abbastanza buono per i nostri scopi. Relegare questo URL a un metodo separato consente la sottoclasse specifica del progetto quando necessario:
class FancyLoader extends AutoLoader { elementURL(tag) { // fancy logic }
}
Ad ogni modo, nota che ci stiamo affidando a this.rootDir
. È qui che entra in gioco la suddetta configurabilità. Aggiungiamo un getter corrispondente:
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;
}
Potresti pensarci observedAttributes
ora, ma questo non rende le cose più facili. Più l'aggiornamento root-dir
in fase di esecuzione sembra qualcosa di cui non avremo mai bisogno.
Ora possiamo - e dobbiamo - configurare la nostra directory degli elementi: <ce-autoloader root-dir="/components">
.
Con questo, il nostro caricatore automatico può fare il suo lavoro. Tranne che funziona solo una volta, per gli elementi che esistono già quando il caricatore automatico viene inizializzato. Probabilmente vorremo tenere conto anche degli elementi aggiunti dinamicamente. Ecco dove MutationObserver
entra in gioco:
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();
}
In questo modo, il browser ci avvisa ogni volta che appare un nuovo elemento nel DOM — o meglio, nel nostro rispettivo sottoalbero — che poi utilizziamo per riavviare il processo di scoperta. (Potresti obiettare che stiamo reinventando elementi personalizzati qui, e avresti ragione.)
Il nostro caricatore automatico è ora completamente funzionante. I miglioramenti futuri potrebbero esaminare potenziali condizioni di competizione e analizzare le ottimizzazioni. Ma è probabile che questo sia abbastanza buono per la maggior parte degli scenari. Fammi sapere nei commenti se hai un approccio diverso e possiamo confrontare le note!
- Distribuzione di contenuti basati su SEO e PR. Ricevi amplificazione oggi.
- Platoblockchain. Web3 Metaverse Intelligence. Conoscenza amplificata. Accedi qui.
- Fonte: https://css-tricks.com/an-approach-to-lazy-loading-custom-elements/
- 1
- a
- Chi siamo
- sopra
- Il mio account
- effettivamente
- aggiunto
- aggiunta
- Tutti
- alleviare
- consente
- già
- ed
- approccio
- discutere
- in giro
- disponibile
- del browser
- i candidati
- non può
- che
- attento
- probabilità
- dai un'occhiata
- bambino
- classe
- Venire
- Commenti
- confrontare
- condizioni
- Contenitore
- Convenzione
- Corrispondente
- potuto
- corso
- costume
- Design
- diverso
- scoperta
- documento
- non
- DOM
- Cadere
- dinamicamente
- più facile
- facilmente
- Elaborare
- elementi
- abbastanza
- errore
- Etere (ETH)
- EV
- Anche
- Ogni
- Tranne
- costoso
- fallito
- fan
- Nome
- da
- completamente
- funzionale
- futuro
- andando
- buono
- capo
- qui
- HTML
- HTTPS
- ideale
- identificare
- subito
- realizzare
- implementazione
- Implementazione
- in
- invece
- indagare
- IT
- JavaScript
- Lavoro
- Genere
- Sapere
- Lunghezza
- LIMITE
- piccolo
- caricare
- Caricamento in corso
- Guarda
- Principale
- make
- FA
- si intende
- metodo
- forza
- mancante
- modulo
- Scopri di più
- maggior parte
- cambiano
- Mozilla
- multiplo
- Bisogno
- di applicazione
- New
- nodo
- parte
- performance
- Platone
- Platone Data Intelligence
- PlatoneDati
- Giocare
- più
- potenziale
- probabilmente
- processi
- fini
- Gara
- recentemente
- rimuovere
- quelli
- ritorno
- radice
- Scenari
- portata
- sembra
- separato
- set
- dovrebbero
- mostrato
- Un'espansione
- singolo
- So
- qualcosa
- inizia a
- step
- Ancora
- strategie
- tale
- supportato
- TAG
- prende
- tecniche
- I
- loro
- cose
- Pensiero
- a
- vero
- aggiornamento
- aggiornato
- URI
- URL
- us
- uso
- generalmente
- via
- quale
- volere
- Lavora
- lavori
- scrittura
- Trasferimento da aeroporto a Sharm
- zefiro