Introducción Detallada a Webpack 4.x

Este artículo explorará las configuraciones más comunes de Webpack 4.x, presentando ejemplos detallados para cada funcionalidad. Aunque extenso, seguir los ejemplos paso a paso te ayudará a dominar Webpack con facilidad.

1. ¿Qué es Webpack y por qué usarlo?

1.1. ¿Qué es Webpack?

En esencia, Webpack es un empaquetador de módulos.

1.2. ¿Por qué se utiliza?

En el desarrollo web tradicional, un archivo HTML a menudo dependía de múltiples scripts JavaScript, cuya secuencia de carga era crucial debido a sus interdependencias. Además, las nuevas características del lenguaje (como ES6+), o preprocesadores CSS como Less o Sass, no se integraban de manera nativa. Aquí es donde Webpack se vuelve indispensable. Su propósito es ver tu proyecto como un todo unificado. Partiendo de un punto de entrada principal (por ejemplo, main.js), Webpack rastrea todas las dependencias, las procesa y las consolida en uno o varios archivos JavaScript que el navegador puede interpretar.

2. Un Ejemplo de Empaquetado Básico

2.1. Preparación del Entorno

Crea una carpeta vacía para tu proyecto. Navega a ella desde tu terminal (por ejemplo, my-webpack-app) y ejecuta npm init para generar un archivo package.json.

npm init

Puedes aceptar los valores predeterminados presionando Enter repetidamente, o usar npm init -y para generar el archivo directamente.

2.2. Instalación de Webpack

Para una configuración completa, instala Webpack globalmente y localmente. La instalación local debe incluir webpack-cli, ya que Webpack 4+ ha delegado algunas funcionalidades a este paquete. Aquí están los comandos:

npm install webpack --global              # Instalación global de webpack
npm install webpack-cli webpack --save-dev # Instalación local de webpack y cli

Sugerencia: Puedes usar alias para los comandos: install como i, --global como -g, --save-dev como -D (agrega a dependencias de desarrollo en package.json), y --save como -S (agrega a dependencias de producción). Para acelerar las instalaciones, considera usar un registro de npm alternativo como cnpm si estás en China.

cnpm i webpack -g                 # webpack global
cnpm i webpack-cli webpack -D     # webpack y cli local

2.3. Creación de Archivos

Dentro de la carpeta my-webpack-app, crea dos subcarpetas: src y dist. Luego, crea los siguientes archivos:

  • index.html — dentro de dist
  • greeting.js — dentro de src
  • main.js — dentro de src

Tu estructura de proyecto debería verse así:

my-webpack-app/
├── package.json
├── node_modules/
├── dist/
│   └── index.html
└── src/
    ├── greeting.js
    └── main.js

En index.html, incluirás el archivo JavaScript empaquetado:

<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Proyecto Webpack</title>
</head>
<body>
    <div id='app'></div>
    <script src="app.js"></script> <!-- Nuestro archivo empaquetado -->
</body>
</html>

En greeting.js, exporta una función que crea un elemento DIV:

// greeting.js
module.exports = function() {
    let container = document.createElement('div');
    container.innerHTML = "¡Hola desde Webpack!";
    return container;
};

Finalmente, en main.js, importa y utiliza el módulo greeting.js:

// main.js
const makeGreeting = require('./greeting.js');
document.querySelector("#app").appendChild(makeGreeting());

Este flujo simula cómo greeting.js es incorporado por main.js. Webpack luego tomará main.js como punto de partida y empaquetará todo en app.js, que será referenciado por index.html.

2.4. Ejecutando el Empaquetado con Webpack

Desde la terminal, utiliza el siguiente comando:

webpack src/main.js --output dist/app.js
# --output puede abreviarse como -o

Este comando instruye a Webpack para que empaquete src/main.js y guarde el resultado como dist/app.js. Tras la ejecución, se generará app.js.

