👉 Ver todas las notas
Webpack es un module bundler, su tarea es analizar el código de nuestra aplicación, detectar los diferentes módulos que el mismo importa/exporta, aplicar (opcionalmente) las transformaciones necesarias y empaquetar todo el código en un único archivo .js
y referenciarlo en el index.html
, para que este pueda ser ejecutado en los browsers luego.
Uno de los problemas que resuelve Webpack entonces, es la necesidad que teníamos previamente de linkear todos los scripts de librerías y módulos que utilizáramos en el index.html
, en el orden correcto.
Los problemas que generaba esto eran:
- dependencia de servidores de CDN (librerías)
- problemas para el versionado: si queremos actualizar alguna librería, tenemos que modificar manualmente los números de versiones en el
src
, lo que es poco práctico y propenso a errores - cargar los scrips en el orden incorrecto (si tienen dependencias entre si)
<!-- Linkeando scripts como antes -->
<body>
...
<script src="libs/react.min.js"></script>
<script src="src/admin.js"></script>
<script src="src/api.js.js"></script>
<script src="src/auth.js"></script>
</body>
<!-- Con Webpack -->
<body>
...
<script src="dist/bundle.js"></script>
</body>
Aparte de resolver el problema del bundling, Webpack permite, a través de diferentes plugins y loaders, aplicar diferentes transformaciones (ej: transpilar TS, o código ES6+, agregar Babel, etc) y optimizaciones a nuestro código (ej: code splitting, image opt, etc).
Por defecto, Webpack sólo puede analizar y procesar los archivos .js
y .json
de nuestro código. Probablemente necesitemos incluir estilos (CSS) e imágenes. Es por este motivo que existen los loaders, que le permiten a Webpack procesar más tipos de archivos, para realizar diferentes transformaciones, optimizaciones e incluirlos en el bundle final.
Para cada loader, tenemos que especificar sobre qué archivos se va a aplicar (podemos usar regex).
Los loaders se instalan a través de npm
, por ejemplo, para procesar SVGs y CSS
npm i -D svg-inline-loader css-loader
Si además queremos inyectar los estilos directamente en el DOM, podemos usar style-loader
npm i -D style-loader
A diferencia de los loaders, que se utilizan para procesar y transformar archivos mientras generamos el bundle, los plugins le permiten a Webpack ejecutar ciertas tareas después de que el bundle fue creado. Por ejemplo, HtmlWebpackPlugin
: genera un index.html
, lo coloca en /dist
(o en cualquier otro destino que definamos en output
), agregándole un <script>
tag que referencia al bundle creado. Otro plugin útil es EnvironmentPlugin
, que nos permite configurar el entorno de producción para el deployment.
Para agregar plugins, debemos instalarlos (al igual que los loaders) y setearlos en la propiedad plugins
de la config
npm i -D html-webpack-plugin
// webpack.config.js
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
module: {
rules: [ ... ]
},
output: { ... },
plugins: [
new HtmlWebpackPlugin(),
new webpack.EnvironmentPlugin({
'NODE_ENV': 'production'
})
]
}
El Hot Module Replacement/Reloading permite agilizar el proceso de desarrollo, al dejarnos ver los cambios de forma instantánea en el browser, sin necesidad de refrescar el mismo. El mismo sólo estará disponible en el entorno de desarrollo, no así en producción.
Instalar Webpack y su CLI como dependencias de desarrollo en el proyecto:
npm i -D webpack webpack-cli
Crear el archivo webpack.config.js
para guardar las opciones de configuración.
// webpack.config.js
module.exports = {
entry: './src/index.js',
module: {
rules: [
{
test: /\.svg$/, // aplicar esta transformación a todos los archivos .svg
use: 'svg-inline-loader' // loader que vamos a usar
},
{
test: /\.css$/, // aplicar esta transformación a todos los archivos .css
use: [ 'style-loader', 'css-loader' ] // en este caso usamos 2 loaders, por eso el array. El orden importa, webpack los procesa en orden inverso
}
]
}
}
En este archivo de config deberemos indicar cosas como:
- entry point de nuestra app (definido en
entry
) - transformaciones que querramos aplicar (definidas en las
rules
dentro demodule
) - destino, con la ubicación del archivo empaquetado (definido en
output
)
Agregando el babel-loader
para transpilar JS ES6+
npm i -D babel-loader
// webpack.config.js
module.exports = {
entry: './src/index.js',
module: {
rules: [
{
test: /\.svg$/,
use: 'svg-inline-loader'
},
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
{
test: /\.(js)$/, // aplicar esta transformación a todos los archivos .js
use: 'babel-loader'
}
]
}
}
Finalmente, para agregar el destino, el output
, que es un objeto con las siguientes propiedades
output: {
path: ... // ubicación de /dist, el directorio donde se va a ubicar el bundle
filename: ... // nombre del bundle
}
// webpack.config.js
module.exports = {
entry: './src/index.js',
module: {
rules: [
{
test: /\.svg$/,
use: 'svg-inline-loader'
},
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
{
test: /\.(js)$/, // aplicar esta transformación a todos los archivos .js
use: 'babel-loader'
}
]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.bundle.js`
}
}
- Webpack analiza el archivo definido como entry point
- Detecta y examina todos los imports/exports del mismo (o
requires
, si estamos usando CommonJS) y genera un dependency graph, para saber qué módulos dependen de cuáles otros y en qué orden debe insertarlos en el bundle final - Comienza a crear el bundle y a medida que va agregando los scripts, aplica las transformaciones definidas en la config y agrega el output correspondiente
- Finalmente ubica el bundle final según
output
definido en la config
Podemos realizar el proceso de preparar nuestro bundle para producción (setear el NODE_ENV
, minificar el código, etc) manualmente, en el caso de que querramos optimizar algo específico. Pero para simplificar y ahorrar tiempo, podemos usar el mode
de la config y setearlo en production, que va a setear ciertos parámetros por default (como los mencionados antes).
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
module: {
rules: [ ... ]
},
output: { ... },
plugins: [
new HtmlWebpackPlugin()
],
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development'
}
El mode development de webpack deshabilita las optimizaciones mencionadas y activa el HMR para desarrollar localmente.
El DevServer nos permite levantar un servidor local de desarrollo y carga los archivos en memoria, evitándonos esperar a que se complete el build cada vez que guardamos cambios, para poder ver los mismos en el browser. Este módulo además es el que incluye el HMR (live reloading). Para usarlo, lo instalamos con
npm i -D webpack-dev-server
En nuestro package.json
, agregamos los scripts de build (bundle optimizado para producción) y dev (este último levanta un server local con HMR)
"scripts": {
"build": "NODE_ENV='production' webpack",
"dev": "webpack serve"
}
y luego lo corremos con npm run build
.