Mantenha o controle: um guia para Webpack e React, Pt. 1
Publicados: 2022-03-11Ao iniciar um novo projeto React, você tem muitos modelos para escolher: Create React App, react-boilerplate
e React Starter Kit, para citar alguns.
Esses modelos, adotados por milhares de desenvolvedores, são capazes de dar suporte ao desenvolvimento de aplicativos em grande escala. Mas eles deixam a experiência do desenvolvedor e agrupam a saída sobrecarregada com vários padrões, o que pode não ser o ideal.
Se você deseja manter um maior grau de controle sobre o processo de compilação, pode optar por investir em uma configuração personalizada do Webpack. Como você aprenderá neste tutorial do Webpack, essa tarefa não é muito complicada, e o conhecimento pode até ser útil ao solucionar problemas de configurações de outras pessoas.
Webpack: Introdução
A maneira como escrevemos JavaScript hoje é diferente do código que o navegador pode executar. Frequentemente, contamos com outros tipos de recursos, linguagens transpiladas e recursos experimentais que ainda não foram suportados em navegadores modernos. O Webpack é um empacotador de módulos para JavaScript que pode preencher essa lacuna e produzir código compatível com vários navegadores sem nenhum custo quando se trata de experiência do desenvolvedor.
Antes de começarmos, você deve ter em mente que todo o código apresentado neste tutorial do Webpack também está disponível na forma de um arquivo de configuração de exemplo Webpack/React completo no GitHub. Por favor, sinta-se à vontade para consultá-lo lá e voltar a este artigo se tiver alguma dúvida.
Configuração básica
Desde o Legato (versão 4), o Webpack não requer nenhuma configuração para ser executado. Escolher um modo de construção aplicará um conjunto de padrões mais adequado ao ambiente de destino. No espírito deste artigo, vamos deixar esses padrões de lado e implementar uma configuração sensata para cada ambiente de destino.
Primeiro, precisamos instalar webpack
e webpack-cli
:
npm install -D webpack webpack-cli
Em seguida, precisamos preencher o webpack.config.js
com uma configuração com as seguintes opções:
-
devtool
: Habilita a geração do mapa de origem no modo de desenvolvimento. -
entry
: O arquivo principal do nosso aplicativo React. -
output.path
: O diretório raiz para armazenar os arquivos de saída. -
output.filename
: O padrão de nome de arquivo a ser usado para arquivos gerados. -
output.publicPath
: O caminho para o diretório raiz onde os arquivos serão implantados no servidor web.
const path = require("path"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; const isDevelopment = !isProduction; return { devtool: isDevelopment && "cheap-module-source-map", entry: "./src/index.js", output: { path: path.resolve(__dirname, "dist"), filename: "assets/js/[name].[contenthash:8].js", publicPath: "/" } }; };
A configuração acima funciona bem para arquivos JavaScript simples. Mas ao usar Webpack e React, precisaremos realizar transformações adicionais antes de enviar o código para nossos usuários. Na próxima seção, usaremos o Babel para alterar a maneira como o Webpack carrega os arquivos JavaScript.
Carregador JS
Babel é um compilador JavaScript com muitos plugins para transformação de código. Nesta seção, vamos apresentá-lo como um carregador em nossa configuração do Webpack e configurá-lo para transformar o código JavaScript moderno em tal que seja entendido por navegadores comuns.
Primeiro, precisaremos instalar babel-loader
e @babel/core
:
npm install -D @babel/core babel-loader
Em seguida, adicionaremos uma seção de module
à nossa configuração do Webpack, tornando o babel-loader
responsável por carregar arquivos JavaScript:
@@ -11,6 +11,25 @@ module.exports = function(_env, argv) { path: path.resolve(__dirname, "dist"), filename: "assets/js/[name].[contenthash:8].js", publicPath: "/" + }, + module: { + rules: [ + { + test: /\.jsx?$/, + exclude: /node_modules/, + use: { + loader: "babel-loader", + options: { + cacheDirectory: true, + cacheCompression: false, + envName: isProduction ? "production" : "development" + } + } + } + ] + }, + resolve: { + extensions: [".js", ".jsx"] } }; };
Vamos configurar o Babel usando um arquivo de configuração separado, babel.config.js
. Ele usará os seguintes recursos:
-
@babel/preset-env
: transforma recursos JavaScript modernos em código compatível com versões anteriores. -
@babel/preset-react
react : Transforma a sintaxe JSX em chamadas de função JavaScript simples. -
@babel/plugin-transform-runtime
: Reduz a duplicação de código extraindo os auxiliares Babel em módulos compartilhados. -
@babel/plugin-syntax-dynamic-import
: Habilita a sintaxe de importação dinâmicaimport()
em navegadores sem suporte nativo aoPromise
. -
@babel/plugin-proposal-class-properties
: Habilita o suporte para a proposta de sintaxe de campo de instância pública, para escrever componentes React baseados em classe.
Também habilitaremos algumas otimizações de produção específicas do React:
-
babel-plugin-transform-react-remove-prop-types
remove prop-types desnecessários do código de produção. -
@babel/plugin-transform-react-inline-elements
avaliaReact.createElement
durante a compilação e inline o resultado. -
@babel/plugin-transform-react-constant-elements
extrai elementos React estáticos como constantes.
O comando abaixo irá instalar todas as dependências necessárias:
npm install -D @babel/preset-env @babel/preset-react @babel/runtime @babel/plugin-transform-runtime @babel/plugin-syntax-dynamic-import @babel/plugin-proposal-class-properties babel-plugin-transform-react-remove-prop-types @babel/plugin-transform-react-inline-elements @babel/plugin-transform-react-constant-elements
Em seguida, preencheremos babel.config.js
com estas configurações:
module.exports = { presets: [ [ "@babel/preset-env", { modules: false } ], "@babel/preset-react" ], plugins: [ "@babel/plugin-transform-runtime", "@babel/plugin-syntax-dynamic-import", "@babel/plugin-proposal-class-properties" ], env: { production: { only: ["src"], plugins: [ [ "transform-react-remove-prop-types", { removeImport: true } ], "@babel/plugin-transform-react-inline-elements", "@babel/plugin-transform-react-constant-elements" ] } } };
Essa configuração nos permite escrever JavaScript moderno de forma compatível com todos os navegadores relevantes. Existem outros tipos de recursos que podemos precisar em um aplicativo React, que abordaremos nas seções a seguir.
Carregador de CSS
Quando se trata de estilizar aplicativos React, no mínimo, precisamos incluir arquivos CSS simples. Vamos fazer isso no Webpack usando os seguintes carregadores:
-
css-loader
: analisa arquivos CSS, resolvendo recursos externos, como imagens, fontes e importações de estilo adicionais. -
style-loader
: Durante o desenvolvimento, injeta estilos carregados no documento em tempo de execução. -
mini-css-extract-plugin
: Extrai estilos carregados em arquivos separados para uso em produção para aproveitar o cache do navegador.
Vamos instalar os carregadores CSS acima:
npm install -D css-loader style-loader mini-css-extract-plugin
Em seguida, adicionaremos uma nova regra à seção module.rules
de nossa configuração do Webpack:
@@ -1,4 +1,5 @@ const path = require("path"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -25,6 +26,13 @@ module.exports = function(_env, argv) { envName: isProduction ? "production" : "development" } } + }, + { + test: /\.css$/, + use: [ + isProduction ? MiniCssExtractPlugin.loader : "style-loader", + "css-loader" + ] } ] },
Também adicionaremos MiniCssExtractPlugin
à seção de plugins
, que só ativaremos no modo de produção:
@@ -38,6 +38,13 @@ module.exports = function(_env, argv) { }, resolve: { extensions: [".js", ".jsx"] - } + }, + plugins: [ + isProduction && + new MiniCssExtractPlugin({ + filename: "assets/css/[name].[contenthash:8].css", + chunkFilename: "assets/css/[name].[contenthash:8].chunk.css" + }) + ].filter(Boolean) }; };
Essa configuração funciona para arquivos CSS simples e pode ser estendida para trabalhar com vários processadores CSS, como Sass e PostCSS, que discutiremos no próximo artigo.
Carregador de imagens
O Webpack também pode ser usado para carregar recursos estáticos, como imagens, vídeos e outros arquivos binários. A maneira mais genérica de lidar com esses tipos de arquivos é usando file-loader
ou url-loader
, que fornecerá uma referência de URL para os recursos necessários para seus consumidores.
Nesta seção, adicionaremos o url-loader
para lidar com formatos de imagem comuns. O que diferencia url-loader
do file-loader
é que, se o tamanho do arquivo original for menor que um determinado limite, ele incorporará o arquivo inteiro na URL como conteúdo codificado em base64, eliminando assim a necessidade de uma solicitação adicional.
Primeiro instalamos url-loader
:
npm install -D url-loader
Em seguida, adicionamos uma nova regra à seção module.rules
de nossa configuração do Webpack:
@@ -34,6 +34,16 @@ module.exports = function(_env, argv) { isProduction ? MiniCssExtractPlugin.loader : "style-loader", "css-loader" ] + }, + { + test: /\.(png|jpg|gif)$/i, + use: { + loader: "url-loader", + options: { + limit: 8192, + name: "static/media/[name].[hash:8].[ext]" + } + } } ] },
SVG
Para imagens SVG, usaremos o carregador @svgr/webpack
, que transforma arquivos importados em componentes React.
Instalamos @svgr/webpack
:
npm install -D @svgr/webpack
Em seguida, adicionamos uma nova regra à seção module.rules
de nossa configuração do Webpack:
@@ -44,6 +44,10 @@ module.exports = function(_env, argv) { name: "static/media/[name].[hash:8].[ext]" } } + }, + { + test: /\.svg$/, + use: ["@svgr/webpack"] } ] },
Imagens SVG como componentes React podem ser convenientes, e @svgr/webpack
realiza otimização usando SVGO.

