Desmistificando o webpack

Todos nós, definitivamente, já pensamos em usar o webpack em algum momento. Ele é de longe o module bundler (agrupador de módulos) mais popular do mercado, devido ao número infinito de carregamentos e possibilidades de customização que ele traz aos processos.

De certa forma, o webpack influenciou o crescimento de alguns ecossistemas JavaScript.

Mas quantas vezes nós pensamos em abrir esse arquivo de bundler e entender o que acontece durante o processo?

Como o meu aplicativo, que contém centenas de arquivos individuais, funciona de maneira tão incrível e coesa a partir desse único agrupador? 

Vamos detalhar os conceitos dessa ferramenta e entender o que acontece durante o processo de bundling.

Não analisarei os elementos na configuração do webpack, pois eles já são mencionados em detalhes na própria documentação da ferramenta. Em vez disso, focarei nos conceitos principais.

O que é um bundler?

Antes de irmos adiante, vamos entender o que é um bundler. Um bundler é um programa que pega um número de arquivos e os coloca juntos de uma maneira que não interfere na forma como os códigos funcionam.

Isso permite que você escreva códigos de maneira modular, mas os sirva como um arquivo monolítico. 

Por que precisamos de um bundler?

Hoje, mais do que nunca, pensando na manutenção e na reutilização, escrevemos os códigos em módulos. Esse estilo modular funciona bem se o aplicativo for pequeno. 

Porém, à medida que os aplicativos aumentam em complexidade e tamanho, fica difícil gerenciar o número crescente de dependências e códigos durante a execução desse código modular. 

Por exemplo, considere que você está criando um aplicativo HTML/JavaScript que consiste em 50 módulos JS. No seu HTML, você não pode ter 50 tags de script para usá-las na página. 

É aqui que o bundler entra em ação, ele agrupa todos esses 50 arquivos e fornece um arquivo que você pode usar no HTML com uma única tag de script.

Desmistificando o webpack

Ok, chega de ficar no básico. Vamos mergulhar no webpack agora!

Considere esses três arquivos:


// A.js

const B = require('./B');

B.printValue();

// B.js

const C = require('./C.js')

const printValue = () => {
  console.log(`The value of C.text is ${C.text}`);
};

module.exports = {
  printValue,
};

// C.js

module.exports = {
  text: 'Hello World!!!',
};

Eu defini A.js como meu ponto de entrada para o webpack e a saída para um único arquivo agrupado. Quando você executa o webpack, duas coisas acontecem:

  • Formação do gráfico de dependência;
  • Resolução do gráfico de dependência e Tree-Shaking.

Formar o gráfico de dependência

A primeira coisa que o webpack fará é analisar os módulos presentes e formar um gráfico de dependência.

O gráfico de dependência é um gráfico direcionado que diz como cada módulo está conectado a outro módulo. 

É bastante popular entre os gerenciadores de pacotes, como npm, maven, snap, etc. Ele começa no ponto de entrada A.js e nosso gráfico inicialmente se parece com isso, com apenas um ponto.

Initial

Aí, o webpack passa a entender que B.js está sendo requisitado por A.js. Então ele vai e cria um link de A até B no gráfico. 

Second

Agora, analisando B.js, ele entende que também precisa do C.js. Então, novamente, no gráfico, ele cria um link de B até C. 

Third

Agora, hipoteticamente, se A.js exige outro arquivo chamado D.js que, em retorno, necessita do C.js, o gráfico se torna:

Extra

Veja, é algo relativamente simples. Agora no C.js, o webpack percebe que não possui mais módulos como dependências e gera o gráfico completo de dependências.

Resolvendo os módulos

Ok! O webpack tem os módulos e os gráficos. Ele precisa colocar todos eles em um único arquivo, então ele pega um ponto por vez no gráfico começando da raiz A.js.

Ele copia o conteúdo do A.js para os arquivos de saída, marca o ponto como resolvido e depois vai para o “filho” do A.js.

Suponha que, se o módulo que já foi resolvido anteriormente aparecer de novo, ele será apenas ignorado.

Da mesma forma, ele continua adicionando conteúdo dos módulos ao arquivo de saída até terminar de percorrer o gráfico de dependência

Tree-Shaking

Tree-Shaking é o processo de remover um código morto da saída. Enquanto o webpack está criando o gráfico, ele também marca qual módulo está sendo usado ou não.

Se o módulo não estiver sendo usado em nenhum lugar, ele o remove, pois é efetivamente um código morto (dead code). Um ponto a ser observado: o webpack faz isso apenas no módulo de produção. 

Vamos olhar para o código dos três arquivos:


/******/ (function(modules) { 
// webpackBootstrap 
/******/    // Load entry module and return exports
/******/    return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

// A.js

const B = __webpack_require__(1);

B.printValue();

/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {

// B.js

const C = __webpack_require__(2)

const printValue = () => {
  console.log(`The value of C.text is ${C.text}`);
};

module.exports = {
  printValue,
};

/***/ }),
/* 2 */
/***/ (function(module, exports) {

// C.js

module.exports = {
  text: 'Hello World!!!',
};

/***/ })
/******/ ]);

Você pode reconhecer imediatamente que é um IIFE. As funções pegam uma lista de módulos e executam os comandos dos códigos de cada módulo.

Nós podemos ver que o primeiro módulo é o nosso arquivo de entrada  A.js.

O segundo é o B.js e o terceiro é o C.js. E nós podemos ver que cada um desses módulos está modificado como funções que podem ser executadas. 

O parâmetro module é o substituto para o padrão node module object. 

exports é o substituto para o objeto de exports object e __webpack_require__ é o substituto para o require usado em nossos programas. 

O // webpackBootstrap contém a implementação da função que é bastante longa. Vamos ver somente a implementação de __webpack_require__.


function __webpack_require__(moduleId) {
/******/
/******/        // Check if module is in cache
/******/        if(installedModules[moduleId]) {
/******/            return installedModules[moduleId].exports;
/******/        }
/******/        // Create a new module (and put it into the cache)
/******/        var module = installedModules[moduleId] = {
/******/            i: moduleId,
/******/            l: false,
/******/            exports: {}
/******/        };
/******/
/******/        // Execute the module function
/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/        // Flag the module as loaded
/******/        module.l = true;
/******/
/******/        // Return the exports of the module
/******/        return module.exports;
/******/    }

O código é bastante simples de entender. Ele pega um moduleId e checa se este módulo está presente no cache installedModules. Se ele não estiver presente, ele cria uma entrada no cache. 

A próxima linha modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); na verdade executa a função de módulo na matriz de módulos pela qual passamos anteriormente para a parent function. 

Comparando isso com a sintaxe fn.call(), nós podemos deduzir que o module é o objeto criado anteriormente. 

O escopo exports e this é o objeto exports do módulo objeto criado, e __webpack_require__ é a própria função. Em seguida, ele define o módulo como carregado no cache e retorna às exportações do módulo.

Essa é a forma como o webpack funciona em seu nível fundamental. Ainda há um monte de coisas mais poderosas que ele faz também, como, por exemplo, minimizar o carregamento inicial solicitando módulos de maneira específica, a qual eu altamente recomendo que vocês explorem. 

É sempre melhor entender como uma ferramenta de utilidade funciona antes de começarmos a usá-la.

Isso ajuda a escrever códigos mais otimizados, tendo sempre em mente o funcionamento interno e as restrições da ferramenta que estamos usando. 

Este é um artigo traduzido, você pode acessar a versão original em inglês aqui. Todos os créditos para o autor: Adarsh.

twitterfacebooklinkedinyoutube-playinstagram