Verás una salida en la terminal indicando que Webpack ha compilado main.js y greeting.js. Si abres index.html en tu navegador, deberías ver el mensaje. Sin embargo, escribir este comando largo repetidamente es tedioso. Afortunadamente, Webpack permite una configuración más eficiente.

2.5. Configuración de Webpack a través de un Archivo

Para simplificar el proceso, crea un archivo de configuración llamado webpack.config.js en la raíz de tu proyecto. Inicialmente, configuraremos los puntos de entrada (entry) y salida (output).

// webpack.config.js
const path = require('path');

module.exports = {
    entry: path.resolve(__dirname, 'src', 'main.js'), // Archivo de entrada
    output: {
        path: path.resolve(__dirname, 'dist'),       // Ubicación del archivo empaquetado
        filename: 'app.js'                          // Nombre del archivo de salida
    }
};

__dirname es una variable global de Node.js que apunta al directorio del script actual. path.resolve es una función de Node.js para construir rutas absolutas. Con este archivo, ahora puedes ejecutar simplemente webpack en la terminal, y Webpack cargará automáticamente esta configuración.

webpack

¡Mucho más conveniente! Pero podemos hacerlo aún mejor.

2.6. Empaquetado Inteligente con NPM Scripts

Para tareas más complejas o para agrupar comandos, el archivo package.json es ideal. Modificaremos la sección scripts:

  "scripts": {
    "start": "webpack"
  },

Ahora, puedes ejecutar npm start en la terminal para iniciar el empaquetado. El comando start es especial; se puede invocar directamente con npm start. Para cualquier otro script (como build), usarías npm run build.

npm start

Esto simplifica enormemente el flujo de trabajo. Pero Webpack ofrece mucho más.

3. Configuración de un Servidor de Desarrollo Local

Hasta ahora, hemos visto el sitio abriendo un archivo localmente. Es más profesional y eficinete ejecutarlo en un servidor de desarrollo local, similar a cómo funcionan los frameworks modernos como React o Vue.

3.1. Uso de webpack-dev-server

Webpack proporciona un servidor de desarrollo local opcional, construido sobre Node.js. Es un componente separado que debe instalarse como una dependencia de desarrollo:

npm i webpack-dev-server -D

El servidor de desarrollo se configura a través de la opción devServer en webpack.config.js. Algunas opciones comunes son:

  • contentBase: Especifica el directorio desde donde se servirán los archivos (ej. "./dist").
  • port: El número de puerto (por defecto, 8080).
  • inline: Si es true, recarga automáticamente la página cuando los archivos fuente cambian.
  • historyApiFallback: Si es true, las rutas que no se encuentran en el servidor redirigirán a index.html, útil para aplicaciones de una sola página.

Añadamos estas configuraciones a webpack.config.js:

// webpack.config.js
const path = require('path');

module.exports = {
    entry: path.resolve(__dirname, 'src', 'main.js'),
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'app.js'
    },
    devServer: {
        contentBase: path.resolve(__dirname, 'dist'), // Directorio de archivos servidos
        port: 8088,                                  // Puerto 8088
        inline: true,                                // Recarga en vivo
        historyApiFallback: true                     // No redireccionar para SPAs
    }
};

Ahora, actualiza los scripts en package.json. Cambiaremos start a build (para el empaquetado final) y agregaremos un script dev para el servidor de desarrollo:

  "scripts": {
    "build": "webpack",
    "dev": "webpack-dev-server --open"
  },

El comando --open abrirá automáticamente el navegador después de iniciar el servidor. Ejecuta npm run dev:

npm run dev

Tu aplicación estará disponible en http://localhost:8088/. Para detener el servidor, usa Ctrl+C y luego Y.

Para el script build, puedes agregar la opción --watch para que Webpack reempaquete automáticamente los archivos cuando detecte cambios, lo que es útil durante el desarrollo sin un servidor en vivo.

  "scripts": {
    "build": "webpack --watch",
    "dev": "webpack-dev-server --open"
  },

Ahora, npm run build observará los cambios y reempaquetará. Solo necesitarías recargar el navegador manualmente.

