---
title: Gestión de estáticos con Webpack
tags: webpack, javascript
description: Documentación sobre la configuración de webpack para gestión de estáticos
---
# Webpack: gestión de estáticos
## 1. Introducción a Webpack
### ¿Qué es Webpack?
**Webpack** nació a finales de 2012 de la mano de un solo desarrollador (*Tobias Koppers*), y en la actualidad es utilizado por miles de proyectos de desarrollo web Front-End: desde frameworks como React o Angular hasta en el desarrollo de aplicaciones tan conocidas como Twitter, Instagram, PayPal o la versión web de Whatsapp.
**Webpack** se define como un empaquetador de módulos, podríamos definir **Webpack** como una herramienta Open Source utilizada por los desarrolladores para empaquetar y exportar todos los ficheros necesarios para que un proyecto funcione con todas sus dependencias frontend.

## 2. Instalación de Webpack
Para instalar webpack vamos a hacerlo utilizando [npm](https://docs.npmjs.com/) que deberemos tener instalado en nuestra máquina.
Podemos instalar Webpack de dos formas:
1. lanzando el comando:
```
npm install --save-dev webpack
```
2. Añadiendo webpack al package.json de nuestro proyecto y lanzando el siguiente comando:
```
npm install
```
Este último comando se utiliza para instalar todas las dependencias listadas en el archivo package.json de nuestro proyecto
## 3. Conceptos básicos
* **Entry Point**: Indican a Webpack los archivos de entrada para generar los paquetes o archivos *.bundle.js.
El valor por defecto es `./src/index.js` pero podemos especificar un valor diferente o incluso múltiples Entry points con la propiedad entry en el archivo de configuración de webpack.
**webpack.config.js:**
```
module.exports = {
entry: './path/to/my/entry/file.js',
};
```
* **Output**: Indican a Webpack el lugar donde se colocarán los paquetes *.bundle.* que se hayan generado: JavaScript, CSS, HTML, etc.
Por defecto los bundles se colocan en `./dist/main.js` para el main output y `./dist` para cualquier otro archivo generado.
**webpack.config.js:**
```
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js',
},
};
```
* **Loaders**: Son las rutinas que hacen posible que Webpack cargue, transforme y procese todos los archivos o entradas.
**webpack.config.js:**
```
const path = require('path');
module.exports = {
output: {
filename: 'my-first-webpack.bundle.js',
},
module: {
rules: [{ test: /\.txt$/, use: 'raw-loader' }],
},
};
```
Para usar los loaders, existen dos propiedades en la configuración de Webpack:
La propiedad "test" identifica que archivo o archivos serán transformados.
La propiedad "use" indica que loader debe ser utilizado para transformarlo.
* **Plugins**: Amplían las funcionalidades por defecto que incluye Webpack. Permiten realizar tareas en el código de nuestra aplicación como la optimización, minificación, ofuscación, por mencionar algunas.
**webpack.config.js:**
```
const HtmlWebpackPlugin = require('html-webpack-plugin'); //installed via npm
const webpack = require('webpack'); //to access built-in plugins
module.exports = {
module: {
rules: [{ test: /\.txt$/, use: 'raw-loader' }],
},
plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })],
};
```
* **Mode**: Al configurar esta propiedad en `development`, `production` o `none` podemos elegir en qué entorno se aplican las distintas optimizaciones, por defecto su valor es `production`
**webpack.config.js:**
```
module.exports = {
mode: 'production',
};
```
## 4. Configuración básica
La configuración básica para reutilizar en nuestros proyectos
### Plugins
En nuestra configuración básica común vamos a necesitar añadir algunos plugins, vamos a explicar cuáles y en qué consisten:
1. [clean-webpack-plugin](https://www.npmjs.com/package/clean-webpack-plugin): Por defecto este plugin elimina todos los archivos dentro de la carpeta output.path así como todos los assets no utilizados después de cada rebuild realizado con éxito.
**webpack.config.js:**
```
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpackConfig = {
plugins: [
new CleanWebpackPlugin(),
],
};
module.exports = webpackConfig;
```
2. [duplicate-package-checker-webpack-plugin](https://www.npmjs.com/package/duplicate-package-checker-webpack-plugin): Este plugin genera un Warning si en un bundle existen múltiples versiones de un mismo paquete
**webpack.config.js:**
```
const DuplicatePackageCheckerPlugin = require("duplicate-package-checker-webpack-plugin");
module.exports = {
plugins: [new DuplicatePackageCheckerPlugin()]
};var DuplicatePackageCheckerPlugin = require("duplicate-package-checker-webpack-plugin");
module.exports = {
plugins: [new DuplicatePackageCheckerPlugin(verbose: true,)]
};
```
3. [mini-css-extract-plugin](https://webpack.js.org/plugins/mini-css-extract-plugin/): Este plugin extrae CSS en archivos separados. Crea un archivo CSS por cada archivo JS que contiene CSS
**webpack.config.js:**
```
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
plugins: [new MiniCssExtractPlugin({ filename: "[name].bundle.css" })],
};
```
4. [optimize-css-assets-webpack-plugin](https://www.npmjs.com/package/optimize-css-assets-webpack-plugin): Este plugin se utiliza para optimizar y minimizar los assets CSS
**webpack.config.js:**
```
const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = merge(common, {
optimization: {
minimize: true,
minimizer: [
new OptimizeCssAssetsPlugin(),
]}
}
```
5. [terser-webpack-plugin](https://www.npmjs.com/package/terser-webpack-plugin): Este plugin se utiliza para minificar js utilizando tesrer (Este plugin solo es necesario en versiones de Webpack por debajo de la 5):
**webpack.config.js:**
```
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [new TerserPlugin()],
},
};
```
### Estructura de los archivos de configuración
Vamos separar el webpack.config.js en tres partes para mantener separada la configuración de dev y de producción.
Se compondrá de los siguientes archivos JS:
* webpack.common.js: Archivo de ajustes generales que se utilizan en ambos entornos.
* webpack.prod.js: Archivo de ajustes para producción.
* webpack.dev.js: Archivo de ajustes para desarrollo.
El webpack.common.js se incluye dentro de cada uno de los archivos de los entornos. Para que Webpack pueda acceder a los distintos archivos, en webpack.config.js se deberá incluir los archivos condicionados al entorno en el que nos encontramos. Ejemplo extraído de Uriach:
**webpack.config.js:**
const production = require('./webpack/webpack.prod.js');
const development = require('./webpack/webpack.dev.js');
function webpackEnviromentSelector(env) {
if (env && env.production)
return production;
return development;
}
module.exports = webpackEnviromentSelector;
Los archivos con las utilidades mínimas para proyectos habituales son los siguientes:
**webpack.common.js:**
const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const DuplicatePackageCheckerPlugin = require('duplicate-package-checker-webpack-plugin');
const ENTRY_POINT = {
app: "./assets/app.js",
};
const PLUGINS = [
new CleanWebpackPlugin(),
new DuplicatePackageCheckerPlugin({
verbose: true,
}),
new webpack.ProvidePlugin( {
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery'
}),
];
const HTML_RULES = {
test: /\.html$/,
use: ["html-loader"]
};
const JS_RULES = {
test: /\.js/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
};
const IMAGE_RULES = {
test: /\.(png|jpg|gif|svg)$/,
use: {
loader: "file-loader",
options: {
name: "[name].[hash].[ext]",
outputPath: "img"
}
}
};
const ICON_RULES = {
test: /\.(ico)$/,
use: {
loader: "file-loader",
options: {
name: "[name].[hash].[ext]",
outputPath: "icons"
}
}
};
const FONT_RULES = {
test: /\.(ttf|otf|eot|woff|woff2)$/,
use: {
loader: "file-loader",
options: {
name: "[name].[hash].[ext]",
outputPath: "fonts"
}
}
};
const IMPORT_ALIAS = {
'node_modules': path.join(__dirname, 'node_modules'),
jquery: path.resolve(__dirname, '../node_modules/jquery'),
bootstrap: path.resolve(__dirname, '../node_modules/bootstrap'),
};
module.exports = {
context: __dirname,
entry: ENTRY_POINT,
plugins: PLUGINS,
resolve: {
alias: IMPORT_ALIAS,
},
module: {
rules: [
HTML_RULES,
JS_RULES,
IMAGE_RULES,
ICON_RULES,
FONT_RULES,
]
}
};
**webpack.prod.js:**
```
const path = require("path");
const merge = require("webpack-merge");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const DuplicatePackageCheckerPlugin = require('duplicate-package-checker-webpack-plugin');
const common = require("./webpack.common");
const OPTIMIZATION = {
minimize: true,
minimizer: [
new OptimizeCssAssetsPlugin(),
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true,
terserOptions: {
output: {
comments: false
},
},
})
]
};
const OUTPUT = {
filename: "[name].[contentHash].js",
path: path.resolve('./app/bundles/static/'),
};
const PLUGINS = [
new MiniCssExtractPlugin({ filename: "[name].[contentHash].css" }),
new DuplicatePackageCheckerPlugin(),
];
const CSS_RULES = {
test: /\.css/,
use: [
MiniCssExtractPlugin.loader, //2. Extract css into files
{ loader: 'css-loader' }, //1. Turns css into commonjs
{ loader: 'postcss-loader' },
]
};
const SASS_RULES = {
test: /\.(sa|sc)ss$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader, //3. Extract css into files
{ loader: 'css-loader' }, //2. Turns css into commonjs
{ loader: 'sass-loader' } //1. Turns sass into css
]
};
module.exports = merge(common, {
mode: "production",
output: OUTPUT,
optimization: OPTIMIZATION,
plugins: PLUGINS,
module: {
rules: [
CSS_RULES,
SASS_RULES
]
}
});
```
**webpack.dev.js:**
```
const path = require("path");
const webpack = require("webpack");
const merge = require("webpack-merge");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const common = require("./webpack.common");
const OUTPUT = {
filename: "[name].bundle.js",
path: path.resolve('./app/bundles/static/'),
publicPath: "http://localhost:8080/static/",
};
const PLUGINS = [
new MiniCssExtractPlugin({ filename: "[name].bundle.css" }),
new webpack.HotModuleReplacementPlugin(),
];
const CSS_RULES = {
test: /\.css/,
use: [
MiniCssExtractPlugin.loader,
{ loader: 'css-loader', options: { sourceMap: true } },
{ loader: 'postcss-loader', options: { sourceMap: true } },
]
};
const SASS_RULES = {
test: /\.(sa|sc)ss$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader, //3. Extract css into files
{ loader: 'css-loader', options: { sourceMap: true } },
{
loader: 'postcss-loader',
options: {
sourceMap: true,
config: {
path: 'postcss.config.js'
}
}
}, //2. Turns css into commonjs
{ loader: 'sass-loader', options: { sourceMap: true } } //1. Turns sass into css
]
};
module.exports = merge(common, {
mode: "development",
devtool: 'source-map',
output: OUTPUT,
plugins: PLUGINS,
module: {
rules: [
CSS_RULES,
SASS_RULES,
]
},
devServer: {
hot: true,
compress: true,
quiet: false,
disableHostCheck: true,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET,OPTIONS,HEAD,PUT,POST,DELETE,PATCH',
'Access-Control-Allow-Headers': 'Origin, Content-Type, Accept, Authorization, X-Request-With',
'Access-Control-Allow-Credentials': 'true',
},
host: '0.0.0.0', // Enabled for docker
port: 8080,
publicPath: '/static/'
},
});
```