--- 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. ![](https://i.imgur.com/MaioHlh.gif) ## 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/' }, }); ```