Repositório de estudos do Webpack para aplicações javascript.
- O que é o Webpack?
- Iniciando o Webpack
- Configurações básicas
- Entradas (Entry Point)
- Saída (Output)
- Loaders
- Plugins
- Modes
- Integrações
- Recursos Avançados
- Module Federation
É um module bundler (empacotador de módulos) que recebe entradas (arquivos / entries) e transforma eles em uma saída (output) que condensa esses entradas.
É possível criar diferentes configurações para diferentes ambientes.
node 10.13+- navegadores precisam suportar javascript
ES5(para versões antigas é preciso usar opolyfill)
- Criar um projeto com
npm
npm init -y- Instalar as dependências do
webpack
npm install webpack webpack-cli-
Criar um arquivo
index.jsem algum lugar do projeto (o arquivoindex.jsé o entrypoint default do webpack). -
Gerar o build do
index.js
npx webpackTambém é possível criar um script dentro de package.json para gerar o build.
{
...,
"scripts": {
...
"build": "webpack"
},
...
}Um arquivo de configurações não é necessário para o
webpackfuncionar
O webpack por default lê o arquivo webpack.config.js. Um exemplo básico de configurações do webpack
const path = require('path')
module.exports = {
// Arquivo de entrada
entry: './src/index.js',
// Pasta de saída dos arquivos após o build
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
}
}É o arquivo de entrada do webpack onde as dependências são declaradas, por default é o index.js.
É a saída (bundle) do webpack, por default ele fica em dist/main.js
Recurso que permite o webpack processar diversos tipos de arquivos, além de .js. Loaders diferentes precisam ser instalados como dependências. Geralmente os loaders são dependências de desenvolvimento.
ATENÇÃO: A ordem de importação dos loaders importa ATENÇÃO: Quanto menos loaders, mais rápido e otimizado o build será
Um css loader permite carregarmos um arquivo .css dentro de um arquivo .js, ou seja.
import './example.css'- Instalar os loaders
npm install --save-dev style-loader css-loader- Atualizar o arquivo
webpack.config.js
module.exports = {
// ...
module: {
rules: [
{
// Regra para buscar arquivos .css
test: /\.css$/,
use: [
'style-loader', 'css-loader'
]
}
]
}
//...
}- Executar o build
npm run build- O arquivo
dist.jsadiciona os arquivos.cssdentro do javascript, deixando tudo em apenas um único arquivo
Semelhante ao loader do css, mas será preciso adicionar o sass-loader.
- Instalar os loaders
npm install --save-dev sass-loader sass- Atualizar o arquivo
webpack.config.js
module.exports = {
// ...
module: {
rules: [
{
// Regra para buscar arquivos .scss
test: /\.scss$/,
use: [
'style-loader', 'css-loader', 'sass-loader'
]
},
]
}
//...
}- Executar o build
npm run buildÉ o mesmo loader que carrega outros arquivos.
- Instalar o loader
npm install --save-dev file-loader- Atualizar o arquivo
webpack.config.js
module.exports = {
// ...
module: {
rules: [
{
// Regra para buscar arquivos de imagens
test: /\.(png|jpg|jpeg)$/,
use: [
'file-loader'
]
}
]
}
//...
}- Executar o build
npm run build- O
webpackcopia a imagem que importamos e cola ela dentro da pastadist
Para essas funcionalidades é preciso utilizar o babel-loader
- Instalar o loader
npm install --save-dev babel-loader @babel/preset-env @babel/plugin-proposal-object-rest-spread- Atualizar o arquivo
webpack.config.js
module.exports = {
// ...
module: {
rules: [
{
// Regra para buscar arquivos .js
test: /\.m?js$/,
// Excluir arquivos para evitar comparações e transformações desnecessárias
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-proposal-object-rest-spread']
}
}
}
]
}
//...
}- Executar o build
npm run build- O
webpackcopia a imagem que importamos e cola ela dentro da pastadist
É possível carregar arquivos .html dentro de um arquivo .js usando o html-loader
- Instalar o loader
npm install --save-dev html-loader- Atualizar o arquivo
webpack.config.js
module.exports = {
// ...
module: {
rules: [
{
// Regra para buscar arquivos .html
test: /\.html$/,
use: 'html-loader'
}
]
}
//...
}- Executar o build
npm run buildÉ possível carregar arquivos .text (ou outros arquivos tenham texto) dentro de um arquivo .js usando o raw-loader
- Instalar o loader
npm install --save-dev raw-loader- Atualizar o arquivo
webpack.config.js
module.exports = {
// ...
module: {
rules: [
{
// Regra para buscar arquivos .txt
test: /\.txt$/,
use: 'raw-loader'
}
]
}
//...
}- Executar o build
npm run buildApós a versão 2 do webpack, os arquivos .json possuem um loader nativo no webpack, usando apenas o require
Funcionalidades que podem ser adicionados nos projetos, como pro exemplo: minificador de arquivos javascript. Plugins diferentes precisam ser insatalados como dependências.
Para realizar isso é preciso usar o plugin MiniCSSExtractPlugin. Esse plugin vai entrar no lugar do style-loader.
- Instalar o plugin
npm install --save-dev mini-css-extract-plugin- Atualizar o arquivo
webpack.config.js
const MiniCSSExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
// ...
module: {
rules: [
{
// Regra para buscar arquivos .css
test: /\.css$/,
use: [
MiniCSSExtractPlugin.loader, 'css-loader'
]
}
]
},
plugins: [
//...
new MiniCSSExtractPlugin({
// Arquivo que vai ser gerado no build
filename: 'styles.css'
})
//...
]
//...
}- Executar o build
npm run buildPara realizar isso é preciso usar o plugin TerserPlugin. Em modo de produção não é necessário, pois o webpack já faz minificação de arquivos .js.
- Instalar o plugin
npm install --save-dev terser-webpack-plugin- Atualizar o arquivo
webpack.config.js
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
// ...
optimization: {
minimize: true,
minimizer: [new TerserPlugin()]
},
//...
}- Executar o build
npm run buildPara realizar isso é preciso usar o plugin DefinePlugin. Ele é nativo do webpack, ou seja, não é preciso instalar dependências adicionais.
- Atualizar o arquivo
webpack.config.js
const webpack = require('webpack')
module.exports = {
// ...
plugins: [
// ...
new webpack.DefinePlugin({
// Definindo constantes globais
VERSION: JSON.stringify('1.0.2'),
PORT: JSON.stringify('3000')
})
// ...
]
//...
}- Executar o build
npm run buildPara realizar isso é preciso usar o plugin DotenvPlugin.
- Instalar o plugin
npm install --save-dev dotenv-webpack- Atualizar o arquivo
webpack.config.js
const DotenvPlugin = require('dotenv-webpack')
module.exports = {
// ...
plugins: [
// ...
new DotenvPlugin()
// ...
]
//...
}- Executar o build
npm run buildPara realizar isso é preciso usar o plugin HtmlWebpackPlugin. Com ele, um arquivo .html será gerado, sem precisar linkar nenhum arquivo da pasta dist
- Instalar o plugin
npm install --save-dev html-webpack-plugin- Atualizar o arquivo
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// ...
plugins: [
// ...
new HtmlWebpackPlugin()
// ...
]
//...
}- Executar o build
npm run buildElimina o cache de navegadores caso as alterações do arquivo gerado no build mude. Não é necessário plugin, somente uma alteração no arquivo webpack.config.js
module.exports = {
// ...
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
},
// ...
plugins: [
// ...
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
})
// ...
]
// ...
}Porém para cada atualização no código, o build não irá excluir os arquivos antigos e irá criar novos. Para resolver isso é preciso usar o plugin CleanWebpackPlugin. Ele irá apagar arquivos desnecessários na pasta dist.
- Instalar o plugin
npm install --save-dev clean-webpack-plugin- Atualizar o arquivo
webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
// ...
plugins: [
// É interessante colocar em primeiro para limpar os arquivos antes de realizar qualquer mudança
new CleanWebpackPlugin()
// ...
]
//...
}- Executar o build
npm run buildÉ a forma que o webpack pode rodar, como por exemplo: modo de desenvolvimento ou produção.
Este modo não é otimizado, ou seja, ele não minifica arquivos.
Diferente do modo development, esse modo minifica arquivos por default. É o modo indicado para fazer deploys.
- Criar um arquivo
webpack.config.dev.jspara ambiente de desenvolvimento
const path = require('path')
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'dist.js',
path: path.resolve(__dirname, 'dist')
},
mode: 'development',
plugins: [
new TerserPlugin()
]
}- Criar um arquivo
webpack.config.prod.jspara ambiente de produção
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'dist.js',
path: path.resolve(__dirname, 'dist')
},
mode: 'production',
}- Alterar os scripts no
package.json
"scripts": {
"build": "webpack",
"dev": "webpack --config webpack.config.dev.js",
"prod": "webpack --config webpack.config.prod.js"
},- Instalar o servidor de dev do
webpack
npm install --save-dev webpack-dev-server- Alterar o arquivo
webpack.config.dev.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// ...
devServer: {
static: {
directory: path.join(__dirname, 'dist'),
},
port: 3000,
open: true,
hot: true,
historyApiFallback: true,
},
// ...
plugins: [
// ...
new HtmlWebpackPlugin({
filename: 'index.html',
title: 'Modes'
})
// ...
]
// ...
}- Alterar o script
devdentro depackage.json
"dev": "webpack serve --config webpack.config.dev.js",É possível integrar o webpack com diversos pacotes.
Como o jquery é uma lib de javascript puro, nenhuma configuração a mais é necessária para realizar o build desse pacote.
O webpack v5 carrega fontes automaticamente.
- Baixar fontes nos arquivos desejados e adicionar elas em uma pasta
asset
📁 asset
└── 📝 Roboto-Bold.ttf
└── 📝 Roboto-Light.ttf
└── 📝 Roboto-Regular.ttf
- Criar um arquivo css para carregar essas fontes:
@font-face {
font-family: 'Roboto';
font-weight: 400;
src: url('<caminho-para-fonte>/<fonte>.ttf');
}- Executar o build
npm run build- instalar os pacotes do
font-awesome
npm install --save-dev @fortawesome/fontawesome-svg-core @fortawesome/free-brands-svg-icons @fortawesome/free-regular-svg-icons @fortawesome/free-solid-svg-icons- Adicionar o ícone no arquivo
.js
import { library, dom } from '@fortawesome/fontawesome-svg-core'
import { faGithub } from '@fortawesome/free-brands-svg-icons'
library.add(faGithub)
dom.watch()- Adicionar o ícone no arquivo
index.html
<h1>Github <i class="fa-brands fa-github"></i></h1>- instalar os pacotes do
bootstrap.
npm install bootstrap jquery popper.js @popperjs/core- Adicionar os estilos do
bootstrapem um arquivo.scss
@import "~bootstrap/scss/bootstrap"- Importar o bootstrap no arquivo
.js
import 'bootstrap'
import './styles/bootstrap.css'- Alterar o
.htmlpara testar obootstrap
<!-- Classes do bootstrap -->
<div class="alert alert-primary" role="alert">
Isso é um alerta
</div>-
Adicionar o
sass-loaderno arquivowebpack.config.js -
Executar o build
npm run buildReact é um pacote mais complicado de integrar com o webpack
- Instalar as dependências necessárias para o
react
npm install react react-dom- Instalar as dependências do
babel
npm install @babel/core @babel/plugin-proposal-class-properties @babel/preset-env @babel/preset-react babel-loader- Criar um arquivo
.babelrcna raiz do projeto
{
"presets": [
[
"@babel/preset-env", {
"modules": false,
"targets": {
"browsers": [
"last 2 Chrome versions",
"last 2 Firefox versions",
"last 2 Safari versions",
"last 2 iOS versions",
"last 1 Android versions",
"last 1 ChromeAndroid versions",
"ie 11"
]
}
}
],
"@babel/preset-react"
],
"plugins": ["@babel/plugin-proposal-class-properties"]
}- Alterar o arquivo
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'dist.js',
path: path.resolve(__dirname, 'dist')
},
devServer: {
historyApiFallback: true
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public/index.html'),
filename: 'index.html'
})
],
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader'
},
{
test: /\.css$/,
use: [
'style-loader', 'css-loader'
]
}
]
},
mode: 'development'
}- O arquivo
public/index.htmlé o entry point doreact, então é preciso alterar ele
<body>
<div id="root"></div>
<script src="dist.js"></script>
</body>- Criar o componente
Appemsrc/app.jsque é componente principal da aplicação
import React from 'react'
export default function App() {
return <h1>React App</h1>
}- Alterar o arquivo
src/index.js
import React from 'react'
import { createRoot } from 'react-dom/client'
import App from './app'
const root = createRoot(document.getElementById('root'))
root.render(<App />)- Executar o build
npm run buildAssim como o React, o Vue também é um pacote complicado de integrar com o webpack
- Instalar as dependências necessárias para o
vue
npm install --save-dev core-js vue vue-router vue-loader vue-template-compiler- Instalar as dependências do
babel
npm install --save-dev @babel/core @babel/preset-env babel-loader autoprefixer clean-webpack-plugin html-webpack-plugin mini-css-extract-plugin- Criar um arquivo
.babelrcna raiz do projeto
{
"presets": [
[
"@babel/preset-env",
{
"useBuildIns": "usage",
"corejs": 3
}
]
]
}- Alterar o arquivo
webpack.config.js
const { VueLoaderPlugin } = require('vue-loader')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCSSExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'dist.js',
path: path.resolve(__dirname, 'dist')
},
devServer: {
historyApiFallback: true
},
plugins: [
new VueLoaderPlugin(),
new MiniCSSExtractPlugin({}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public/index.html'),
filename: 'index.html'
})
],
// Configuração específica do vue
resolve: {
alias: {
'Vue': 'vue/dist/vue.runtime.esm.js'
},
extensions: ['.*', '.js', '.vue', '.json']
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
},
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.css$/,
use: [
'style-loader', 'css-loader'
]
}
]
},
mode: 'development'
}- O arquivo
public/index.htmlé o entry point dovue, então é preciso alterar ele
<body>
<div id="app"></div>
</body>- Criar o componente
Appemsrc/app.vueque é componente principal da aplicação
<template>
<div id="app">
App Vue
</div>
</template>- Alterar o arquivo
src/index.js
import { createApp } from 'vue'
import App from './app.vue'
import router from './router'
createApp(App).use(router).mount('#app')- Executar o build
npm run build- Instalar as dependências do
typescript
npm install --save-dev typescript ts-loader- Criar um arquivo
tsconfig.jsonna raiz do projeto
{
"compilerOptions": {
"outDir": "./dist",
"noImplicitAny": true,
"module": "es6",
"target": "es5",
"jsx": "react",
"allowJs": true
}
}- Atualizar o arquivo
webpack.config.js
module.exports = {
entry: './src/index.ts',
output: {
filename: 'dist.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['.tsx', '.ts', '.js']
},
mode: 'development'
}- Executar o build
npm run build- Instalar as dependências do
express
npm install express
npm install --save-dev @types/express- Instalar as dependências do
typescript
npm install --save-dev typescript ts-loader- Criar um arquivo
tsconfig.jsonna raiz do projeto
{
"compilerOptions": {
"sourceMap": true
}
}- Atualizar o arquivo
webpack.config.js
module.exports = {
entry: './src/index.ts',
output: {
filename: 'dist.js',
path: path.resolve(__dirname, 'dist')
},
target: 'node',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['.tsx', '.ts', '.js']
},
mode: 'development'
}- Criar o arquivo
src/index.ts
import * as express from "express";
import { Request, Response } from "express";
const app = express();
const PORT = 3000;
app.get("/", (req: Request, res: Response) => {
res.send({
message: "Hello World",
});
});
app.listen(PORT, () => {
console.log("Server started at port: " + PORT);
});- Criar um script
startempackage.json
"start": "node dist/dist.js"- Iniciar servidor
npm run startÉ uma forma de não utilizar variáveis globais de módulos no código, ex: $ do JQuery. Esse recurso é uma boa prática do webpack.
- Atualizar o arquivo
webpack.config.js
const webpack = require('webpack')
module.exports = {
entry: './src/index.js',
output: {
filename: 'dist.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
// _ será carregado como variável global do 'lodash'
new webpack.ProvidePlugin({
_: 'lodash'
})
]
}Existem diversos fatores que podem melhorar o build de um projeto
- Inserir um
pathpara limitar quais pastas que osloadersvão conferir// ... module: { rules: [ // ... { test: /\.js$/, loader: 'babel-loader', // Vai usar o babel-loader apeans na pasta `src` include: path.resolve(__dirname, 'src') } // ... ] }, // ...
- Utilizar o mínimo de plugins possível
- Utilizar o servidor do webpack (
webpack-dev-server), pois compila na memório, não no disco - Criar um
chunkemruntimedentro deoptimization(runtimeChunk)module.exports = { // ... output: { filename: '[name].js', path: path.resolve(__dirname, 'dist') }, // ... plugins: [ new HtmlWebpackPlugin({ title: 'Recursos Avançados Webpack' }) ], // ... optimization: { runtimeChunk: true } }
Recurso do webpack-dev-server que recarrega as funcionalidades de arquivos alterados sem precisar gerar o build novamente.
module.exports = {
// ...
devServer: {
static: {
directory: path.join(__dirname, 'dist'),
},
historyApiFallback: true,
hot: true,
port: 3000,
open: true,
},
// ...
}Podemos dividir os arquivos em diferentes funcionalidades, para isso é preciso informar diversos entries.
module.exports = {
entry: {
index: {
import: './src/index.js',
dependOn: 'shared'
},
teste: {
import: './src/teste.js',
dependOn: 'shared'
},
// Informando dependências compartilhadas para evitar duplicação de código em arquivos separados
shared: 'lodash'
},
// ...
}- Rodar o comando:
npx webpack --profile --json=bundle.json- Fazer upload no website Webpack Chart
Carregar componentes apenas quando ele precisa ser utilizado, otimizando assim o carregamente de uma página. Ex: chamar uma função apenas quando ela é requisitada
function createButton() {
const btn = document.createElement('button')
btn.innerText = 'Botão'
document.querySelector('body').appendChild(btn)
// A importação do arquivo ocorre após o clique do botão
btn.onclick = e => import('./lazy').then(module => {
const lazy = module.default
lazy()
})
}
createButton()É uma funcionalidade do webpack para criar múltiplas aplicações em um projeto, com ele é possível desenvolver várias aplicações diferentes que são coordenadas por um único build.
Com ele é possível simular uma arquitetura de Micro Frontend
- Criar aplicações separadas
📦 packages
┣ 📂 app_1
┣ 📂 app_2
┗ 📂 app_3
- Configurar o
Module Federationpara cada app
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { ModuleFederationPlugin } = require('webpack').container
module.exports = {
entry: './src/index.js',
output: {
filename: '[name].js',
path: path.resolve(__dirname, './dist'),
publicPath: 'http://localhost:9001/'
},
devServer: {
static: {
directory : path.resolve(__dirname, './dist'),
},
devMiddleware: {
index: false,
},
port: 9001,
open: true,
historyApiFallback: {
index: 'index.html'
}
},
resolve: {
extensions: [".jsx", ".js", ".json"]
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: require.resolve("babel-loader"),
options: {
presets: [
require.resolve("@babel/preset-react")
]
}
},
{
test: /\.css$/,
use: [
'style-loader', 'css-loader'
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: './public/index.html',
title: 'App'
})
]
}- Como boa prática, o
webpackrecomenda criar um arquivobootstrap.jsque inicia o app React.
import React from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
const root = createRoot(document.getElementById('root'))
root.render(<App />)- Criar um arquivo
index.jsque importa o arquivobootstrap.js
import('./bootstrap')-
Fazer o mesmo procedimento para os demais apps
-
Configurar o
Module Federation. Nesse cenário, os apps são expostos enquanto um único app consome todos eles
6.1 Para arquivos expostos:
new ModuleFederationPlugin({
name: 'App1',
// Esse arquivo que será consumido pelo app consumidor
filename: 'remoteEntry.js',
exposes: {
'./App1Page': './src/component.js'
}
})6.2 Para o app consumidor:
new ModuleFederationPlugin({
name: 'App',
// Consumindo arquivos expostos
remotes: {
App1: 'App1@http://localhost:9002/remoteEntry.js',
App2: 'App2@http://localhost:9003/remoteEntry.js',
// ...
}
})- Atualizar os componentes do app consumidor
const App1Page = React.lazy(() => import("App1/App1Page"))
const App2Page = React.lazy(() => import("App2/App2Page"))
// ...