Webpack sem Medo — Parte 1: Introdução

Esta série tem o objetivo de apresentar as funcionalidades do webpack de forma gradativa, explicando conceitos e mostrando exemplos.

Por que “sem medo”?
Conheço algumas pessoas (incluindo eu mesmo) que tiveram um primeiro contato com webpack, não entenderam direito como usar, ficaram mais confusas ainda lendo a documentação e criaram uma aversão à ferramenta.
Muitas dessas pessoas tiveram contato com a versão 1 do webpack, que tinha uma configuração mais confusa e uma documentação menos organizada.
A boa notícia é que a partir da versão 2 isso mudou radicalmente. A configuração ficou mais fácil de compreender, e a documentação foi completamente reescrita, com excelentes guias, explicações claras e completas.
Se tinha medo do webpack, a hora de perder o medo é essa!

Mapa da série:

Mas o que é webpack?

webpack é um module bundler (empacotador de módulos) para JavaScript, em outras palavras, ele junta os arquivos JS (e também outros formatos) da sua aplicação (seja arquivos seus ou dependências externas) em um arquivo só (ou mais de um), de forma otimizada. Os arquivos são unificados na ordem certa, sem duplicação e podem ser minificados para reduzir o tamanho.

Um ponto forte do webpack que ele é altamente configurável e extensível através de plugins. Isso permite fazer algumas coisas interessantes como:

  • Utilizar outros sabores de JS, como ES6+, TypeScript, JSX e fazer a transpilação (conversão para ES5) automaticamente no processo de empacotamento.
  • Tratar e empacotar outros tipos de arquivos, como CSS (e variações como SASS, LESS, Stylus), SVG, imagens, arquivos de templates (Pug, Handlebars).
  • E muito mais, como veremos mais pra frente…

Grafo de dependências

Como o webpack sabe quais arquivos incluir e em qual ordem? Ele monta um grafo de dependências, que é uma estrutura mais ou menos assim:

Grafo de dependências

Partindo de um arquivo inicial (entrada), ele verifica quais outros arquivos são importados por este, e os adiciona ao grafo. Para cada um desses ele também verifica quais eles importam, assim sucessivamente até que o grafo contenha todos os arquivos (módulos) necessários para a aplicação.

Módulos

Para indicar que um arquivo JS depende de outro, ele precisa importá-lo. Ex.:

import React from 'react'
import utils from './utils'

No exemplo acima ele importa uma dependência externa (React) e uma interna (o caminho começa com ./). Note também que não é necessário informar a extensão do arquivo se for JS.

O arquivo que vai ser importado, em contrapartida, precisa indicar o que ele exporta. Ex.:

const utils = { ... }  
export default utils

Cada arquivo desses, que exporta algum dado ou funcionalidade, é chamado de módulo.

Dividir sua aplicação em módulos é uma boa prática, pelos seguintes benefícios:

  • Manutenibilidade: as funcionalidades ficam menos acopladas, é mais fácil alterar uma parte do código sem afetar o sistema inteiro.
  • Isolamento: variáveis e funções declaradas dentro de um módulo são acessíveis apenas dentro dele (exceto aquilo que o módulo explicitamente exporta), evitando conflitos de variáveis e acesso desnecessário de uma parte do código a outra não relacionada.
  • Reusabilidade: um módulo com uma funcionalidade bem definida, é como uma peça de lego, pode ser reutilizada em outras partes do mesmo sistema, ou em outros sistemas.
  • Testabilidade: com o código menos acoplado, fica mais fácil escrever testes unitários para validar cada funcionalidade.

A forma de fazer import/export mostrada acima usa o padrão ES Modules, que é o adotado a partir do ES6 nos navegadores. O Node.js atualmente usa o padrão CommonJS, que muda um pouco a sintaxe:

const express = require('express')  
const utils = require('./utils')

module.exports = ...

Ambas as sintaxes são suportadas pelo webpack, mas aqui vamos usar ES Modules, que é o padrão no navegador, e está caminhando para se tornar o padrão no Node.js (veja referência [8]).

Leitura importante
Recomendo se familiarizar com a sintaxe do ES Modules, e as diferentes formas de import/export. Veja referências [5], [6] e [7] ao final do artigo.

Os 4 conceitos fundamentais do webpack

