Libertando-se dos bundlers (empacotadores)
Sumário
Como tudo começou?! §
No princípio era o desenvolvimento inline… E para quem está começando no mundo do webdev, era tudo muito bom. Contudo, quando avançamos para algo mais complexo, nos deparamos com a falta de reusabilidade nessa forma de programar — sem falar na dor de cabeça de ter que gerenciar centenas de variáveis globais (aquele velho código “macarrônico”[1])… Se você nunca passou por isso, pode se considerar um sortudo.
Aí, no auge da nossa maturidade de desenvolvedor júnior, passamos a quebrar o código em partes menores e dividi-los em vários arquivos para melhor manutenção… Só que nos deparamos com outro problema: o gerenciamento de dependências! É quando um script A
depende do script B
que, por sua vez, depende do script C
… Isso sem falar nos casos onde o script C
também depende do script A
, causando uma dependência circular[2]. E lá vamos nós quebrando a cabeça para colocar nossas tags script
em ordem de carregamento… Depois de um tempinho organizando as dependências de um projeto complexo, vem o estagiário e decide ordenar os scripts em ordem alfabética e faz o commit na sexta-feira às 18h00 😱… 💣💥🔥.
Com o tempo, foi desenvolvido o padrão modular IIFE[3] (Immediately-invoked function expression), para contornar o problema das variáveis globais:
// Exemplo de padrão modular usando IIFE
var contador = (function () {
var num = 0;
return {
incrementar: function () {
return num++;
},
resetar: function () {
return (num = 0);
},
};
})();
console.log(contador.incrementar()); // 1
console.log(contador.incrementar()); // 2
console.log(contador.num); // undefined
console.log(contador.resetar()); // 0
No exemplo acima, protegemos a variável num
dentro da função IIFE de forma que se tentarmos acessar ela fora da função IIFE, iremos receber undefined
como resposta. Contudo, isso não resolvia o problema do gerenciamento das dependências.
Observação: Como estou citando um breve histórico da motivação por trás dos empacotadores, omiti propositalmente o uso das palavras-chave
let/const
bem como outros novos constructos do JavaScript.
Os Empacotadores §
A partir de milhares de casos semelhantes ao que foi citado, surgiram o que chamamos de empacotadores[4]. Com o tempo, foram surgindo ferramentas como RequireJS, Browserify, Webpack, Rollup, Parcel, Microbundle, Snowpack… Para dar conta do recado, cada um do seu jeito: usando AMD, UMD, CommonJS, RequireJS… Isso resolveu os problemas anteriores e muito mais. Tudo ia muito bem, obrigado.
Pegando carona nesse lance de empacotadores, muitas bibliotecas JavaScript começaram a usar e abusar do gerenciamento de dependências e separavam cada vez mais as funcionalidades em pequenos arquivos (muitas vezes de forma desnecessária), publicando essas pequenas funcionalidades em pacotes e versões próprias sem qualquer controle ou sem se importar com quebra de compatibilidade… Isso resultou num problema chamado “dependency hell”[5].
Dependency Hell: um termo coloquial para expressar a frustração de alguns desenvolvedores ao usarem pacotes que dependem de versões diferentes de um mesmo pacote que você já usa 😨! Por exemplo: seu projeto utiliza o [email protected] e uma biblioteca [email protected] que, por sua vez, usa o [email protected]… Já viu no que vai dar, não é?! Logo, você vai ter milhares de arquivos, muitas vezes usando versões diferentes da mesma biblioteca e milhares de dependências das quais você nem imagina do que é capaz (como o caso de uma biblioteca popular que roubava bitcoins[6]).
Porém, um belo dia os navegadores começaram a dar suporte à um padrão de gerenciamento de dependências chamado ESM[7] (EcmaScript Module) bem como um mapeamento de onde esse módulo reside, os importmaps[8]… Juntamente com os ServiceWorkers[9], isso abriu as portas para possibilidades que, até então, estavam restritas aos empacotadores e é sobre isso que eu quero falar (finalmente!).
ESM + ImportMaps §
Um dos grandes lances da importação de pacotes via ESM é que não precisamos nos preocupar em baixar as dependências para o nosso computador antes de começar a desenvolver (com a baixa qualidade nos serviços de acesso à Internet em muitos lugares por aí, talvez isso não seja tão vantajoso assim 😅). Então, criar um simples programa usando ESM fica assim:
<html>
<body>
<script type="module">
import confetti from "https://cdn.skypack.dev/canvas-confetti";
confetti(); // Uma "explosão" de confetes aparece na tela 🎉
</script>
</body>
</html>
Nenhum empacotador foi necessário e conseguimos isolamentos de variáveis e gerenciamento de dependências nativamente. Mas aí você me diz: “Peraê, jovem… Ter que ficar digitando a URL nos imports não é nada divertido… E se tivermos centenas de arquivos e precisarmos alterar para um outro CDN?! Me diga como fica?!”… Tem razão, isso é muito verboso mesmo. Porém, é aí que o importmaps entra em ação! Convertendo esse exemplo para uma versão com importmaps, ficaria algo assim:
<html>
<body>
<script src="https://unpkg.com/[email protected]"></script>
<script type="importmap-shim">
{
"imports": {
"canvas-confetti": "https://cdn.skypack.dev/canvas-confetti"
}
}
</script>
<script type="module-shim">
import confetti from 'canvas-confetti';
confetti(); // Uma "explosão" de confetes aparece na tela 😁
</script>
</body>
</html>
Observação: no momento da escrita desse artigo, importmap ainda não é largamente compatível pelos navegadores, por isso adaptei o código para usar o es-modules-shim (que funciona muito bem por sinal). Quando essa proposta for totalmente implementada, a alteração nesse código se resumiria à remoção do shim[10] junto com a remoção do trecho
-shim
no atributotype
.
Isso já é uma enorme melhoria em nosso código! Nós reduzimos a repetição das URLs e, caso seja necessário, podemos trocá-las por outro CDN de nossa preferência! Contudo, ainda temos um longo caminho pela frente para nos livrarmos dos empacotadores de uma vez por todas como:
- Como carregar CSS?
- Como organizar um projeto complexo?
- E o alias que os empacotadores nos oferecem (ex.:
import lib from "~/lib"
), vou perder isso?! - Isso aí só funciona se minha dependência estiver no formato ESM… E seu eu precisar usar uma dependência que esteja em UMD (como React)?!
- E como fica isso e aquilo… E aí???
São boas perguntas! No meu próximo artigo, irei construir o bom e velho TodoMVC usando esses princípios mencionados e tentar responder a maior parte delas.
Bundlers, em inglês ↩︎
https://www.theregister.com/2018/11/26/npm_repo_bitcoin_stealer/ ↩︎
https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Guide/Modules ↩︎
https://developer.mozilla.org/pt-BR/docs/Web/API/Service_Worker_API/Using_Service_Workers ↩︎
https://pt.stackoverflow.com/questions/195116/o-que-é-um-shim ↩︎