3.2. Configuración de Source Maps para Depuración

Depurar código empaquetado puede ser difícil. Los Source Maps resuelven esto al mapear el código compilado de vuelta a su fuente original. Para habilitarlos, añade la opción devtool a tu configuración de Webpack:

// webpack.config.js
const path = require('path');

module.exports = {
    // ...otras configuraciones
    devServer: {
        // ...
    },
    devtool: 'source-map' // Genera un archivo .map completo para depuración, pero puede ralentizar el empaquetado
};

Después de ejecutar npm run build, verás un archivo app.js.map en tu carpeta dist. Con esto, cualquier error en el navegador te indicará la línea correcta en tu código fuente original.

4. Loaders

Los Loaders son una de las características más potentes de Webpack. Permiten a Webpack procesar diferentes tipos de archivos (como CSS, Sass, imágenes o código ES6+) y convertirlos en módulos válidos para el gráfico de dependencias de tu aplicación. Por ejemplo, pueden transformar Sass a CSS o ES6 a ES5.

Los Loaders deben instalarse por separado y configurarse en la sección module.rules de webpack.config.js. Las propiedades clave de un loader son:

  • test: Una expresión regular para identificar los archivos que debe procesar el loader.
  • use: Un array de loaders a aplicar a los archivos que coincidan con test. Los loaders se aplican de derecha a izquierda (o de abajo hacia arriba).
  • exclude/include: Para especificar qué carpetas ignorar o incluir.
  • options: Para pasar configuraciones adicionales al loader.

4.1. Configuración de css-loader y style-loader

Para cargar archivos CSS, necesitas style-loader y css-loader:

cnpm i style-loader css-loader -D

Añade la siguiente regla a tu webpack.config.js:

// webpack.config.js
const path = require('path');

module.exports = {
    // ...otras configuraciones
    module: {
        rules: [
            {
                test: /\.css$/, // Coincide con archivos .css
                use: ['style-loader', 'css-loader'] // El orden es crucial: 'css-loader' primero, luego 'style-loader'
            }
        ]
    }
};

Crea una carpeta css dentro de src y un archivo styles.css:

/* styles.css */
body {
    background-color: lightgray;
}

Importa este CSS en main.js:

// main.js
import './css/styles.css'; // Importa el archivo CSS

const makeGreeting = require('./greeting.js');
document.querySelector("#app").appendChild(makeGreeting());

Ejecuta npm run dev. El fondo de la página debería ser gris claro.

4.2. Configuración de Sass/SCSS

Para compilar archivos Sass, necesitas sass-loader y node-sass (porque sass-loader depende de él):

cnpm i sass-loader node-sass -D

Añade una nueva regla para Sass a webpack.config.js:

// webpack.config.js
const path = require('path');

module.exports = {
    // ...
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\.(scss|sass)$/, // Coincide con archivos .scss o .sass
                use: ['style-loader', 'css-loader', 'sass-loader'] // Orden: 'sass-loader' -> 'css-loader' -> 'style-loader'
            }
        ]
    }
};

Crea theme.scss en la carpeta src/css:

/* theme.scss */
$primary-color: #007bff;
body {
    color: $primary-color;
}

Importa theme.scss en main.js:

// main.js
import './css/styles.css';
import './css/theme.scss'; // Importa el archivo SCSS

const makeGreeting = require('./greeting.js');
document.querySelector("#app").appendChild(makeGreeting());

Reinicia el servidor con npm run dev. El texto en la página debería aparecer azul.

4.3. Configuración de Loaders para Imágenes

Para manejar imágenes, empezaremos con file-loader:

npm install file-loader --save-dev

Agrega la configuración de file-loader a webpack.config.js:

// webpack.config.js
const path = require('path');

module.exports = {
    // ...
    module: {
        rules: [
            // ...reglas existentes para CSS/Sass
            {
                test: /\.(png|jpg|jpeg|gif)$/, // Coincide con varios formatos de imagen
                use: ['file-loader']
            }
        ]
    }
};

Supongamos que tienes una imagen background.png en src/assets/images/. Modifica styles.css para usarla como fondo:

/* styles.css */
body {
    background: url('../assets/images/background.png') no-repeat center;
    background-size: cover;
}

Ejecuta npm run build. Deberías ver que la imagen se copia a la carpeta dist con un nombre de archivo hash. Luego, npm run dev debería mostrar la imagen de fondo.

4.4. Uso de url-loader

url-loader es una extensión de file-loader, con la capacidad adicional de convertir archivos pequeños en URL de datos Base64, evitando solicitudes HTTP adicionales. Instálalo:

npm install url-loader --save-dev

Reemplaza la configuración de file-loader por url-loader:

// webpack.config.js
// ...
module.exports = {
    // ...
    module: {
        rules: [
            // ...
            {
                test: /\.(png|jpg|jpeg|gif|bmp|webp)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 1024 * 40, // Los archivos menores a 40KB se convertirán a Base64
                            name: '[name].[ext]',  // Mantener nombre y extensión original
                            outputPath: 'img/'     // Output en la subcarpeta 'img' dentro de 'dist'
                        }
                    }
                ]
            }
        ]
    }
};

La opción limit es clave: si el tamaño del archivo es menor al límite especificado (en bytes), se codificará en Base64. De lo contrario, url-loader actuará como file-loader y moverá el archivo. Ejecuta npm run build y luego npm run dev. Si tu imagen es pequeña, la verás incrustada como Base64 en el CSS; si es grande, se guardará en dist/img/. La codificación Base64 evita una petición HTTP al servidor, lo que puede optimizar el rendimiento para imágenes pequeñas.

4.5. Integración de Librerías de Fuentes (Font Awesome)

Para integrar librerías de iconos como Font Awesome, primero instálala:

npm install font-awesome --save

Nota la diferencia entre --save (dependencia de producción, en dependencies de package.json) y --save-dev (dependencia de desarrollo, en devDependencies). Font Awesome es una dependencia de producción. Importa el CSS de Font Awesome en main.js:

// main.js
import './css/styles.css';
import './css/theme.scss';
import "font-awesome/css/font-awesome.css"; // Importa el CSS de Font Awesome

const makeGreeting = require('./greeting.js');
document.querySelector("#app").appendChild(makeGreeting());

Si ejecutas npm run dev ahora, probablemente verás errores sobre tipos de archivos de fuente desconocidos (.eot, .svg, .ttf, .woff). Necesitamos un loader para manejarlos. file-loader es ideal para esto:

// webpack.config.js
// ...
module.exports = {
    // ...
    module: {
        rules: [
            // ...reglas existentes
            {
                test: /\.(otf|eot|svg|ttf|woff|woff2)$/, // Coincide con archivos de fuente
                use: ['file-loader']
            }
        ]
    }
};

Ahora, ejecuta npm run build. Verás los archivos de fuente copiados a dist. Para probar los iconos, añade algunos a tu index.html (o al template si lo usas):

<div id="app">
    <i class="fa fa-bath" aria-hidden="true"></i>
    <i class="fa fa-envelope-open" aria-hidden="true"></i>
    <i class="fa fa-microchip" aria-hidden="true"></i>
    <i class="fa fa-user-circle-o" aria-hidden="true"></i>
</div>

Después de ejecutar npm run dev, los iconos deberían mostrarse correctamente.

4.6. Instalación de Librerías de Terceros (jQuery)

Para usar una librería como jQuery, primero instálala:

cnpm install jquery --save-dev

Crea un archivo interaction.js en src/:

/* interaction.js */
let $ = require('jquery'); // Importa jQuery

$("#app").on('click', function() {
    $(this).html("¡Librería jQuery importada!");
});

Importa este archivo en main.js:

// main.js
import './css/styles.css';
import './css/theme.scss';
import "font-awesome/css/font-awesome.css";

require('./interaction.js'); // Importa el script de interacción

const makeGreeting = require('./greeting.js');
document.querySelector("#app").appendChild(makeGreeting());

Ejecuta npm run dev. Al hacer clic en el texto, debería cambiar. Si se reemplaza, es porque el handler de click de jQuery es para el elemento root.

5. Babel

Babel es un compilador de JavaScript que te permite:

  • Utilizar las últimas características de JavaScript (ES6, ES7, etc.) sin preocuparte por la compatibilidad del navegador.
  • Emplear lenguajes extendidos basados en JavaScript, como JSX para React.

5.1. Instalación y Configuración de Babel

Babel se compone de varios paquetes modulares. El núcleo es babel-core. Necesitarás paquetes adicionales para funcionalidades específicas, como babel-preset-env para transpilar a una versión compatible con el entorno y babel-preset-react para JSX.

cnpm i babel-core babel-loader babel-preset-env babel-preset-react -D
# 'env' se refiere a la configuración del entorno actual, en lugar de versiones fijas como 'es2015'.

Agrega una regla para Babel a webpack.config.js:

// webpack.config.js
// ...
module.exports = {
    // ...
    module: {
        rules: [
            // ...otras reglas de loaders
            {
                test: /(\.jsx|\.js)$/, // Coincide con archivos .js y .jsx
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: [
                            "env", "react"
                        ]
                    }
                },
                exclude: /node_modules/ // Excluye la carpeta node_modules
            }
        ]
    }
};

Para probar las capacidades de ES6 y JSX, instalaremos React y ReactDOM:

cnpm i react react-dom -D

Ahora, modifica greeting.js para usar React:

// greeting.js
import React, { Component } from 'react';

let userName = 'Carlos';

export default class WelcomeComponent extends Component {
    render() {
        return (
            <div>
                {userName}, ¡bienvenido a React!
            </div>
        );
    }
}

Y actualiza main.js para renderizar el componente React:

// main.js
import './css/styles.css';
import './css/theme.scss';
import "font-awesome/css/font-awesome.css";

import React from 'react';
import { render } from 'react-dom';
import WelcomeComponent from './greeting'; // Se puede omitir la extensión .js

render(<WelcomeComponent />, document.getElementById('app'));

Al ejecutar npm run dev, podrías encontrar un error de compatibilidad entre babel-loader y las versiones de Babel. Una solución común para Webpack 4 es revertir a una versión específica de babel-loader:

cnpm i babel-loader@7 babel-core babel-preset-env babel-preset-react -D

Después de esta corrección, npm run dev debería mostrar "Carlos, ¡bienvenido a React!".

5.2. Optimización de la Configuración de Babel

Aunque Babel se puede configurar directamente en webpack.config.js, es una buena práctica modularizar su configuración. Crea un archivo .babelrc en la raíz de tu proyecto. Webpack lo reconocerá automáticamente.

/* .babelrc */
{
    "presets": ["env", "react"]
}

Una vez que muevas los presets, la sección use de babel-loader en webpack.config.js se simplificará:

// webpack.config.js
// ...
module.exports = {
    // ...
    module: {
        rules: [
            // ...
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader" // Sin la opción "options" aquí
                },
                exclude: /node_modules/
            }
        ]
    }
};

Todo debería seguir funcionando correctamente.

6. Plugins

Los Plugins extienden las capacidades de Webpack. A diferencia de los Loaders, que procesan archivos individuales durante la fase de empaquetado, los Plugins actúan sobre todo el proceso de compilación, realizando tareas complejas.

6.1. Uso Básico de Plugins

Para usar un plugin, instálalo (si no es parte de Webpack), impórtalo y crea una instancia en el array plugins de webpack.config.js. Como ejemplo, usaremos webpack.BannerPlugin (incluido con Webpack) para añadir un banner de copyright a los archivos de salida.

// webpack.config.js
const webpack = require('webpack'); // Webpack es necesario para este plugin

module.exports = {
    // ...
    plugins: [
        new webpack.BannerPlugin('Copyright © Mi Empresa. Todos los derechos reservados.')
    ]
};

Ejecuta npm run build. Si abres dist/app.js, verás tu banner de copyright al principio del archivo.

6.2. Generación Automática de HTML con HtmlWebpackPlugin

Hasta ahora, hemos creado index.html manualmente y hemos insertado app.js. Si los nombres de los archivos empaquetados cambian, o si tenemos múltiples puntos de entrada, tendríamos que actualizar index.html manualmente. HtmlWebpackPlugin automatiza esto.

Instala el plugin:

cnpm i html-webpack-plugin -D

Para usarlo, elimina la carpeta dist. Crea un archivo de plantilla en src/index.template.html (puedes personalizarlo):

<!-- src/index.template.html -->
<html lang="es">
  <head>
    <meta charset="utf-8">
    <title>Mi Aplicación Webpack</title>
  </head>
  <body>
    <div id='app'></div>
  </body>
</html>

Ahora, configura HtmlWebpackPlugin en webpack.config.js para usar esta plantilla:

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    // ...
    plugins: [
        new webpack.BannerPlugin('Copyright © Mi Empresa. Todos los derechos reservados.'),
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, 'src', 'index.template.html') // Ruta a tu plantilla HTML
        })
    ]
};

Cuando ejecutes npm run build, dist/index.html se generará automáticamente, incluyendo una etiqueta <script> que apunta a app.js.

6.3. Limpieza de la Carpeta /dist con CleanWebpackPlugin

Con el tiempo, la carpeta /dist puede acumular archivos viejos o no utilizados. Es una buena práctica limpiarla antes de cada nueva compilación. CleanWebpackPlugin se encarga de esto.

Instálalo:

cnpm i clean-webpack-plugin -D

Integra el plugin en webpack.config.js:

// webpack.config.js
const CleanWebpackPlugin = require('clean-webpack-plugin'); // Importación para Webpack 4

module.exports = {
    // ...
    plugins: [
        new webpack.BannerPlugin('Copyright © Mi Empresa. Todos los derechos reservados.'),
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, 'src', 'index.template.html')
        }),
        new CleanWebpackPlugin(['dist']) // Limpia la carpeta 'dist' antes de cada compilación
    ]
};

Ahora, cada vez que ejecutes npm run build, la carpeta dist será eliminada y recreada con los nuevos archivos empaquetados.

6.4. Recarga en Caliente (Hot Module Replacement)

El Hot Module Replacement (HMR) permite que los módulos sean actualizados en tiempo de ejecución sin una recarga completa de la página. Es muy útil durante el desarrollo para ver los cambios instantáneamente.

HMR es un plugin integrado de Webpack. Configúralo de la siguiente manera:

// webpack.config.js
module.exports = {
    // ...
    devServer: {
        contentBase: path.resolve(__dirname, 'dist'),
        port: 8088,
        inline: true,
        historyApiFallback: true,
        hot: true // Habilita Hot Module Replacement
    },
    plugins: [
        // ...otros plugins
        new webpack.HotModuleReplacementPlugin() // Instancia del plugin de HMR
    ]
};

Al ejecutar npm run dev y modificar un archivo como greeting.js, la interfaz de usuario se actualizará sin recargar la página completa. La velocidad puede variar según la configuración de devtool y la complejidad del proyecto.

7. Optimización y Extensiones del Proyecto

7.1. Separación de la Configuración

Las configuraciones de Webpack pueden volverse complejas rápidamente. Es mejor separar las configuraciones para entornos de desarrollo y producción, y tener una base común. Para esto:

  1. Crea tres archivos en la raíz del proyecto: webpack.common.js (común), webpack.dev.js (desarrollo) y webpack.prod.js (producción).
  2. Instala webpack-merge para combinar estas configuraciones:
cnpm i webpack-merge -D

Ahora, migra las configuraciones:

// webpack.common.js
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: path.resolve(__dirname, 'src', 'main.js'),
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'app.js'
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\.(scss|sass)$/,
                use: ['style-loader', 'css-loader', 'sass-loader']
            },
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader"
                },
                exclude: /node_modules/
            },
            {
                test: /\.(png|jpg|jpeg|gif|bmp|webp)$/,
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 1024 * 40,
                        name: '[name].[ext]',
                        outputPath: 'img/'
                    }
                }]
            },
            {
                test: /\.(otf|eot|svg|ttf|woff|woff2)$/,
                use: ['file-loader']
            }
        ]
    },
    plugins: [
        new webpack.BannerPlugin('Copyright © Mi Empresa. Todos los derechos reservados.'),
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, 'src', 'index.template.html')
        }),
        new webpack.HotModuleReplacementPlugin()
    ]
};

// webpack.dev.js
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
    mode: 'development', // Especifica el modo de desarrollo
    devtool: 'eval-source-map', // Un devtool más rápido para desarrollo
    devServer: {
        contentBase: path.resolve(__dirname, 'dist'),
        port: 8088,
        inline: true,
        historyApiFallback: true,
        hot: true
    }
});

// webpack.prod.js
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const CleanWebpackPlugin = require('clean-webpack-plugin'); // Importación para Webpack 4

module.exports = merge(common, {
    mode: 'production', // Especifica el modo de producción
    devtool: 'source-map', // Source map completo para producción
    plugins: [
        new CleanWebpackPlugin(['dist'])
    ]
});

Finalmente, actualiza los scripts en package.json para usar las nuevas configuraciones y especificar el modo:

  "scripts": {
    "build": "webpack --config webpack.prod.js",
    "dev": "webpack-dev-server --open --config webpack.dev.js"
  },

Ahora, npm run build usará la configuración de producción y npm run dev la de desarrollo. La separación de código nos permite diferenciar optimizaciones y herramientas entre entornos.

7.2. Múltiples Puntos de Entrada y Salida

Para poryectos más grandes, puede que necesites múltiples puntos de entrada. Webpack los maneja configurando entry como un objeto y usando [name].js en output.filename para generar nombres de archivo dinámicos.

Crea un archivo another.js en src/:

// src/another.js
function createAnotherDiv() {
    let elem = document.createElement('div');
    elem.innerHTML = 'Este es el segundo punto de entrada.';
    return elem;
}

document.getElementById('app').appendChild(createAnotherDiv());

Modifica webpack.common.js:

// webpack.common.js
// ...
module.exports = {
    entry: {
        main: path.resolve(__dirname, 'src', 'main.js'),
        another: path.resolve(__dirname, 'src', 'another.js')
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js' // [name] se reemplaza por 'main' o 'another'
    },
    // ...
};

Ejecuta npm run build. Verás dist/main.js y dist/another.js. HtmlWebpackPlugin insertará automáticamente ambas etiquetas <script> en index.html. Si ejecutas npm run dev, ambas salidas aparecerán en la página.

7.3. Mejora del Manejo de CSS y Recursos

Aquí abordamos el postprocesamiento de CSS, la extracción, la eliminación de CSS redundante y la gestión avanzada de imágenes.

1. Añadir Prefijos CSS Automáticamente

Para añadir prefijos de proveedor automáticamente (como -webkit-transform), usaremos postcss-loader con autoprefixer.

cnpm i postcss-loader autoprefixer -D

Crea postcss.config.js en la raíz del proyecto:

// postcss.config.js
module.exports = {
    plugins: [
        require('autoprefixer')
    ]
};

Modifica la regla de CSS en webpack.common.js:

// webpack.common.js
// ...
module.exports = {
    // ...
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    { loader: 'style-loader' },
                    { loader: 'css-loader' },
                    { loader: 'postcss-loader' } // Añade postcss-loader
                ]
            },
            // ...
        ]
    },
    // ...
};

Si añades una propiedad como transform: rotate(45deg); a tu CSS, npm run dev mostrará los prefijos añadidos automáticamente en los estilos del navegador.

2. Separar CSS en Archivos Individuales

Aunque Webpack tiende a incrustar CSS en JS, a menudo es deseable tener archivos CSS separados. Para Webpack 4, usaremos extract-text-webpack-plugin@next.

cnpm i extract-text-webpack-plugin@next -D

Actualiza webpack.common.js:

// webpack.common.js
const ExtractTextPlugin = require('extract-text-webpack-plugin'); // Importa el plugin

module.exports = {
    // ...
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ExtractTextPlugin.extract({ // Usa extract para separar CSS
                    fallback: 'style-loader',
                    use: ['css-loader', 'postcss-loader']
                })
            },
            {
                test: /\.(scss|sass)$/,
                use: ExtractTextPlugin.extract({
                    fallback: 'style-loader',
                    use: ['css-loader', 'sass-loader', 'postcss-loader']
                })
            },
            // ...
        ]
    },
    plugins: [
        // ...
        new ExtractTextPlugin('css/[name].css') // Extrae CSS a un archivo 'css/[nombre].css'
    ]
};

Después de npm run build, encontrarás un archivo main.css (y another.css si tienes multiples entry points) en dist/css/, y index.html lo referenciará automáticamente.

3. Eliminación de CSS Redundante (PurifyCSS)

Para eliminar CSS no utilizado, usamos purifycss-webpack y glob. Esta optimización es ideal para producción.

cnpm i purifycss-webpack purify-css glob -D

Modifica webpack.prod.js:

// webpack.prod.js
const PurifyCssWebpack = require('purifycss-webpack');
const glob = require('glob');

module.exports = merge(common, {
    // ...
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new PurifyCssWebpack({
            paths: glob.sync(path.join(__dirname, 'src/**/*.html'), { nodir: true }) // Escanea archivos HTML
        })
    ]
});

Si añades CSS que no se usa en tus archivos HTML, npm run build lo eliminará del CSS empaquetado.

4. Gestión de Imágenes Avanzada

Para asegurar que las imágenes se manejen correctamente (especialmente con rutas relativas en CSS y la opción limit de url-loader), ajustaremos url-loader y la regla de CSS.

Si tu background.png no se muestra correctamente tras empaquetar, es probable que la ruta en CSS sea incorrecta en el archivo final. Necesitamos especificar publicPath para los assets referenciados en CSS.

// webpack.common.js
// ...
module.exports = {
    // ...
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ExtractTextPlugin.extract({
                    fallback: 'style-loader',
                    use: ['css-loader', 'postcss-loader'],
                    publicPath: '../' // Ruta pública para assets en CSS
                })
            },
            {
                test: /\.(png|jpg|svg|gif)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 1000, // Limite de 1KB para Base64
                            outputPath: 'assets/images/' // Ruta de salida en dist
                        }
                    }
                ]
            },
            // ...
        ]
    },
    // ...
};

Con publicPath: '../' en css-loader, las rutas relativas en tu CSS (como url(../assets/images/background.png)) se resolverán correctamente en el CSS extraído, apuntando a la carpeta de imágenes en dist.

7.4. Compresión de Código con Modos de Webpack

Webpack 4+ introduce la opción mode para simplificar las configuraciones de desarrollo y producción. En production, Webpack automáticamente realiza optimizaciones como la minificación de JavaScript.

Hemos configurado mode: 'development' en webpack.dev.js y mode: 'production' en webpack.prod.js, lo cual ya aplica estas optimizaciones. Estas configuraciones se activan automáticamente al ejecutar los scripts definidos en package.json con --config:

  "scripts": {
    "build": "webpack --config webpack.prod.js",
    "dev": "webpack-dev-server --open --config webpack.dev.js"
  },

El modo production minificará tu código JavaScript automáticamente, mientras que el modo development priorizará la velocidad de compilación y la depuración. Esto también resuelve advertencias previas sobre la falta de un modo especificado.

Etiquetas: webpack JavaScript frontend bundler loaders

Publicado el 7-1 23:43