Antes de começar a colocar a mão na massa é importante compreender os 4 core concepts do webpack: entry, output, loaders e plugins. Tendo estes conceitos em mente fica mais fácil entender como o webpack trabalha.

Ilustração dos 4 conceitos fundamentais do webpack

Nesta primeira parte vamos ver apenas os dois primeiros, que já serão suficientes para os primeiros exemplos. Veremos os outros quando forem necessários.

Entry (entrada)

Lembra que eu falei lá em cima sobre o grafo de dependências, e que para montá-lo o webpack parte de um arquivo inicial e vai seguindo os imports? Pois é, este arquivo inicial é o entry point (ponto de entrada) da aplicação.

Veremos que toda configuração de webpack possui pelo menos um entry point. Pode ter mais de um (nesse caso serão gerados mais de um grafo de dependências), mas vamos ver esses casos mais avançados em outro momento.

Output (saída)

Uma vez montado o grafo de dependências, o webpack precisa saber onde ele vai salvar o arquivo unificado, ou vários arquivos, dependendo da configuração.


Exemplo 1: Minificação de JS

Vamos ver na prática o exemplo mais básico de um setup de webpack, com apenas uma entrada e uma saída. Vamos pegar uma aplicação divida em arquivos JS (módulos) e gerar um arquivo único.

Este exemplo completo pode ser encontrado aqui: https://github.com/doug2k1/webpack-scenarios/tree/master/1-basic

Pré-requisitos

  • Possuir Node.js instalado (de preferência uma versão recente)
  • Conhecimento básico de terminal/linha de comando (saber navegar entre pastas e executar comandos simples)

Instalação

Inicializar o arquivo package.json para gerenciar dependências, caso sua aplicação ainda não possua. Rode no terminal, na raiz da aplicação:

npm init -y

Instalar o webpack e o webpack-cli (que é o utilitário de linha de comando do webpack):

npm i -D webpack webpack-cli

Usamos -D para salvar o webpack nas dependências de desenvolvimento (devDependencies), já que ele só vai ser usado durante o desenvolvimento. Serão criadas as entradas abaixo no package.json (a versão pode ser diferente, dependendo de qual versão é a mais recente quando você executar o comando):

"devDependencies": {  
  "webpack": "^4.20.2",  
  "webpack-cli": "^3.1.2"  
}

A estrutura de pastas vai ficar assim:

|-- node_modules  
|-- package.json  
|-- package-lock.json

Explicando: node_modules é a pasta que guarda todos os arquivos baixados das dependências instaladas, package.json é onde listamos as dependências e versões (entre outras informações da aplicação) e package-lock.json é um arquivo que “trava” as versões das dependências. Quando outros desenvolvedores executarem npm i para baixar as dependências, este arquivo garante que serão instaladas as mesmas versões para todo mundo.

Zero Config

Vou mostrar daqui a pouco como configurar o webpack usando um arquivo de configuração, mas para este exemplo simples você nem vai precisar deste arquivo.

Arquivos da aplicação

Por enquanto o projeto ainda não tem nenhum arquivo da aplicação em si. Uma prática comum é colocar o código-fonte da aplicação em uma pasta src. Primeiro vamos criar um index.js nesta pasta como nosso ponto de entrada:

|-- src  
  |-- index.js

Com o conteúdo:

import cow from './cow'  
  
document.querySelector('#box').innerText = cow.say('Webpack is great!')

Este arquivo importa outro arquivo que está na mesma pasta, o cow.js (./ significa “mesma pasta do arquivo atual”). Vamos agora criar este outro arquivo:

|-- src  
  |-- index.js  
  |-- cow.js

Com o conteúdo:

import cowsay from 'cowsay-browser'

export default {  
  say: function (str) {  
    return cowsay.say({ text: str })  
  }  
}

Veja que este arquivo já importa uma dependência externa (nome da dependência, sem ./). Precisamos usar o NPM para instalá-la:

npm i -S cowsay-browser

Aqui usamos -S para salvar a dependência no package.json como dependência normal, isto é, que será usada pela aplicação final. Vai ser adicionada esta entrada:

"dependencies": {  
  "cowsay-browser": "^1.1.8"  
}

Arquivo HTML

Para ver o resultado no navegador, vamos precisar de um arquivo HTML. Vamos criar então um index.html na raiz da aplicação, com este conteúdo:

