Djalma Júnior

Djalma Júnior

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 atributo type.

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.


  1. https://pt.wikipedia.org/wiki/Código_espaguete ↩︎

  2. https://pt.wikipedia.org/wiki/Referência_circular ↩︎

  3. https://developer.mozilla.org/pt-BR/docs/Glossary/IIFE ↩︎

  4. Bundlers, em inglês ↩︎

  5. https://en.wikipedia.org/wiki/Dependency_hell ↩︎

  6. https://www.theregister.com/2018/11/26/npm_repo_bitcoin_stealer/ ↩︎

  7. https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Guide/Modules ↩︎

  8. https://github.com/WICG/import-maps ↩︎

  9. https://developer.mozilla.org/pt-BR/docs/Web/API/Service_Worker_API/Using_Service_Workers ↩︎

  10. https://pt.stackoverflow.com/questions/195116/o-que-é-um-shim ↩︎