Tree shaking de ícones
Importe os ícones que você usa, e o resto do conjunto fica fora do seu bundle. Este guia mostra o que faz isso funcionar, o que quebra esse comportamento sem nenhum aviso e como ficam os números em um build real.
Do que você precisa
Qualquer bundler que faça tree shaking de módulos ES: Vite, Rollup, webpack 5 ou Rspack em modo de produção, Parcel, ou esbuild com bundling e code splitting ativados. Não há nada para configurar. Use imports nomeados a partir dos pontos de entrada ESM:
import { HomeIcon, SearchIcon } from 'glyphfox/react'; import { iconHome } from 'glyphfox'; // icon data, also tree shaken import { renderToString } from 'glyphfox/core'; // the engine without icon data
O mesmo vale para glyphfox/vue, que é gerado exatamente como o ponto de entrada do React, e para os componentes Svelte, que também são um arquivo por ícone. Servidores de desenvolvimento não fazem tree shaking, então avalie o tamanho só em builds de produção.
Como funciona
Três características do pacote permitem que o bundler descarte todos os ícones que você não importa.
- Um módulo por ícone. O build grava cada ícone em um arquivo próprio, e o index de cada ponto de entrada só faz o re-export desses arquivos. Um bundler consegue seguir só o re-export que você usa e ignorar os outros.
- Chamadas puras. Cada componente é criado com
/*#__PURE__*/ createIcon(icon), o que indica ao minificador que a chamada não tem nenhum efeito que valha manter quando o resultado não é usado. - Efeitos colaterais declarados. O campo
sideEffectsdopackage.jsonlista os únicos arquivos que fazem algo ao serem importados. Qualquer outro módulo pode ser ignorado por completo quando nenhum dos exports dele é usado.
import { createIcon } from '../runtime.js'; import icon from '../../icons/home.js'; export const HomeIcon = /*#__PURE__*/ createIcon(icon); export default HomeIcon;
"sideEffects": [ "./dist/web/index.js", "./dist/web/glyphfox.min.js", "./dist/runtime/auto.js", "*.css" ]
Os arquivos com efeitos colaterais são justamente os que existem para trabalhar na importação: glyphfox/web define o custom element com todos os ícones, o arquivo da CDN prepara tudo para uma página sem build e glyphfox/runtime substitui os elementos data-glyph.
Tamanhos medidos
Um exemplo de medição do build atual, feita com esbuild 0.28: ESM minificado com code splitting, contando só os chunks carregados logo de início, com o React marcado como externo. Use os números como referência de proporção, não como promessa; o seu bundler e o seu próprio código vão alterar esses valores.
| O que o app importa | Minificado | Gzip |
|---|---|---|
| Um ícone React mais o runtime compartilhado | 76,0 KB | 27,6 KB |
| 10 ícones React | 79,3 KB | 29,7 KB |
| 50 ícones React | 90,9 KB | 37,9 KB |
import * as Icons com chave dinâmica (todos os ícones) | 214,7 KB | não medido |
renderToString de glyphfox/core mais um ícone | 37,4 KB | 15,2 KB |
toSvg do ponto de entrada principal glyphfox | 153,8 KB | 45,0 KB |
O arquivo da CDN glyphfox.min.js, sem etapa de build | 216,4 KB | 62,4 KB |
sprite.svg com todos os ícones | 117,9 KB | 15,7 KB |
Duas coisas chamam a atenção. O runtime é a parte fixa: renderização, os três eixos, modificadores, fusão, morph e os presets de animação são compartilhados por todos os ícones e custam o mesmo para um ícone ou para cinquenta. Cada ícone extra sai barato, cerca de 0,2 KB com gzip em média entre os builds de 10 e de 50 ícones. Um build de teste com Vite 8.3.5 confirmou: o chunk de entrada com HomeIcon ficou em 81,5 KB minificado e 27,7 KB com gzip.
Ícones pedidos pelo nome
Icon com um nome, morphTo e Fusion aceitam nomes como strings. Quando um nome não foi importado em lugar nenhum, o motor carrega o ícone sob demanda da pasta de ícones:
let loader = (name) => import(`../icons/${name}.js`).then((m) => m.default);
O que acontece com esse import depende do bundler. O webpack (e, portanto, o Next.js) e o esbuild não têm como saber quais nomes serão pedidos, então preparam um chunk para cada arquivo que corresponde ao padrão: um por ícone, com algumas centenas de bytes cada, baixado só quando uma página renderiza um nome que não foi importado. O Vite não transforma imports dinâmicos dentro de node_modules: o build de produção dele não emite chunks de ícones e mantém o import como foi escrito, então um nome que não foi importado nem registrado fica vazio em produção, embora funcione em desenvolvimento, onde a dependência é pré-empacotada.
Se um app realmente precisa de qualquer nome em tempo de execução, como em um seletor de ícones, inclua o glyphfox de volta na transformação. O Vite passa então a emitir os chunks por ícone e mostra um aviso INEFFECTIVE_DYNAMIC_IMPORT para cada ícone que também é importado diretamente, o que é inofensivo:
export default { build: { dynamicImportVarsOptions: { exclude: [/node_modules\/(?!glyphfox\/)/] }, }, };
Quando você conhece os nomes de antemão, registre os dados deles e nada é buscado em tempo de execução, com qualquer bundler. Se você mesmo serve os dados dos ícones, substitua o loader; o loader padrão continua no código, então o webpack ainda emite os chunks, mas o app não vai requisitá-los:
import { registerIcons, setIconLoader } from 'glyphfox/core'; import { iconSettings, iconUsers } from 'glyphfox'; registerIcons([iconSettings, iconUsers]); // optional: load other names from one JSON file you host (a copy of glyphfox/icons.json) let all; setIconLoader(async (name) => { all ??= await fetch('/icons.json').then((r) => r.json()); return all[name] ?? null; });
Como entregar um subconjunto fixo
Páginas sem bundler, pacotes de design system e apps nativos muitas vezes querem uma lista fixa de ícones com props fixas. A CLI gera exatamente isso, a partir de nomes na linha de comando ou de um arquivo com um nome por linha:
# a sprite with only these icons npx glyphfox build home search file+check --type sprite --out public/icons # a CSS file with one mask class per icon npx glyphfox build home search bell --type css --out public/icons # a small package of SVG strings, with the props baked in npx glyphfox build --from icons.txt --type package --name @acme/icons --weight 1.5 --out acme-icons
Um sprite ou arquivo CSS com um subconjunto não contém motor nenhum, o que supera qualquer bundle quando os ícones nunca mudam. Para páginas que carregam o arquivo da CDN, um subconjunto costuma ser a maior economia individual: o arquivo completo traz todos os ícones por conveniência. O guia de sprites SVG e o guia de HTML e CDN cobrem as duas configurações.
Confira o seu bundle
Os dados de ícone mantêm as chaves name e body depois da minificação, o que permite uma contagem rápida sem nenhum analisador. Em um build do Vite, os ícones que você importa diretamente ficam no chunk de entrada, ao lado do placeholder chamado empty, que o runtime desenha enquanto um nome carrega:
npx vite build grep -ohE 'name:["`][a-z0-9-]+["`],body' dist/assets/index-*.js | grep -v empty | sort -u | wc -l
O número deve bater com os ícones que você importa. Se estiver perto do tamanho do conjunto inteiro, um dos padrões abaixo está puxando tudo para dentro. Outros bundlers podem mover módulos compartilhados para chunks separados; com eles, faça a mesma contagem em todos os arquivos que a página carrega logo de início e deixe de fora os chunks de ícones carregados sob demanda.
Armadilhas comuns
import *com chave dinâmicaIcons[name]em um import de namespace obriga o bundler a manter todos os ícones. Em vez disso, renderize<Icon name={name} />, que carrega só os nomes que de fato aparecem.requiredo CommonJSrequire('glyphfox/react')resolve para oindex.cjsempacotado, um único arquivo com todos os ícones que nenhum bundler consegue dividir. Garanta que o código do seu app useimportpara que o build ESM seja escolhido.toSvgno clientetoSvgrecebe nomes, então precisa do registro completo e leva todos os ícones junto. No código do navegador, userenderToStringdeglyphfox/corecom dados de ícone importados. No servidor, o tamanho não importa.- esbuild sem splitting
- Sem
splitting: true, o esbuild embute todos os módulos que o loader dinâmico consegue alcançar: um único ícone chegou a 201 KB minificado em um só arquivo, em vez de 76 KB. O mesmo acontece com qualquer saída que embute imports dinâmicos, como um build de biblioteca IIFE ou UMD. glyphfox/webpara poucos ícones- Esse ponto de entrada define o elemento com o conjunto inteiro de propósito. Para um subconjunto, use
defineGlyphFoxdeglyphfox/web/elementeregisterIcons. - Avaliar o tamanho em desenvolvimento
- Servidores de desenvolvimento carregam todos os módulos de um barrel file. Imports profundos como
glyphfox/react/icons/homedeixam o desenvolvimento mais rápido, mas o que conta para o tamanho é o build de produção.
Perguntas frequentes
Por que a saída do meu build tem centenas de arquivos minúsculos?
Com webpack ou esbuild, são os chunks lazy do loader sob demanda, um por ícone, preparados porque um nome pode ser pedido como string em tempo de execução. Eles ficam no servidor, a menos que uma página renderize um nome que não foi importado, então não custam nada para os usuários. Se você registrar os seus nomes com registerIcons, eles nunca serão requisitados. Um build de produção do Vite não cria esses arquivos, como explicado em ícones pedidos pelo nome.
Quanto custa cada ícone extra?
Na medição acima, cerca de 0,2 KB com gzip por ícone, além do runtime compartilhado de aproximadamente 28 KB com gzip. O runtime é pago uma única vez, então o segundo ícone sai muito mais barato do que o primeiro.