somos fãs de Elementos Personalizados por aqui. Seu design os torna particularmente passível de carregamento lento, o que pode ser uma vantagem para o desempenho.
Inspirado por de um colega experimentos, recentemente comecei a escrever um carregador automático simples: Sempre que um elemento personalizado aparecer no DOM, queremos carregar a implementação correspondente se ainda não estiver disponível. O navegador então se encarrega de atualizar esses elementos a partir daí.
Provavelmente, você não precisará de tudo isso; geralmente há uma abordagem mais simples. Usadas deliberadamente, as técnicas mostradas aqui ainda podem ser uma adição útil ao seu conjunto de ferramentas.
Para consistência, queremos que nosso carregador automático também seja um elemento personalizado — o que também significa que podemos configurá-lo facilmente via HTML. Mas primeiro, vamos identificar os elementos personalizados não resolvidos, passo a passo:
class AutoLoader extends HTMLElement { connectedCallback() { let scope = this.parentNode; this.discover(scope); }
}
customElements.define("ce-autoloader", AutoLoader);
Assumindo que carregamos este módulo antecipadamente (usando async
é ideal), podemos soltar um <ce-autoloader>
elemento para o <body>
do nosso documento. Isso iniciará imediatamente o processo de descoberta de todos os elementos filhos de <body>
, que agora constitui nosso elemento raiz. Poderíamos limitar a descoberta a uma subárvore do nosso documento adicionando <ce-autoloader>
em vez disso, para o respectivo elemento de contêiner - na verdade, podemos até ter várias instâncias para diferentes subárvores.
Claro, ainda temos que implementar isso discover
método (como parte do AutoLoader
classe acima):
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); } }
}
Aqui, verificamos nosso elemento raiz junto com cada descendente (*
). Se for um elemento personalizado — conforme indicado por tags hifenizadas — mas ainda não atualizado, tentaremos carregar a definição correspondente. Consultar o DOM dessa forma pode ser caro, então devemos ter um pouco de cuidado. Podemos aliviar a carga no thread principal adiando este trabalho:
connectedCallback() { let scope = this.parentNode; requestIdleCallback(() => { this.discover(scope); });
}
requestIdleCallback
ainda não é universalmente suportado, mas podemos usar requestAnimationFrame
como recurso:
let defer = window.requestIdleCallback || requestAnimationFrame; class AutoLoader extends HTMLElement { connectedCallback() { let scope = this.parentNode; defer(() => { this.discover(scope); }); } // ...
}
Agora podemos passar para a implementação do que falta load
método para injetar dinamicamente um <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`;
}
Observe a convenção codificada em elementURL
. O src
URL do atributo assume que há um diretório onde residem todas as definições de elementos personalizados (por exemplo <my-widget>
→ /components/my-widget.js
). Poderíamos chegar a estratégias mais elaboradas, mas isso é bom o suficiente para nossos propósitos. Relegar esta URL para um método separado permite subclasses específicas do projeto quando necessário:
class FancyLoader extends AutoLoader { elementURL(tag) { // fancy logic }
}
De qualquer forma, observe que estamos contando com this.rootDir
. É aqui que entra a configurabilidade mencionada acima. Vamos adicionar um getter correspondente:
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;
}
Você pode estar pensando em observedAttributes
agora, mas isso realmente não torna as coisas mais fáceis. Mais atualização root-dir
em tempo de execução parece algo que nunca vamos precisar.
Agora podemos — e devemos — configurar nosso diretório de elementos: <ce-autoloader root-dir="/components">
.
Com isso, nosso carregador automático pode fazer seu trabalho. Exceto que funciona apenas uma vez, para elementos que já existem quando o carregador automático é inicializado. Provavelmente também queremos considerar os elementos adicionados dinamicamente. Isso e onde MutationObserver
entra em jogo:
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();
}
Dessa forma, o navegador nos avisa sempre que um novo elemento aparece no DOM — ou melhor, em nossa respectiva subárvore — que usamos para reiniciar o processo de descoberta. (Você pode argumentar que estamos reinventando elementos personalizados aqui, e você estaria correto.)
Nosso carregador automático agora está totalmente funcional. Aprimoramentos futuros podem examinar possíveis condições de corrida e investigar otimizações. Mas é provável que isso seja bom o suficiente para a maioria dos cenários. Deixe-me saber nos comentários se você tem uma abordagem diferente e podemos comparar as notas!
- Conteúdo com tecnologia de SEO e distribuição de relações públicas. Seja amplificado hoje.
- Platoblockchain. Inteligência Metaverso Web3. Conhecimento Ampliado. Acesse aqui.
- Fonte: https://css-tricks.com/an-approach-to-lazy-loading-custom-elements/
- 1
- a
- Sobre
- acima
- Conta
- adicionado
- Adição
- Todos os Produtos
- aliviar
- permite
- já
- e
- abordagem
- argumentar
- por aí
- disponível
- navegador
- candidatos
- não podes
- Cuidado
- cuidadoso
- chances
- verificar
- criança
- classe
- como
- comentários
- comparar
- condições
- Recipiente
- Convenção
- Correspondente
- poderia
- curso
- personalizadas
- Design
- diferente
- descoberta
- documento
- Não faz
- DOM
- Cair
- dinamicamente
- mais fácil
- facilmente
- Elaborar
- elementos
- suficiente
- erro
- Éter (ETH)
- EV
- Mesmo
- Cada
- Exceto
- caro
- fracassado
- fãs
- Primeiro nome
- da
- totalmente
- funcional
- futuro
- vai
- Bom estado, com sinais de uso
- cabeça
- SUA PARTICIPAÇÃO FAZ A DIFERENÇA
- HTML
- HTTPS
- ideal
- identificar
- imediatamente
- executar
- implementação
- implementação
- in
- em vez disso
- investigar
- IT
- JavaScript
- Trabalho
- Tipo
- Saber
- Comprimento
- LIMITE
- pequeno
- carregar
- carregamento
- olhar
- a Principal
- fazer
- FAZ
- significa
- método
- poder
- desaparecido
- módulo
- mais
- a maioria
- mover
- Mozilla
- múltiplo
- você merece...
- necessário
- Novo
- nó
- parte
- atuação
- platão
- Inteligência de Dados Platão
- PlatãoData
- Jogar
- mais
- potencial
- provavelmente
- processo
- fins
- Corrida
- recentemente
- remover
- aqueles
- retorno
- raiz
- cenários
- escopo
- parece
- separado
- conjunto
- rede de apoio social
- mostrando
- simples
- solteiro
- So
- algo
- começo
- Passo
- Ainda
- estratégias
- tal
- Suportado
- TAG
- toma
- técnicas
- A
- deles
- coisas
- Pensando
- para
- verdadeiro
- atualização
- atualizado
- URI
- URL
- us
- usar
- geralmente
- via
- qual
- precisarão
- Atividades:
- trabalho
- escrita
- investimentos
- zefirnet