<!doctype html>  
<html>  
<head>  
  <title>Webpack</title>  
</head>  
<body>  
  <pre id="box"></pre>  
  <script src="dist/main.js"></script>  
</body>  
</html>

Veja que carrega um arquivo dist/main.js que ainda não temos. Este é o arquivo que o webpack vai gerar a partir de nossos fontes.

Configurando o webpack

Por default, o webpack considera como ponto de entrada (entry point) o arquivo src/index.js e como arquivo de saída (output) dist/main.js. Por isto este primeiro exemplo não precisa de um arquivo de configuração.

Mas, se quiséssemos alterar o entry point ou o output, poderíamos criar um arquivo webpack.config.js na raiz da aplicação com o conteúdo:

const path = require('path')

module.exports = {  
  entry: './src/index.js',

  output: {  
    path: path.resolve('dist'),  
    filename: 'main.js'  
  }  
}

Veja que este arquivo é um módulo, que exporta um objeto. Aqui devemos usar o padrão CommonJS, pois este arquivo vai executar no Node.js, quando o webpack for acionado.

O primeiro item, entry, é o nosso ponto de entrada.

O segundo, output, é onde o webpack vai salvar o bundle gerado. Esta configuração pede o caminho da pasta (path) e o nome do arquivo (filename) separados. O caminho da pasta deve ser absoluto, por isso usamos a função path.resolve do Node para gerar um caminho absoluto a partir de um relativo.

Gerando o bundle (Fazendo a magia acontecer)

Agora que o circo está montado, vamos gerar esse tal main.js! Para isso, vamos adicionar uma entrada no nosso package.json:

"scripts": {  
  "build": "webpack"  
}

Com isso, basta executar no terminal:

npm run build

Este comando irá executar o webpack (através do webpack-cli), que vai percorrer o grafo de dependências, partindo do entry point, e gerar o main.js, que contém todo o código da aplicação e das dependências.

Saída do webpack

Você pode abrir o index.html no navegador e ver o resultado.

Resultado no navegador (vaquinha que curte webpack)

Modos: desenvolvimento e produção

Se você olhar o arquivo main.js gerado vai ver que ele está minificado (sem identação e quebras de linhas, com os nomes de variáveis alterados e com tamanho bem menor que os arquivos originais). Isso acontece porque o webpack, por padrão, roda em modo produção (production).

O modo production faz duas coisas por baixo dos panos:

  • Ativa o plugin UglifyJS para minificar o bundle. O UglifyJS faz uma minificação agressiva. Além de remover espaços, quebras de linha e comentários, ele renomeia variáveis e faz transformações no código para deixar o mais curto possível.
  • Seta a variável process.env.NODE_ENV="production". Algumas bibliotecas olham esta variável, e se estiver com o valor “production” ativam algumas otimizações para produção.

O outro modo, desenvolvimento (development), não minifica o código, mas ele roda mais rápido. Ideal (como o nome diz) para usar enquanto ainda está desenvolvendo o código.

Para escolher o modo, basta passar a opção --mode para o webpack-cli:

webpack --mode development

Opção watch

Legal, mas toda vez que alterar um JS vou precisar rodar este comando manualmente?

Não, jovem! Para isso tem a opção watch, onde o webpack vai ficar monitorando os arquivos e a cada alteração ele vai reconstruir o arquivo final.

Para isso basta passar a flag -w para o cli:

webpack -w

Com isso, para deixar o workflow mais versátil você pode definir dois scripts no package.json:

"scripts": {  
  "build:watch": "webpack -w --mode development",  
  "build:prod": "webpack --mode production"  
},

Assim, durante o desenvolvimento você usa npm run build:watch para rodar em modo development com a opção watch, e para mandar o código para produção você usa npm run build:prod.


O que vem pela frente

Nos próximos artigos da série vamos falar de loaders, para tratar outros formatos de arquivos, plugins, para realizar diferentes tarefas e apresentar cenários mais avançados.

Feedbacks?

Qualquer crítica ou sugestão, comente ou entre em contato.

Referências

Tags:

Sobre o Autor

Douglas Matoso
Douglas Matoso

Desenvolvedor web desde 2008. Criador do Web Dev Drops.

0 Comentários

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *