Mais fácil, mais amigável e muito mais robusto, webpack 2 é a solução para aplicações web!

Introdução ao webpack 2

webpack 2 será lançado assim que a documentação estiver completa. Mas isso não quer dizer que você não pode começar a usar a versão 2 agora, basta saber como configura-lá.

O que é o webpack?

Sendo direto, webpack é um empacotador de módulos para JavaScript. Porém, desde seu lançamento, ele, na verdade, funciona mais como um gerenciador para todo o seu código front-end (isso aconteceu intencionalmente ou, pela vontade da comunidade).

O antigo processo dos automatizadores de tarefas: seu HTML, CSS e JS funcionam isoladamente. Você deve gerenciar cada um separadamente, e é seu trabalho garantir que tudo funcione em produção corretamente.

Um automatizador de tarefa como Gulp, pode cuidar de vários transpiladores e pré-processadores diferentes, mas, em todos esses casos, ele irá receber um arquivo de entrada e transformar isso em um arquivo compilado de saída. O problema, é que ele funciona baseado em arquivo-por-arquivo, sem se preocupar com o sistema de um modo geral. Esse é o fardo do desenvolvedor: continuar de onde o automatizador de tarefas parou e encontrar uma maneira adequada de unir todas essas partes para que tudo funcione em produção.

webpack tenta diminuir essa carga do desenvolvedor, realizando uma abordagem ousada: “E se nós tivéssemos uma parte do processo de desenvolvimento que cuidasse das dependências por conta própria? E se pudéssemos simplesmente escrever nosso código, de uma maneira que, o processo de empacotamento cuidasse de si próprio, baseado apenas no que é necessário gerar no final?”

Se você faz parte da comunidade web nos últimos anos, você já sabe que o método favorito de resolver problemas é: “crie isso em JavaScript”. Então, webpack tenta criar um processo simples de empacotamento passando tudo pelo JavaScript. O bacana é, seu verdadeiro poder não está no design de gerenciamento automático de dependencias, o ponto principal, é que todo esse gerenciamento é feito através de 100% JavaScript (com Node.js por baixo dos panos). webpack nós dá a possibilidade de escrever um processo, usando JavaScript, que no final, tem um sentido mais real para projetos de larga escala.

Em outras palavras, você não escreve um configuração para o webpack, você escreve uma configuração para o seu projeto!

Resumindo, se você já se frustou com algo como:

  • Acidentalmente incluiu estilos e bibliotecas JS que você não precisa no seu arquivo final
  • Problemas com escopo na sequência de bibliotecas — tanto em CSS quanto em JS
  • Procurando um bom sistema para usar módulos Node/Bower no seu JavaScript, muitas vezes, utilizando algumas magias negras e configurações no back-end
  • Precisando otimizar a distribuição de dependenciâs, mas com medo de quebrar alguma coisa

…então, você irá se beneficiar utilizando webpack. Ele cuida de todos os pontos acima com muito pouco esforço, delegando esses problemas para o JavaScript, ao invés da sua cabeça. A melhor parte? webpack pode rodar puramente no back-end, significando que você construir sites que melhoram a experiência do usuário progressivamente.


Primeiros Passos

Vamos usar Yarn nesse artigo, (se você está no macOS, brew install yarn), ao invés do npm, mas, é totalmente opcional, eles fazem a mesma coisa.

Vamos criar uma nova pasta a partir do terminal e adicionar o webpack 2 nesse novo projeto:

mkdir webpack2 && cd webpack2
yarn add --dev webpack@2.1.0-beta.25 webpack-dev-server@2.1.0-beta.10

Em seguida, vamos declarar a seguinte configuração para o webpack, copie e cole em um novo arquivo chamado webpack.config.js na raiz do seu projeto:

const webpack = require("webpack");
module.exports = {
context: __dirname + "/src",
entry: {
app: "./app.js",
},
output: {
path: __dirname + "/dist",
filename: "[name].bundle.js",
},
};

Nota: __dirname, nesse caso, se refere a raiz do seu projeto.

Lembra que o webpack “sabe” o que acontece no seu projeto? Ele “sabe” porque, primeiramente, ele vai ler seu código (não se preocupado, tudo sob NDA. webpack, basicamente, realiza o seguinte:

  1. Começa procurando um contexto caso a chave context exista (nesse casso, irá olhar a partir da pasta src/)
  2. …procurará por arquivo da chave entry
  3. …irá ler o conteúdo de cada arquivo. Todo import (ES6) ou require() (Node) que aparecer no seu código, será parseado e empacotado para gerar o arquivo final. Ele procura as dependências das dependências das dependências (e assim por diante), até construir a árvore de dependências corretamente — utilizando apenas o que é necessário para essas dependências, e nada mais
  4. A partir daí, webpack empacota tudo e gera os arquivos necessários na pasta definida no output.path, nomeando seus arquivos a partir da configuração no output.filename (perceba que, o [name] será substituído por cada nome dentro da chave entry dinamicamente)

Vamos criar um arquivo, src/app.js, adicionar:

yarn add --dev moment

…e escrever:

import moment from 'moment';
var rightNow = moment().format('MMMM Do YYYY, h:mm:ss a');
console.log( rightNow );
// "October 23rd 2016, 9:30:24 pm"

Se rodarmos:

./node_modules/.bin/webpack -p

Nota: você pode adicionar o webpack globalmente ou utilizar npm-scripts, o parâmetro -p roda em modo de produção minificando o arquivo final.

Isso irá criar o arquivo dist/app.bundle.js que liga a data atual e hora no console. Perceba que o webpack automaticamente importou o módulo moment (aliás, caso você tenha um arquivo moment.js no seu diretório, por padrão, webpack dará prioridade para esse arquivo ao invés do módulo node moment).


Trabalhando com múltiplos arquivos

Você pode especificar qualquer quantidade de chaves entry e output, apenas modificando configuração do webpack:

Múltiplos arquivos, único arquivo final

const webpack = require("webpack");
module.exports = {
context: __dirname + "/src",
entry: {
app: ["./home.js", "./events.js", "./vendor.js"],
},
output: {
path: __dirname + "/dist",
filename: "[name].bundle.js",
},
};

Irá empacotar todos os arquivos .js listados em um único arquivo final chamado dist/app.bundle.js, a ordem do array é respeitada (home.js primeiro, events.js em seguida, etc).

Múltiplos arquivos, múltiplos arquivos finais

const webpack = require("webpack");
module.exports = {
context: __dirname + "/src",
entry: {
home: "./home.js",
events: "./events.js",
contact: "./contact.js",
},
output: {
path: __dirname + "/dist",
filename: "[name].bundle.js",
},
};

Vamos supor que você escolha gerar múltiplos arquivos finais para quebrar seu código em pequenas partes. Você pode alcançar isso com a configuração acima, ela irá gerar os seguintes arquivos: dist/home.bundle.js, dist/events.bundle.js e dist/contact.bundle.js.


Avançado — Dividindo seu código automaticamente com CommonsChunkPlugin

Se você estiver dividindo sua aplicação em vários arquivos finais no seu output (bem útil caso sua aplicação não precise de várias partes no carregamento inicial), é bem possível que, nessas partes, você esteja utilizando uma dependência em comum entre eles, por padrão, webpack irá resolver as dependências unicamente para cada arquivo. Felizmente, webpack tem uma solução interna, podemos utilizar o CommonsChunkPlugin para isso:

module.exports = {
// …
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "commons",
filename: "commons.js",
minChunks: 2,
}),
],
// …
};

Agora, no seus arquivos finais, você terá um chamado common.js que, caso você tenha uma dependência que seja usada no mínimo 2 vezes (veja minChunks), ela será extraída para esse arquivo no final (e você pode fazer otimizações de cache, etc). Claro que, isso irá realizar uma requisição adicional no navegador, porém, você previne o cliente de fazer o download das mesmas bibliotecas mais de uma vez. Existem vários cenários que essa solução é ótima, lembre-se sempre de testar no projeto.


Desenvolvimento

Outro ponto legal, webpack tem o seu próprio servidor de desenvolvimento (nós instalamos no início desse artigo, wepack-dev-server), não importa se você está desenvolvendo um site estático ou prototipando algo, ele é perfeito para ambos. Para utiliza-lo com a nossa configuração atual, podemos adicionar a chave devServer:

module.exports = {
context: __dirname + "/src",
entry: {
app: "./app.js",
},
output: {
filename: "[name].bundle.js",
path: __dirname + "/dist/assets",
publicPath: "/assets", // Nova chave
},
devServer: {
contentBase: __dirname + "/src", // Nova chave
},
};

Agora, nós podemos criar um arquivo src/index.html na nossa pasta e importar nosso arquivo final:

<script src="/assets/app.bundle.js"></script>

…e do nosso terminal, podemos rodar:

./node_modules/.bin/webpack-dev-server

Você terá um servidor rodando em localhost:8080. Perceba como o caminho /assets no nosso HTML é o mesmo caminho da chave output.publicPath — você pode colocar o que você quiser aqui, quando empacotamos para produção, é uma ótima oportunidade para colocar a URL da CDN do seus assets.

webpack irá recarregar o navegador automaticamente, baseado nas mudanças no seu JavaScript. Contudo, qualquer mudança no webpack.config.js, você irá precisar reiniciar o seu servidor.


Exportando como variável global

Em algumas vezes, queremos exportar nosso código para que ele tenha uma nomenclatura global acessível (como window.jQuery), para isso, basta apenas adicionar a chave output.library na sua configuração:

module.exports = {
output: {
library: 'myClassName',
}
};

…e isso irá adicionar myClassName no objeto window, como window.myClassName. Usando esse escopo, você pode chamar qualquer método disponível do seu código (você pode ler mais como exportar métodos globais na documentação).


Loaders

Até agora, nós só aprendemos como utilizar JavaScript no webpack, mas na verdade, nós podemos utiliza qualquer tipo de arquivo (enquanto ele seja carregado via JS).

Nós fazemos isso utilizando os Loaders.

Um loader é básicamente um pré-processador, como o SASS, ou um transpilador, como o Babel. Podemos procurar eles no repositório do NPM, geralmente são nomeados com *-loader, tais como, sass-loader e babel-loader.

Babel + ES6

Se quisermos usar ES6 através do Babel no nosso projeto, primeiramente devemos instalar o loader apropriado:

yarn add --dev babel-loader babel-core babel-preset-es2015

…e então, adicioná-los na nossa configuração:

module.exports = {
// …
module: {
rules: [
{
test: /\.js$/,
use: [{
loader: "babel-loader",
options: { presets: ["es2015"] }
}],
},

// Loaders para outros tipos de arquivo...
],
},
// …
};

Nota para usuários de webpack 1: O conceito principal de loaders continua o mesmo, apenas a sintaxe que mudou. Enquanto a documentação oficial não é finalizada, essa pode ser (ou não pode ser) a sintaxe final

Com isso, webpack irá procurar todos os arquivos que combinam com a expressão regular /\.js$/, e irá utilizar o babel-loader nesses arquivos. webpack utiliza expressões regulares para te dar um controle total — ou seja, não nos limitamos a extensões de arquivos ou muito menos como a estrutura do nosso projeto está.

Por exemplo, talvez a sua pasta /codigo-legado/ não esteja usando ES6, então, você pode muito bem atualizar a expressão acima para /^((?!codigo-legado).)*\.js$/. Fazendo com que a pasta seja ignorada para aquele loader.

CSS + Style Loader

E se quisermos carregar CSS na nossa aplicação? Já sabemos que precisamos passar todos os arquivos pelo JS, então, podemos adicionar no nosso src/app.js:

import styles from './assets/stylesheets/application.css';

Nota: não esqueça de criar esse arquivo CSS na pasta correta!

Se tentarmos rodar a aplicação agora, vamos receber um erro parecido com:

You may need an appropriate loader to handle this file type.

Lembre-se, podemos carregar tudo via JS, nós só precisamos do Loader correto.

Adicione:

yarn add --dev css-loader style-loader

Mude sua configuração:

module.exports = {
// …
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
// …
],
},
};

Nota: Loaders são processados da direita para esquerda, ou seja, css-loader irá ocorrer antes de style-loader e etc.

Uma observação aqui, se você tentar executar o empacotamento para produção, irá perceber que nós estamos carregando o CSS através do JS, e o style-loader está adicionando os estilos manualmente dentro da tag <head> do nosso HTML.

De primeira, parece meio idiota essa abordagem, mas, aos poucos, quanto mais você pensa sobre isso, começa a fazer mais sentido. Você conseguiu salvar uma requisição no navegador — e mais, conseguiu diminuir a latência de uma requisição — e se você estiver carregando toda sua aplicação através do JS, essa abordagem elimina completamente o FOUC.

Você irá notar também — e isso é padrão de fábrica — que o webpack também resolve qualquer declaração @import no seu CSS, parseando essas URL’s e importando o conteúdo delas no seu arquivo principal (ao invés de deixar o comportamento padrão da declaração @import no CSS, que resulta em requisições extras para cada declaração e etc).

Carregar CSS pelo JS é demais! É demais porque agora nós podemos pensar em métodos de modularização para o nosso CSS. Vamos supor que nós carregaremos button.css através do button.js. Isso significa que, se button.js nunca for usado, esse CSS não irá aumentar o arquivo de produção final. Se você utilizar alguma prática CSS orientada a componentes, como SMACSS ou BEM, você irá visualizar o valor de unir CSS, HTML e JS cada vez mais.

CSS + Módulos Node

Podemos tirar vantagem do webpack no carregamento de módulos Node, podemos adicionar ~ na nossa declaração @import. Isso irá dizar ao webpack para procurar aquele módulo como um módulo Node.

Vamos utilizar o normalize.css aqui, primeiro, vamos adiciona-lo com Yarn:

yarn add normalize.css

E agora, podemos utilizar isso no CSS:

@import "~normalize.css";

…e com isso, nós usamos todo o ecossistema criado no NPM para manter e cuidar de bibliotecas de terceiros para nós — versionamento, correção de bugs, etc — sem precisar copiar e colar nenhum código. E somando isso, toda a vantagem do webpack ao importar e empacotar as declarações @import no seu CSS principal.

CSS Modules

Você deve ter ouvido falar sobre CSS Modules, ele remove o C do CSS. Um escopo único para se trabalhar com cada componente e/ou arquivo CSS importado (uma hash única é gerada para isolar suas declarações do escopo global), muito útil quando trabalhamos no CSS, HTML e JS próximos um do outro. Você pode aprender mais sobre CSS Modules nesse link. Se você tem planos em usá-lo, nosso css-loader já está preparada para isso, basta adicionar a seguinte configuração:

module.exports = {
// …
module: {
rules: [
{
test: /\.css$/,
use: [
"style-loader",
{ loader: "css-loader", options: { modules: true } }
],
},
// …
],
},
};

Nota: perceba que estamos passando um objeto de configuração para utilizar o css-loader, você também pode configurar passando uma string, use: [“style-loader”, “css-loader?modules”].

SASS

Precisa utilizar SASS? Sem problema, instale o loader:

yarn add --dev sass-loader node-sass

E adicione outra regra:

module.exports = {
// …
module: {
rules: [
{
test: /\.(sass|scss)$/,
use: [
"style-loader",
"css-loader",
"sass-loader",
]
}
// …
],
},
};

Agora, basta você importar no seu JavaScript arquivos terminando em .scss ou .sass e webpack irá cuidar de tudo.

Gerando CSS final separadamente

Você pode estar trabalhando em uma aplicação que melhore a experiência do usuário progressivamente, ou você simplesmente precisa de um arquivo CSS separado para alguma outra razão. Nós podemos fazer isso trocando nosso style-loader pelo plugin extract-text-webpack-plugin na nossa configuração, sem precisar mexer em nada no nosso código.

A configuração inclui 3 etapas:

  1. Adicionar o plugin como dependência do projeto
  2. Mudar a regra do loader para utilizar o plugin
  3. Adicionar o plugin no array de plugins do webpack

No nosso exemplo:

import styles from './assets/stylesheets/application.css';

Vamos instalar o plugin localmente (no momento que escrevo esse artigo, ainda precisamos instalar a versão beta para utilizar no webpack 2):

yarn add --dev extract-text-webpack-plugin@2.0.0-beta.4

…atualizamos nossa configuração:

const ExtractTextPlugin = require("extract-text-webpack-plugin"); [1]
module.exports = {
// …
module: {
rules: [
{
test: /\.css$/,
use: [
ExtractTextPlugin.extract("css"), [2]
"css-loader",
],
},

// …
]
},
plugins: [
new ExtractTextPlugin({ [3]
filename: "[name].bundle.css",
allChunks: true,
}),
],
};

Nota: as chaves com números na configuração acima são os itens da lista anterior.

Agora, se rodarmos webpack -p, você irá notar que temos um novo arquivo app.bundle.css no nosso diretório final. Agora basta adicionar uma tag no seu HTML.

HTML

Se você chegou até aqui, pode imaginar que nós temos um loader apropriado para trabalhar com HTML, chama-se html-loader. Apesar de não fazer muito sentido nas aplicações de hoje me dia, afinal, você basicamente irá trabalhar com Angular, React, Vue ou Ember.

Ou, caso você precise de um template engine, você pode usar JSX, Mustache ou Handlebars.

É bacana saber que você pode carregar marcação com webpack (incluindo até, arquivos markdown com code highlight). Talvez seja útil para você em algum caso.


Pensando em módulos

Para extrair o melhor do webpack, eu recomendo você pensar em módulos — pequenos, reutilizáveis e autônomos — que fazem apenas uma coisa, e uma coisa bem feita.

Isso significa, pegar algo como:

└── js/
└── application.js // 300KB de código

…e transformar ele em:

└── js/
├── components/
│ ├── button.js
│ ├── calendar.js
│ ├── comment.js
│ ├── modal.js
│ ├── tab.js
│ ├── timer.js
│ ├── video.js
│ └── wysiwyg.js

└── application.js // ~ 1KB de código; includingo apenas imports da pasta ./components/

O resultado é um código mais limpo e re-usável. Cada componente importa sua própria dependência e exporta apenas o que deve ser público para os outros módulos. Some isso a Babel + ES6 e você pode utilizar classes no JavaScript para modularização, e o mais legal, você não precisa pensar em escopo!

Se quiser saber mais sobre módulos, Preethi Kasreddy escreveu um ótimo artigo sobre.


Créditos