Nota: Para certas animações ou mesmo efeitos de mouseover, pode ser necessário manipular o SVG usando JavaScript. Felizmente, @svgr/webpack
incorpora o conteúdo SVG no pacote JavaScript por padrão, permitindo que você ignore as restrições de segurança necessárias para isso.
Carregador de arquivos
Quando precisarmos referenciar qualquer outro tipo de arquivo, o file-loader
genérico fará o trabalho. Ele funciona de maneira semelhante ao url-loader
, fornecendo um URL de recurso para o código que o requer, mas não faz nenhuma tentativa de otimizá-lo.
Como sempre, primeiro instalamos o módulo Node.js. Nesse caso, file-loader
:
npm install -D file-loader
Em seguida, adicionamos uma nova regra à seção module.rules
de nossa configuração do Webpack. Por exemplo:
@@ -48,6 +48,13 @@ module.exports = function(_env, argv) { { test: /\.svg$/, use: ["@svgr/webpack"] + }, + { + test: /\.(eot|otf|ttf|woff|woff2)$/, + loader: require.resolve("file-loader"), + options: { + name: "static/media/[name].[hash:8].[ext]" + } } ] },
Aqui adicionamos file-loader
para carregar fontes, que você pode referenciar em seus arquivos CSS. Você pode estender este exemplo para carregar quaisquer outros tipos de arquivos necessários.
Plugin de ambiente
Podemos usar DefinePlugin DefinePlugin()
do Webpack para expor variáveis de ambiente do ambiente de compilação para o código do nosso aplicativo. Por exemplo:
@@ -1,5 +1,6 @@ const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -65,7 +66,12 @@ module.exports = function(_env, argv) { new MiniCssExtractPlugin({ filename: "assets/css/[name].[contenthash:8].css", chunkFilename: "assets/css/[name].[contenthash:8].chunk.css" - }) + }), + new webpack.DefinePlugin({ + "process.env.NODE_ENV": JSON.stringify( + isProduction ? "production" : "development" + ) + }) ].filter(Boolean) }; };
Aqui substituímos process.env.NODE_ENV
por uma string representando o modo de construção: "development"
ou "production"
.
Plug-in HTML
Na ausência de um arquivo index.html
, nosso pacote JavaScript é inútil, apenas parado sem que ninguém consiga encontrá-lo. Nesta seção, apresentaremos o html-webpack-plugin
para gerar um arquivo HTML para nós.
Instalamos html-webpack-plugin
:
npm install -D html-webpack-plugin
Em seguida, adicionamos html-webpack-plugin
à seção de plugins
da nossa configuração do Webpack:
@@ -1,6 +1,7 @@ const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const webpack = require("webpack"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -71,6 +72,10 @@ module.exports = function(_env, argv) { "process.env.NODE_ENV": JSON.stringify( isProduction ? "production" : "development" ) + }), + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, "public/index.html"), + inject: true }) ].filter(Boolean) };
O arquivo public/index.html
gerado carregará nosso pacote e inicializará nosso aplicativo.
Otimização
Existem várias técnicas de otimização que podemos usar em nosso processo de construção. Começaremos com a minificação de código, um processo pelo qual podemos reduzir o tamanho do nosso pacote sem nenhum custo em termos de funcionalidade. Usaremos dois plugins para minimizar nosso código: terser-webpack-plugin
para código JavaScript e optimize-css-assets-webpack-plugin
para CSS.
Vamos instalá-los:
npm install -D terser-webpack-plugin optimize-css-assets-webpack-plugin
Em seguida, adicionaremos uma seção de optimization
à nossa configuração:
@@ -2,6 +2,8 @@ const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); +const TerserWebpackPlugin = require("terser-webpack-plugin"); +const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -75,6 +77,27 @@ module.exports = function(_env, argv) { isProduction ? "production" : "development" ) }) - ].filter(Boolean) + ].filter(Boolean), + optimization: { + minimize: isProduction, + minimizer: [ + new TerserWebpackPlugin({ + terserOptions: { + compress: { + comparisons: false + }, + mangle: { + safari10: true + }, + output: { + comments: false, + ascii_only: true + }, + warnings: false + } + }), + new OptimizeCssAssetsPlugin() + ] + } }; };
As configurações acima garantirão a compatibilidade do código com todos os navegadores modernos.
Divisão de código
A divisão de código é outra técnica que podemos usar para melhorar o desempenho do nosso aplicativo. A divisão de código pode se referir a duas abordagens diferentes:
- Usando uma instrução
import()
dinâmica, podemos extrair partes do aplicativo que compõem uma parte significativa do tamanho do nosso pacote e carregá-las sob demanda. - Podemos extrair o código que muda com menos frequência, a fim de aproveitar o cache do navegador e melhorar o desempenho dos visitantes recorrentes.
Vamos preencher a seção optimization.splitChunks
da nossa configuração do Webpack com configurações para extrair dependências de terceiros e partes comuns em arquivos separados:
@@ -99,7 +99,29 @@ module.exports = function(_env, argv) { sourceMap: true }), new OptimizeCssAssetsPlugin() - ] + ], + splitChunks: { + chunks: "all", + minSize: 0, + maxInitialRequests: 20, + maxAsyncRequests: 20, + cacheGroups: { + vendors: { + test: /[\\/]node_modules[\\/]/, + name(module, chunks, cacheGroupKey) { + const packageName = module.context.match( + /[\\/]node_modules[\\/](.*?)([\\/]|$)/ + )[1]; + return `${cacheGroupKey}.${packageName.replace("@", "")}`; + } + }, + common: { + minChunks: 2, + priority: -10 + } + } + }, + runtimeChunk: "single" } }; };
Vamos dar uma olhada mais profunda nas opções que usamos aqui:
-
chunks: "all"
: Por padrão, a extração de chunk comum afeta apenas os módulos carregados com umimport()
dinâmico. Essa configuração também permite a otimização para carregamento de ponto de entrada. -
minSize: 0
: Por padrão, apenas os fragmentos acima de um determinado limite de tamanho se qualificam para extração. Essa configuração permite a otimização de todos os códigos comuns, independentemente de seu tamanho. -
maxInitialRequests: 20
emaxAsyncChunks: 20
: Essas configurações aumentam o número máximo de arquivos de origem que podem ser carregados em paralelo para importações de ponto de entrada e importações de ponto de divisão, respectivamente.
Além disso, especificamos a seguinte configuração cacheGroups
:
-
vendors
: Configura a extração para módulos de terceiros.-
test: /[\\/]node_modules[\\/]/
: Padrão de nome de arquivo para correspondência de dependências de terceiros. -
name(module, chunks, cacheGroupKey)
: Agrupa pedaços separados do mesmo módulo, dando-lhes um nome comum.
-
-
common
: Configura a extração de partes comuns do código do aplicativo.-
minChunks: 2
: Um pedaço será considerado comum se referenciado em pelo menos dois módulos. -
priority: -10
: Atribui uma prioridade negativa ao grupo de cachecommon
para que os fragmentos do grupo de cache devendors
sejam considerados primeiro.
-
Também extraímos o código de tempo de execução do Webpack em um único bloco que pode ser compartilhado entre vários pontos de entrada, especificando runtimeChunk: "single"
.
Servidor de desenvolvimento
Até agora, nos concentramos em criar e otimizar a compilação de produção de nosso aplicativo, mas o Webpack também possui seu próprio servidor web com recarga ao vivo e relatórios de erros, o que nos ajudará no processo de desenvolvimento. É chamado webpack-dev-server
e precisamos instalá-lo separadamente:
npm install -D webpack-dev-server
Neste trecho, introduzimos uma seção devServer
em nossa configuração do Webpack:
@@ -120,6 +120,12 @@ module.exports = function(_env, argv) { } }, runtimeChunk: "single" + }, + devServer: { + compress: true, + historyApiFallback: true, + open: true, + overlay: true } }; };
Aqui usamos as seguintes opções:
-
compress: true
: habilita a compactação de ativos para recargas mais rápidas. -
historyApiFallback: true
: habilita um fallback paraindex.html
para roteamento baseado em histórico. -
open: true
: abre o navegador após iniciar o servidor dev. -
overlay: true
: Exibe os erros do Webpack na janela do navegador.
Você também pode precisar definir as configurações de proxy para encaminhar solicitações de API para o servidor de back-end.
Webpack e React: otimizado para desempenho e pronto!
Acabamos de aprender como carregar vários tipos de recursos com o Webpack, como usar o Webpack em um ambiente de desenvolvimento e várias técnicas para otimizar uma compilação de produção. Se precisar, você sempre pode revisar o arquivo de configuração completo para se inspirar para sua própria configuração do React/Webpack. Aprimorar essas habilidades é uma tarifa padrão para qualquer pessoa que ofereça serviços de desenvolvimento React.
Na próxima parte desta série, expandiremos essa configuração com instruções para casos de uso mais específicos, incluindo uso de TypeScript, pré-processadores CSS e técnicas avançadas de otimização envolvendo renderização no lado do servidor e ServiceWorkers. Fique atento para saber tudo o que você precisa saber sobre o Webpack para levar seu aplicativo React à produção.