Saltar al contenido principal

📦 ¿Qué es un Bundler?

📖 Definición

Un Bundler es una herramienta que combina múltiples archivos y módulos en uno o pocos archivos. Optimiza varios recursos como JavaScript, CSS, imágenes y los prepara para que puedan cargarse eficientemente en el navegador. Ejemplos conocidos incluyen Webpack, Vite, Rollup, Parcel.

🎯 Comprender a través de Analogías

Comparación con un Centro de Mudanzas

Se puede pensar en un bundler como un centro de mudanzas:

Antes de la mudanza (antes del bundling)
Objetos en la casa:
├─ Sala: Sofá, TV, Mesa (archivos JavaScript)
├─ Habitación: Cama, Escritorio, Armario (archivos CSS)
├─ Cocina: Refrigerador, Vajilla (archivos de imágenes)
└─ Baño: Lavabo, Toallas (archivos de fuentes)

Problemas:
- Transportar objetos individuales = ineficiente
- Sin orden = difícil de encontrar
- Desperdicio de espacio = lento

Centro de mudanzas (Bundler):
1. Clasificar objetos
2. Empacar en cajas
3. Etiquetar
4. Organizar eficientemente
5. Cargar en el camión

Después de la mudanza (después del bundling):
📦 Caja 1: Muebles (main.js)
📦 Caja 2: Utensilios de cocina (styles.css)
📦 Caja 3: Objetos pequeños (assets)

Ventajas:
- Transportar todo de una vez = rápido
- Organizado = fácil de encontrar
- Uso eficiente del espacio = optimizado

Comparación con una Biblioteca

Libros desordenados (antes del bundling)
- 1000 libros dispersos por el suelo
- Difícil de encontrar
- Desperdicio de espacio
- Polvoriento

Sistema de biblioteca (Bundler)
1. Clasificar por tema
2. Ordenar en estantes
3. Crear índice
4. Colocar etiquetas

Biblioteca organizada (después del bundling)
- Estantes por especialidad
- Búsqueda rápida
- Uso eficiente del espacio
- Gestión limpia

Comparación con la Preparación de Cocina

Antes de preparar (antes del bundling)
Refrigerador completo:
- 20 tipos de verduras
- 5 tipos de carne
- 30 especias
- Todos en diferentes lugares

Cocinero (Bundler):
1. Revisar menú del día
2. Seleccionar solo ingredientes necesarios
3. Preparar
4. Organizar en orden de cocción

Mise en place (después del bundling)
- Solo ingredientes necesarios organizados
- Listo para cocinar inmediatamente
- Eficiente
- Ahorro de tiempo

⚙️ Funcionamiento

1. Necesidad de un Sistema de Módulos

// Problema: Contaminación global
// Cargar múltiples scripts en HTML
<!DOCTYPE html>
<html>
<head>
<script src="utils.js"></script>
<script src="math.js"></script>
<script src="app.js"></script>
</head>
</html>

// utils.js
var name = 'Utils';
function log(msg) {
console.log(msg);
}

// math.js
var name = 'Math'; // ❌ ¡Conflicto! Sobrescribe name de utils.js
function add(a, b) {
return a + b;
}

// app.js
console.log(name); // Imprime 'Math' (esperaba: 'Utils')
log('Hello'); // Funciona, pero no está claro de dónde viene la función

// Problemas:
// 1. Conflictos de nombres de variables
// 2. Orden de dependencias importante
// 3. Contaminación del namespace global
// 4. Muchos archivos = muchas solicitudes HTTP
// 5. El código no utilizado también se carga

2. Sistema de Módulos

// ✅ ES6 Modules (ESM)
// utils.js
export const name = 'Utils';
export function log(msg) {
console.log(msg);
}

// math.js
export const name = 'Math';
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}

// app.js
import { log } from './utils.js';
import { add, subtract } from './math.js';

log('Hello'); // ¡Claro!
console.log(add(1, 2)); // 3

// Ventajas:
// - Aislamiento de namespace
// - Dependencias explícitas
// - Tree shaking posible
// - Reutilización fácil

// Pero problemas:
// 1. Navegadores antiguos no compatibles
// 2. Aún muchas solicitudes HTTP
// 3. Resolución de módulos lenta

// → ¡Se necesita Bundler!

3. Flujo de Trabajo del Bundler

// Paso 1: Entry Point (Punto de entrada)
// webpack.config.js
module.exports = {
entry: './src/index.js' // Punto de inicio
};

// Paso 2: Dependency Graph (Gráfico de dependencias)
// index.js
import { render } from './render.js';
import './styles.css';
import logo from './logo.png';

// render.js
import { createElement } from './dom.js';
import { formatDate } from './utils.js';

// dom.js
import { debounce } from './helpers.js';

// Árbol de dependencias:
index.js
├─ render.js
│ ├─ dom.js
│ │ └─ helpers.js
│ └─ utils.js
├─ styles.css
└─ logo.png

// Paso 3: Loaders (Transformación)
// - CSS → JavaScript
// - SCSS → CSS → JavaScript
// - Imágenes → Base64 o archivo
// - TypeScript → JavaScript
// - JSX → JavaScript

// Paso 4: Plugins (Tareas adicionales)
// - Generar HTML
// - Comprimir código
// - Insertar variables de entorno
// - Generar sourcemaps

// Paso 5: Output (Salida)
// dist/
// ├─ bundle.js (todos los JS combinados)
// ├─ styles.css (CSS extraído)
// └─ logo.abc123.png (hash agregado)

// Resultado:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="dist/styles.css">
</head>
<body>
<script src="dist/bundle.js"></script>
<!-- ¡Un solo archivo! -->
</body>
</html>

4. Tree Shaking

// math.js
export function add(a, b) {
return a + b;
}

export function subtract(a, b) {
return a - b;
}

export function multiply(a, b) {
return a * b;
}

export function divide(a, b) {
return a / b;
}

// app.js
import { add, subtract } from './math.js';

console.log(add(1, 2)); // Se usa
console.log(subtract(5, 3)); // Se usa

// ¡multiply y divide no se usan!

// Bundler (después de Tree Shaking)
// bundle.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
console.log(add(1, 2));
console.log(subtract(5, 3));

// ¡multiply y divide fueron eliminados!
// ¡Tamaño del bundle reducido!

// Condiciones para Tree Shaking:
// 1. Usar ES6 Modules
// 2. Código sin efectos secundarios
// 3. Modo de producción

5. Code Splitting

// Code Splitting (División de código)

// ❌ Todo el código en un bundle
import Home from './pages/Home';
import About from './pages/About';
import Dashboard from './pages/Dashboard';
import Admin from './pages/Admin';

// bundle.js (2MB) - ¡demasiado grande!

// ✅ Dividir con importación dinámica
// app.js
const routes = {
'/': () => import('./pages/Home'),
'/about': () => import('./pages/About'),
'/dashboard': () => import('./pages/Dashboard'),
'/admin': () => import('./pages/Admin')
};

// Router
async function navigate(path) {
const loadPage = routes[path];
if (loadPage) {
const module = await loadPage();
module.default.render();
}
}

// Resultado:
// bundle.js (100KB) - Código base
// home.chunk.js (50KB) - Solo página de inicio
// about.chunk.js (30KB) - Solo página acerca de
// dashboard.chunk.js (200KB) - Solo dashboard
// admin.chunk.js (500KB) - Solo admin

// Ventajas:
// - Carga inicial rápida
// - Cargar solo cuando se necesita
// - El usuario lo percibe más rápido

// Code Splitting en React
import { lazy, Suspense } from 'react';

const Dashboard = lazy(() => import('./pages/Dashboard'));

function App() {
return (
<Suspense fallback={<div>Cargando...</div>}>
<Dashboard />
</Suspense>
);
}

💡 Ejemplos Prácticos

Configuración de Webpack

// webpack.config.js (básico)
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
// 1. Punto de entrada
entry: './src/index.js',

// 2. Salida
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.[contenthash].js', // Hash para invalidación de caché
clean: true // Limpiar carpeta dist antes del build
},

// 3. Modo
mode: 'production', // o 'development'

// 4. Loaders (Transformación de archivos)
module: {
rules: [
// JavaScript (Babel)
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
}
},

// CSS
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, // Extraer archivo CSS
'css-loader' // CSS → JS
]
},

// SCSS
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader' // SCSS → CSS
]
},

// Imágenes
{
test: /\.(png|jpg|gif|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // Incrustar como Base64 si es menor a 8KB
}
}
},

// Fuentes
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource'
}
]
},

// 5. Plugins (Funciones adicionales)
plugins: [
// Generar HTML
new HtmlWebpackPlugin({
template: './src/index.html',
minify: true
}),

// Extraer archivo CSS
new MiniCssExtractPlugin({
filename: 'styles.[contenthash].css'
})
],

// 6. Dev Server
devServer: {
static: './dist',
port: 3000,
hot: true, // Hot Module Replacement
open: true
},

// 7. Sourcemap (Depuración)
devtool: 'source-map',

// 8. Optimización
optimization: {
splitChunks: {
chunks: 'all', // Code Splitting
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10
}
}
}
}
};

// package.json
{
"scripts": {
"dev": "webpack serve --mode development",
"build": "webpack --mode production"
}
}

// Uso:
// npm run dev - Iniciar dev server
// npm run build - Build de producción

Configuración de Vite

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
// 1. Plugins
plugins: [react()],

// 2. Alias
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@components': path.resolve(__dirname, './src/components'),
'@utils': path.resolve(__dirname, './src/utils')
}
},

// 3. Dev Server
server: {
port: 3000,
open: true,
cors: true,
proxy: {
// Proxy de API
'/api': {
target: 'http://localhost:5000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
},

// 4. Build
build: {
outDir: 'dist',
assetsDir: 'assets',
sourcemap: true,

// Límite de tamaño de chunk
chunkSizeWarningLimit: 1000,

// Opciones de Rollup
rollupOptions: {
output: {
// División manual de chunks
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'router': ['react-router-dom'],
'ui': ['@mui/material']
}
}
},

// Compresión
minify: 'terser',
terserOptions: {
compress: {
drop_console: true // Eliminar console.log
}
}
},

// 5. CSS
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/variables.scss";`
}
},
modules: {
localsConvention: 'camelCase'
}
},

// 6. Variables de entorno
define: {
__APP_VERSION__: JSON.stringify('1.0.0')
}
});

// package.json
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}

// Ventajas de Vite:
// 1. Dev server muy rápido (usa ESM)
// 2. Inicio instantáneo (sin bundling)
// 3. HMR ultrarrápido
// 4. Configuración simple
// 5. Valores predeterminados modernos

Configuración de Rollup (para bibliotecas)

// rollup.config.js
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import { terser } from 'rollup-plugin-terser';
import pkg from './package.json';

export default {
// Punto de entrada
input: 'src/index.js',

// Múltiples salidas (diferentes formatos)
output: [
// CommonJS (Node.js)
{
file: pkg.main,
format: 'cjs',
sourcemap: true
},

// ES Module (para bundlers)
{
file: pkg.module,
format: 'esm',
sourcemap: true
},

// UMD (para navegador)
{
file: pkg.browser,
format: 'umd',
name: 'MyLibrary',
sourcemap: true,
globals: {
react: 'React'
}
}
],

// Dependencias externas (no incluir en el bundle)
external: ['react', 'react-dom'],

// Plugins
plugins: [
// Resolver módulos de node
resolve({
extensions: ['.js', '.jsx']
}),

// CommonJS → ESM
commonjs(),

// Transformación con Babel
babel({
exclude: 'node_modules/**',
babelHelpers: 'bundled',
presets: ['@babel/preset-env', '@babel/preset-react']
}),

// Compresión
terser()
]
};

// package.json
{
"name": "my-library",
"version": "1.0.0",
"main": "dist/index.cjs.js", // CommonJS
"module": "dist/index.esm.js", // ES Module
"browser": "dist/index.umd.js", // UMD
"scripts": {
"build": "rollup -c"
}
}

// Ventajas de Rollup:
// 1. Excelente Tree Shaking
// 2. Tamaño de bundle pequeño
// 3. Óptimo para desarrollo de bibliotecas
// 4. Múltiples formatos de salida

Parcel (sin configuración)

// package.json (¡no se requiere archivo de configuración!)
{
"name": "my-app",
"scripts": {
"dev": "parcel src/index.html",
"build": "parcel build src/index.html"
}
}

// src/index.html
<!DOCTYPE html>
<html>
<head>
<title>Parcel App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./index.js"></script>
</body>
</html>

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './styles.css';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

// src/styles.css
body {
margin: 0;
font-family: sans-serif;
}

// src/App.jsx
import React from 'react';

function App() {
return <h1>Hello Parcel!</h1>;
}

export default App;

// ¡Simplemente ejecutar!
// npm run dev

// Parcel hace automáticamente:
// - Transformación con Babel
// - Procesamiento de CSS
// - Optimización de imágenes
// - Configuración de HMR
// - Code Splitting

// Ventajas de Parcel:
// 1. Configuración cero
// 2. Inicio rápido
// 3. Optimización automática
// 4. Amigable para principiantes

// Desventajas:
// 1. Control detallado difícil
// 2. Ecosistema de plugins más pequeño

Estructura de Proyecto Real

# Create React App (Webpack)
my-app/
├── node_modules/
├── public/
│ ├── index.html
│ └── favicon.ico
├── src/
│ ├── components/
│ │ ├── Header.js
│ │ └── Footer.js
│ ├── App.js
│ ├── App.css
│ └── index.js
├── package.json
└── README.md

# Vite
my-vite-app/
├── node_modules/
├── public/
│ └── vite.svg
├── src/
│ ├── assets/
│ ├── components/
│ ├── App.jsx
│ ├── App.css
│ └── main.jsx
├── index.html # ¡En el directorio raíz!
├── vite.config.js
└── package.json

# Next.js (Webpack + SWC)
my-next-app/
├── node_modules/
├── pages/
│ ├── api/
│ ├── _app.js
│ ├── _document.js
│ └── index.js
├── public/
├── styles/
├── next.config.js
└── package.json

Ejemplo de Optimización de Rendimiento

// webpack.config.js (Optimización de producción)

const webpack = require('webpack');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

module.exports = {
mode: 'production',

optimization: {
// 1. Compresión de código
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true, // Eliminar console.log
drop_debugger: true, // Eliminar debugger
pure_funcs: ['console.log']
}
}
}),
new CssMinimizerPlugin()
],

// 2. Code Splitting
splitChunks: {
chunks: 'all',
minSize: 20000,
maxSize: 244000,
cacheGroups: {
// Biblioteca React
react: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'react',
priority: 20
},
// Otras bibliotecas
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10
},
// Código común
common: {
minChunks: 2,
name: 'common',
priority: 5,
reuseExistingChunk: true
}
}
},

// 3. Separar runtime chunk
runtimeChunk: 'single',

// 4. Estabilizar IDs de módulo
moduleIds: 'deterministic'
},

plugins: [
// 5. Compresión Gzip
new CompressionPlugin({
filename: '[path][base].gz',
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 10240,
minRatio: 0.8
}),

// 6. Análisis de Bundle
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'bundle-report.html'
}),

// 7. Variables de entorno
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
],

// 8. Caché
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
},

// 9. Advertencias de rendimiento
performance: {
hints: 'warning',
maxEntrypointSize: 512000,
maxAssetSize: 512000
}
};

// Resultado:
// Antes: bundle.js (2.5MB)
// Después:
// react.js (140KB)
// vendors.js (300KB)
// common.js (50KB)
// main.js (100KB)
// Total: 590KB (¡76% de reducción!)

Configuración Específica por Entorno

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

module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
};

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

module.exports = merge(common, {
mode: 'development',
devtool: 'eval-source-map', // Sourcemap rápido
devServer: {
hot: true,
port: 3000
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'] // CSS inline en JS
}
]
}
});

// webpack.prod.js (producción)
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = merge(common, {
mode: 'production',
devtool: 'source-map', // Sourcemap preciso
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, // Extraer archivo CSS
'css-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
})
]
});

// package.json
{
"scripts": {
"dev": "webpack serve --config webpack.dev.js",
"build": "webpack --config webpack.prod.js"
}
}

🤔 Preguntas Frecuentes

P1. Webpack vs. Vite, ¿cuál elegir?

R: Elegir según las características del proyecto:

// Elegir Webpack
const webpackUseCases = {
adecuadoPara: [
'Requisitos de build complejos',
'Se requiere control detallado',
'Se necesitan muchos plugins',
'Proyectos legacy',
'Soporte para navegadores antiguos'
],
ventajas: [
'Ecosistema maduro',
'Plugins abundantes',
'Control completo',
'Probado en proyectos grandes'
],
desventajas: [
'Dev server lento',
'Configuración compleja',
'Curva de aprendizaje pronunciada'
]
};

// Elegir Vite
const viteUseCases = {
adecuadoPara: [
'Proyectos nuevos',
'Se desea experiencia de desarrollo rápida',
'Solo se soportan navegadores modernos',
'Se prefiere configuración simple'
],
ventajas: [
'Dev server ultrarrápido',
'Inicio instantáneo',
'Configuración simple',
'Valores predeterminados modernos',
'HMR muy rápido'
],
desventajas: [
'Ecosistema relativamente más pequeño',
'Problemas con algunas bibliotecas legacy',
'Build de producción usa Rollup'
]
};

// Comparación de velocidad:
// Inicio de dev server:
// Webpack: 10-60 segundos
// Vite: 0.5-2 segundos (¡20-100x más rápido!)

// HMR (Hot Module Replacement):
// Webpack: 0.5-2 segundos
// Vite: 50-200ms (¡10x más rápido!)

// Build de producción:
// Webpack: Similar
// Vite: Similar (ambos suficientemente rápidos)

// Recomendación:
// - Proyecto nuevo: Vite
// - Proyecto existente: Mantener Webpack
// - Migración: Considerar cuidadosamente

P2. ¿Cómo reducir el tamaño del bundle?

R: Usar múltiples técnicas de optimización:

// 1. Tree Shaking (eliminar código no utilizado)

// ❌ Mal ejemplo (importación completa)
import _ from 'lodash'; // Biblioteca completa (70KB)
_.debounce(fn, 100);

// ✅ Buen ejemplo (solo lo necesario)
import debounce from 'lodash/debounce'; // Solo 2KB
debounce(fn, 100);

// Mejor ejemplo aún (lodash-es)
import { debounce } from 'lodash-es'; // Tree Shaking posible

// 2. Code Splitting (división de código)
// Dividir por ruta
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));

// Carga condicional
if (user.isAdmin) {
const AdminPanel = await import('./AdminPanel');
AdminPanel.render();
}

// 3. Dynamic Import (importación dinámica)
button.addEventListener('click', async () => {
const module = await import('./heavyFeature.js');
module.doSomething();
});

// 4. Usar CDN externo
// webpack.config.js
module.exports = {
externals: {
react: 'React',
'react-dom': 'ReactDOM'
}
};

// index.html
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>

// 5. Optimización de imágenes
// - Usar formato WebP
// - Escalar al tamaño adecuado
// - Lazy loading

// webpack.config.js
{
test: /\.(png|jpg|jpeg)$/,
use: [
{
loader: 'image-webpack-loader',
options: {
mozjpeg: { quality: 75 },
pngquant: { quality: [0.65, 0.9] }
}
}
]
}

// 6. Compression (compresión)
// Gzip, Brotli
new CompressionPlugin({
algorithm: 'brotliCompress',
test: /\.(js|css|html|svg)$/
});

// 7. Bundle Analysis (análisis)
npm install --save-dev webpack-bundle-analyzer

// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

plugins: [
new BundleAnalyzerPlugin()
]

// 8. Reemplazar Moment.js
// ❌ Moment.js (70KB)
import moment from 'moment';

// ✅ date-fns (2-5KB)
import { format } from 'date-fns';

// ✅ Day.js (2KB)
import dayjs from 'dayjs';

// Resultado:
// Antes: 2.5MB
// Después: 500KB (¡80% de reducción!)

P3. El dev server es lento, ¿qué puedo hacer?

R: Hay varios métodos de optimización:

// webpack.config.js (Optimización de dev server)

module.exports = {
// 1. Activar caché
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
},

// 2. Optimizar sourcemap
devtool: 'eval-cheap-module-source-map', // ¡Rápido!
// 'eval-source-map' - medio
// 'source-map' - lento (para producción)

// 3. Caché de Babel
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true // Activar caché
}
}
}
]
},

// 4. Optimización de resolución
resolve: {
// Orden de extensiones
extensions: ['.js', '.jsx'], // Solo necesarias

// Alias
alias: {
'@': path.resolve(__dirname, 'src')
},

// Rutas de módulos
modules: ['node_modules'] // Explícito
},

// 5. Configuración de dev server
devServer: {
hot: true, // HMR
liveReload: false, // No necesario con HMR
client: {
overlay: {
errors: true,
warnings: false // Solo errores
}
}
},

// 6. Desactivar advertencias de rendimiento (durante desarrollo)
performance: {
hints: false
},

// 7. Minimizar estadísticas
stats: 'errors-warnings'
};

// ¡O migrar a Vite!
// Webpack: 30 segundos de inicio
// Vite: 1 segundo de inicio (¡30x más rápido!)

P4. ¿Se puede desarrollar sin bundler?

R: Los navegadores modernos soportan ES Modules, pero hay limitaciones:

<!-- Sin bundler (con ESM) -->
<!DOCTYPE html>
<html>
<head>
<title>No Bundler</title>
</head>
<body>
<div id="app"></div>

<!-- type="module" requerido -->
<script type="module">
import { render } from './render.js';
import { utils } from './utils.js';

render();
</script>
</body>
</html>

<!-- render.js -->
<script type="module">
export function render() {
document.getElementById('app').innerHTML = '<h1>Hello!</h1>';
}
</script>

<!-- Ventajas: -->
- No se requiere configuración
- Inicio inmediato
- Simple

<!-- Desventajas: -->
- HTTP/2 requerido (muchas solicitudes)
- No se soportan node_modules
- No hay transformación de TypeScript
- JSX no utilizable
- Procesamiento de imágenes/CSS difícil
- Optimización de producción difícil
- Navegadores antiguos no soportados

<!-- Import Maps (experimental) -->
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react@18",
"react-dom": "https://esm.sh/react-dom@18"
}
}
</script>

<script type="module">
import React from 'react';
import ReactDOM from 'react-dom';

// ¡Cargado directamente desde CDN!
</script>

<!-- Conclusión: -->
<!-- Prototipo pequeño: Sin bundler posible -->
<!-- Proyecto real: ¡Bundler necesario! -->

P5. ¿Cómo usar TypeScript con un bundler?

R: La mayoría de los bundlers soportan TypeScript:

// Webpack + TypeScript
// webpack.config.js
module.exports = {
entry: './src/index.tsx',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['.tsx', '.ts', '.js']
}
};

// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"jsx": "react",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true
},
"include": ["src"]
}

// Vite + TypeScript (¡soporte automático!)
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
plugins: [react()]
});

// ¡Simplemente escribir archivos .tsx!
// src/App.tsx
import React from 'react';

interface Props {
name: string;
age: number;
}

const App: React.FC<Props> = ({ name, age }) => {
return (
<div>
<h1>Hola, {name}!</h1>
<p>Edad: {age}</p>
</div>
);
};

export default App;

// esbuild (ultrarrápido)
// build.js
const esbuild = require('esbuild');

esbuild.build({
entryPoints: ['src/index.tsx'],
bundle: true,
outfile: 'dist/bundle.js',
loader: { '.tsx': 'tsx' },
minify: true
});

// SWC (estándar de Next.js)
// .swcrc
{
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": true
},
"target": "es2020"
}
}

// Comparación de velocidad:
// tsc (TypeScript): Lento
// Babel: Medio
// esbuild: 10-100x más rápido
// SWC: 20-70x más rápido

// Recomendación:
// - Vite: esbuild incorporado
// - Next.js: SWC estándar
// - Webpack: ts-loader o babel-loader

🎓 Próximos Pasos

Si has entendido los bundlers, aprende a continuación:

  1. ¿Qué es Node.js? (documento en preparación) - Entorno de ejecución para bundlers
  2. ¿Qué es Git? (documento en preparación) - Control de versiones para desarrollo seguro
  3. ¿Qué es CI/CD? - Construcción y despliegue automatizados

🎬 Conclusión

Los bundlers son herramientas indispensables del desarrollo web moderno:

  • Webpack: Potente y maduro, configuración compleja
  • Vite: Experiencia de desarrollo ultrarrápida, moderno
  • Rollup: Desarrollo de bibliotecas, excelente Tree Shaking
  • Parcel: Configuración cero, amigable para principiantes

¡Elige el bundler que se ajuste a tu proyecto y optimízalo para crear aplicaciones web rápidas!