A complexidade indispensável do desenvolvimento front-end de hoje!

O que há de novo no webpack 2?

Eduardo Rabelo
6 min readJun 19, 2016

Muita coisa mudou! Vários problemas resolvidos e muitos projetos para atualizar, veja o que está por vir na nossa complexidade favorita de hoje em dia!

Mudanças principais

As novidades abaixo são referentes à versão 2.0.5-beta+.

Módulos ES6

webpack 2 trás suporte nativo para módulos ES6. Isso significa, que agora, o webpack entende import e export sem precisar transforma-los em módulos CommonJS:

import { pagina, proxPagina } from “./livro”;pagina === 0;
proxPagina();
pagina === 1;

E então:

// livro.jsexport var pagina = 0;export function proxPagina() {
pagina++;
}
export default “Isso é um livro”;

Dividindo seu código com ES6

A especificação do ES6 usa, por padrão, System.import para importar módulos de maneira dinâmica em tempo de execução.

webpack trata o System.import como ponto de divisão e coloca o módulo importado em um pedaço separado (chunk, em inglês).

System.import recebe o nome do módulo como argumento e retorna uma Promise.

function onClick() {System.import(“./meuModulo”).then(module => {
module.default;
}).catch(err => {
console.log(“Erro ao carregar o módulo”);
});
}

Boa novidade: A falha no carregamento de um pedaço separado agora pode ser tratado.

Expressões dinâmicas

É possível passar uma expressão parcial para o System.import. Elas são tratadas de um jeito semelhante as expressões do CommonJS (webpack cria um contexto com todos os arquivos possíveis).

function route(path, query) {
return System.import(“./routes/” + path + “/route”)
.then(route => new route.Route(query));
}
// Isso cria um pedaço separado (chunk) para cada rota possível.

Misturando módulos ES6, AMD e CommonJS

Enquanto AMD e CommonJS você pode livremente misturar os três tipos de importação (mesmo dentro de um arquivo). webpack funciona, neste caso, semelhante ao Babel:

// CommonJS consumindo um módulo ES6
var livro = require(“./livro”);
livro.pagina;
livro.proxPagina();
livro.default === “Isso é um livro”;

E então:

// Módulo ES6 consumindo CommonJS
import fs from “fs”; // module.exports é mapeado para `default`;
import { readFileSync } from “fs”; // exportações nomeadas são lidas do objeto retornado
typeof fs.readFileSync === “function”;
typeof readFileSync === “function”;

Babel e webpack

O preset do Babel es2015, por padrão, transforma todos os módulos ES6 em módulos CommonJS. Para usar o webpack para processar os módulos ES6, você deve usar o preset es2015-webpack.

Otimizações específicas para ES6

A natureza estática dos módulos ES6 permitem um novo tipo de otimização. Por exemplo, em vários casos, é possível analisar quais exports estão sendo usados e quais não.

Em casos onde o webpack analisar que o export não está sendo usado, ele irá omitir essa declaração para outros módulos. Em uma etapa seguinte, o minimizador irá marcar essa declaração como não usada e irá omitir o mesmo do arquivo final.

Nos casos a seguir, é possível essa análise:

  • imports nomeados
  • imports padrões
  • re-exportação

Nos casos a seguir, não é possível essa análise:

  • import * as
  • CommonJS ou AMD consumindo módulos ES6
  • System.import

Transformando exports do ES6

Em casos onde é possível analisar o uso do export, webpack pode transformar os exports para propriedades de apenas uma letra.

Variáveis de ambiente

No passado, variáveis de ambiente eram muito usadas para cuidar de diferentes configurações. webpack 2 trás uma nova maneira de passar opções para suas configurações.

O arquivo de configuração pode exportar uma função que retorna a configuração. A função é chamada pela CLI e o valor é passado pelo parâmetro --env para a função de configuração.

Você pode passar como string (--env dev, será, "dev") ou um objeto complexo de opções (--env.minimize --env.server localhost, será, { minimize: true, server: "localhost" }). Eu recomendo o uso de um objeto, pelo simples fato de ser mais flexível, mas vai de você!

E então:

// webpack.config.babel.js
exports default function(options) {
return {
// ...
devtool: options.dev ?
"cheap-module-eval-source-map" :
"hidden-source-map"
};
}

Opções para o resolve

Houve uma grande refatoração no módulo resolve. Isso significa que as opções também mudaram. Todas para simplificar a configuração e evitar configurações de formas incorretas.

As novas opções são:

{
modules: [path.resolve(__dirname, “app”), “node_modules”]
// (essa opção era dividida em `root`, `modulesDirectories` e
// `fallback` nas opções antigas)
// Onde o resolver iria olhar cada pasta na procura de módulos
// caminhos relativos são procurados em cara pasta pai (como
// node_modules)
// caminhos absolutos são procurados diretamente
// a ordem é respeitada
descriptionFiles: [“package.json”, “bower.json”],
// Esses arquivos JSON serão lidos nos diretórios

mainFields: [“main”, “browser”],
// Esses campos nos arquivos de descrição serão procurados
// primeiramente ao tentar resolver o diretório

mainFiles: [“index”]
// Esses arquivos serão procurados ao tentar resolver o
// diretório

aliasFields: [“browser”],
// Esses campos nos arquivos de descrição oferecem
// um alias para esse pacote
// O conteúdo desses campos é um objeto que mapea
// sua chave ao valor correspondente
extensions: [“.js”, “.json”],
// Essas extensões são procuradas ao resolver o arquivo
enforceExtension: false,
// Se conter o valor `false`, também tentará não usar extensões
// para resolver o arquivo
// baseado na lista `extensions`
moduleExtensions: [“-loader”],
// Essas extensões são procuradas ao resolver o módulo

enforceModuleExtension: false,
// Se conter o valor `false`, também tentará não
// usar extensões para resolver o módulo
// baseado na lista `moduleExtensions`
alias: {
jquery: path.resolve(__dirname, “vendor/jquery-2.0.0.js”)
} // Esses alias são usados ao tentar resolver o módulo
}

Alterações menores, mas significativas!

As mudanças separam os conceitos de configuração e parametrização de plugins, otimizações e polyfills.

Polyfill para Promise

O carregador de arquivos divididos (chunk loader), é baseado em Promise. Isso significa, que você precisa prover um polyfill para Promise em navegadores antigos.

A especificação do ES6 usa promises e eu não quero incluir um polyfill em todos os bundles. Então, depende de você, baseado na sua aplicação, disponibilizar um polyfill, se necessário.

Verifique o suporte nativo em Can I use Promises?.

Outros polyfills

Você precisa definir um polyfill para Object.defineProperty se estiver usando módulos ES6, ou, se estiver usando o objeto module de outras maneiras do que module.exports, module.id, module.loaded ou module.hot.

Para módulos ES6, você também precisa do polyfill para Function.prototype.bind.

Não é novidade, mas você também precisa do polyfill Object.keys para require.context().keys().

Configurações para carregamento de módulos

Agora, a configuração para carregar módulos vai de encontro com resourcePath ao invés de resource. Isso significa que os parâmetros de query string não serão includos para encontrar seus recursos.

Um exemplo, era um problema com o Bootstrap, que complicava o valor da chave test na procura de fontes e images, mudando /\.svg$/ para /\.svg($|\?)/. Agora, você pode usar a forma mais simples.

Os carregadores agora resolvem os caminhos baseado no arquivo de configuração (ou a chave context, se especificado). Isso deve resolver alguns problemas com módulos usando npm link, que estavam fora do projeto atual.

Junto de outras mudanças, agora é possível escrever a seguinte configuração:

loaders: [
{
test: /\.css$/,
loaders: [
“style-loader”,
{ loader: “css-loader”, query: { modules: true } },
{
loader: “sass-loader”,
query: {
includePaths: [
path.resolve(__dirname, “minha-pasta”)
]
}
}
]
}
]

Optimizações de carregamento e minimização

O plugin UglifyJsPlugin não coloca mais os carregadores em modo de minimização por padrão. A opção debug foi removida. Agora, os carregadores não devem ler suas configurações do arquivo do webpack, ao invés disso,você deve prover as opções através do plugin LoaderOptionsPlugin.

new webpack.LoaderOptionsPlugin({  test: /\.css$/,
// opcionalmente passando o campo `test`, `include` ou `exclude`
// se não conter o campo `test`, por padrão, afeta todos os
// carregadores

minimize: true,
debug: false,
options: {
// passar opções para o carregador
}
})

Isso acontece para separação de conceitos. Eu quero desabilitar chaves arbitrárias na configuração, possibilitando uma validação da configuração.

Plugins

Muitos plugins agora recebem um objeto de configuração ou invés de múltiplos argumentos. Isso acontece porque é mais fácil de extender. Eles retornam um erro quando o estilo antigo de argumentos é usado.

Comunicação com HMR

No webpack 1, o sinal de atualização usava o Web Messaging API (postMessage). No webpack 2, usa o padrão de emissão de eventos. Isso significa que WebSockets será injetado no bundle.

Agora, webpack-dev-server injeta esse modo por padrão.

Isso deve permitir ao webpack-dev-server atualizar código em WebWorkers.

Ordem de ocorrência

O plugin não é mais necessário, ordem de ocorrência agora é padrão.

Divisão de código.

require.ensure e AMD require, agora por padrão são assíncronos, mesmo se o pedaço de arquivo (chunk) já estiver carregado.

Créditos

--

--