Webpack, paso a paso.
Configuración básica explicada de un proyecto Javascript que cubre prácticamente todas las casuísticas generales.
El objetivo es poder poner en marcha un proyecto con las siguientes características:
- Javascript ES6 o Typescript.
- Less/Sass + PostCSS.
- Gestión de assets, imágenes sobre todo.
- Rutas relativas para subdominios.
- Sourcemaps en desarrollo.
- Minificación y ofuscación en producción.
Qué es Webpack.
Webpack es un module bundler, o dicho de otra forma, una librería que nos permite gestionar todos los recursos necesarios para correr nuestra aplicación mediante el uso de sintaxis ES6 dentro de nuestro código fuente en Javascript. Nos permite cosas tan locas como importar un archivo CSS desde el JS de un componente y automáticamente añadirlo al bundle final.
Índice.
El siguiente índice muestra todos los pasos que hay que seguir para comprender una configuración completa de Webpack.
- Configuración básica, bundling con import/export y generación de el primer bundle. Output y naming del bundle.
- Generación de HTML con los bundles insertados.
- ES6, Babel y Typescript. ESLint y TSLint.
- CSS: Style loader y CSS loader??? PostCSS, less y Extract Plugin.
- Gestión de assets y FileLoader. Output relativo de los assets.
- Diferentes Entornos! minifica y ofusca para producción. Variables de entorno y webpack mege.
- Webpack Dev Server.
01 Configuración básica.
Instalamos las siguientes dependencias:
- webpack
- webpack-cli
Explicación archivo configuración.
Webpack espera recibir una configuración como un objeto plano de javascript. Cuando se ejecuta webpack sobre la configuración, busca sobre en el principal módulo exportado del script y si es un objeto plano lo procesa como la configuración final. También puede recibir una función que recibe como parámetros las variables de entorno y espera retornar un archivo plano de configuración. Esto nos servirá más adelante para definir los entornos de producción y de desarrollo.
- Definimos el entry point de nuestra aplicación. El archivo JS. El nombre del atributo dentro de entry determinará el nombre del bundle, en nuestro caso app, pero puede ser cualquier nombre. Se usará para referenciar a ese bundle en otras partes de la configuración.
- La salida en output, path es una ruta absoluta y filename es el patrón que seguirá el archivo que va a sacar webpack. Documentación de la api aquí.
const path = require('path');const basePath = __dirname;
const distPath = 'dist';const webpackInitConfig = {
mode: 'development',
resolve: {
extensions: ['.js']
},
entry: {
app: ['./src/index.js'],
},
output: {
path: path.join(basePath, distPath),
filename: '[chunkhash][name].js'
}
};module.exports = webpackInitConfig;
Ejecución
Ejecutar con:
npm run buildGenerará un asset js en la carpeta definida previamente, en nuestro caso dist.
{
...Mi package.json
"scripts": {
"build": "webpack --config=webpack.config.js",
}
}02 Generación HTML.
Instalar las siguientes dependencias:
- html-webpack-plugin
Como hemos explicado previamente, webpack es un module bundler de JS, por lo tanto no soporta nativamente el procesamiento de HTML. Para ello usaremos un paquete llamado html-webpack-plugin. El plugin se encargará de cargar los bundles generados dentro de una etiqueta script y de copiar el archivo html a la carpeta de destino.
Este plugin tiene además soporte para plantillas, como por ejemplo EJS, lo que nos permite insertar variables de entorno dentro del html. Un ejemplo, podríamos insertar la token_key con la que se carga el script de Google Maps.
Cuando ejecutemos la build, veremos nuestro html con los scripts embebidos en él. Ya tenemos todo listo para un desarrollo sencillo.
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');const basePath = __dirname;
const distPath = 'dist';
const indextInput = './src/index.html';
const indexOutput = 'index.html';const webpackInitConfig = {
mode: 'development',
resolve: {
extensions: ['.js']
},
entry: {
app: ['./src/index.js'],
},
output: {
path: path.join(basePath, distPath),
filename: '[chunkhash][name].js'
},
plugins: [
new HTMLWebpackPlugin({
filename: indexOutput,
template: indextInput,
})
]
};module.exports = webpackInitConfig;
03 ES6 y babel.
Instalar las siguientes dependencias:
- @babel/core
- @babel/preset-env
- @babel/polyfill
- babel-loader
Crear los siguientes archivos, relativos a la raíz (muy importante):
- .babelrc
Babel lo vamos a usar para pasar código ES6 en adelante, a ES5, de esta forma esta notación será compatible en todos los navegadores. Además vamos a añadir polyfills, para dar soporte a navegadores antiguos de funciones que no están presentes. Por ejemplo, Object.assign.
Para usar babel dentro de webpack necesitamos instalar las dependencias especificadas anteriormente, paso a explicarlas a continuación:
- @babel/core, es el núcleo de babel.
- @babel/preset-env, es el preset, o configuración, que le dice a babel cómo queremos transpilar nuestro código. Este preset en concreto es el que sirve para traducir el código de Javascript presente en las últimas especificaciones del estándar a ES5 (arrow functions, spread operator). Se irá actualizando según vaya creciendo la especificación. En este preset en concreto no se incluyen funcionalidades que estén en un stage avanzado. Sólo las definitivas. Existen múltiples de presets, por ejemplo, para React (jsx), para Typescript, flow…
- Babel-loader, es el encargado de comunicar a webpack con babel. Es un conector entre ambas librerías.
- @babel/polyfill, para dar soporte a funciones modernas en navegadores antiguos. Por ejemplo Promise o Object.assign. Los desarrolladores de la librería recomiendan incorporarlo el primero de los scripts dentro del entry point. Esto es porque para que funcione correctamente tiene que ser el primero que se ejecute.
.babelrc
"presets": [
"@babel/preset-env"
]
}webpack.config.js
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');const basePath = __dirname;
const distPath = 'dist';
const indextInput = './src/index.html';
const indexOutput = 'index.html';const webpackInitConfig = {
mode: 'development',
resolve: {
extensions: ['.js']
},
entry: {
app: ['@babel/polyfill', './src/index.js'],
},
output: {
path: path.join(basePath, distPath),
filename: '[chunkhash][name].js'
},
module: {
rules: [
{
test: /\.js/,
exclude: /node_modules/,
use: ['babel-loader']
}
]
},
plugins: [
new HTMLWebpackPlugin({
filename: indexOutput,
template: indextInput,
})
]
};module.exports = webpackInitConfig;
Typescript.
Instalar las siguientes dependencias:
- typescript (instalar como dependencia y globalmente)
- ts-loader
Typescript es un superset de Javascript que requiere de una transpilación previa para correr en navegadores. Proporciona comprobación estática de tipos (en tiempo de desarrollo). Esto nos puede resultar útil para localizar fallos antes de que llegue el código a producción.
Typescript incluye su propia librería para pasar código ES6 a ES5, por lo que, por defecto no necesita Babel para transpilar.
Lo cierto es que la última versión de Babel ha traído consigo un plugin para procesar Typescript. Yo soy partidario de si existe un plugin para babel usarlo, por consistencia de librerías, pero el plugin de babel para Typescript simplemente transpila el código, no hace comprobación de tipos, por lo que estaría perdiendo una funcionalidad muy interesante de Typescript.
A continuación se muestra cómo se transpila con su propia librería:
- Necesitamos crear un archivo de configuración de Typescript con ```tsc — init```.
- Modificar el webpack.config.js
- Empezar a programar.
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');const basePath = __dirname;
const distPath = 'dist';const indextInput = './src/index.html';
const indexOutput = 'index.html';const webpackInitConfig = {
mode: 'development',
resolve: {
extensions: ['.js','.ts']
},
entry: {
app: ['@babel/polyfill', './src/index.ts'],
},
output: {
path: path.join(basePath, distPath),
filename: '[chunkhash][name].js'
},
module: {
rules: [
{
test: /\.js/,
exclude: /node_modules/,
use: ['babel-loader']
},
{
test: /\.ts/,
exclude: /node_modules/,
use: ['ts-loader']
}
]
},
plugins: [
new HTMLWebpackPlugin({
filename: indexOutput,
template: indextInput
})
]
};module.exports = webpackInitConfig;
ESlint.
Instalar las siguientes dependencias:
- eslint (global)
- eslint-loader
Eslint permite establecer unas reglas de estilo del código y obligar a la gente del equipo a escribir código de una manera uniforme.
Eslint es una librería ajena a webpack, que se puede usar independientemente del task runner o module bundler que necesite.
A modo similar con Babel, ESlint soporta plugins de terceros que definen una serie de reglas. El más conocido es el de Airbnb y se instala mediante NPM, de todas formas estas reglas se puede sobreescribir fácilmente especificándolo en el archivo propio de configuración.
Para configurar Eslint hay que crear un archivo de configuración en la raíz que define las reglas o conjunto de reglas que vamos a seguir. Desde las últimas versiones, ESlint tiene un asistente de instalación muy práctico que es el que usaremos a continuación:
Una vez instalado eslint de forma global ejecutar el siguiente código en el terminal:
eslint --initHay que seguir los pasos que indica y finalmente instalará las dependencias que necesite y creará el archivo de configuración necesario (existen varios formatos, por lo general el más usado es en JS o JSON).
Por defecto webpack no soporta eslint, pero como hicimos con babel y Typescript, existe una librería “puente” que inyecta ESlint dentro del flujo de webpack, que nos permite mostrar avisos en el log de resultados de webpack o abortar la compilación en el caso de encontrar un fallo grave de est.
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');const basePath = __dirname;
const distPath = 'dist';const indextInput = './src/index.html';
const indexOutput = 'index.html';const webpackInitConfig = {
mode: 'development',
resolve: {
extensions: ['.js', '.ts'],
},
entry: {
app: ['@babel/polyfill', './src/index.js'],
},
output: {
path: path.join(basePath, distPath),
filename: '[chunkhash][name].js',
},
module: {
rules: [
{
test: /\.js/,
exclude: /node_modules/,
use: [
'babel-loader',
'eslint-loader',
],
},
{
test: /\.ts/,
exclude: /node_modules/,
use: ['ts-loader'],
},
],
},
plugins: [
new HTMLWebpackPlugin({
filename: indexOutput,
template: indextInput,
}),
],
};module.exports = webpackInitConfig;
ESlint sólo comprueba el estilo de Javascript. Si queremos hacer lo mismo para Typescript, hay que instalar una librería llamada TSlint y su correspondiente loader, así como sus propias reglas (las de Airbnb también están disponibles).
Esta configuración para Typescript es exactamente igual que la de Javascript, pero dentro de las reglas de TS y el proceso de configuración del archivo de reglas también puede variar un poco (tienen sintaxis parecidas pero no iguales).
04 CSS
Instalamos las siguientes dependencias:
- style-loader
- css-loader
Como hemos dicho previamente, Webpack es un module-bundler, se encarga de encapsular javascript, por lo tanto webpack no sabe manejar archivos que no sean CSS de forma nativa. Necesitamos loaders y plugins.
Tenemos 2 loaders:
- style-loader, sirve para inyectar código css (ojo, no archivo) dentro del dom en una etiqueta <style></style>.
- css-loader, sirve para poder importar código css desde nuestro código javascript, le pasa a style-loader los estilos para que los inyecte en el dom.
El comportamiento esperado de esta configuración es un archivo JS con nuestro código y los estilos inyectándose dentro del javascript. No publica un archivo css como sería esperado, posteriormente abordaremos este tema.
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');const basePath = __dirname;
const distPath = 'dist';const indextInput = './src/index.html';
const indexOutput = 'index.html';const webpackInitConfig = {
mode: 'development',
resolve: {
extensions: ['.js', '.ts'],
},
entry: {
app: ['@babel/polyfill', './src/index.js'],
},
output: {
path: path.join(basePath, distPath),
filename: '[chunkhash][name].js',
},
module: {
rules: [
{
test: /\.js/,
exclude: /node_modules/,
use: [
'babel-loader',
'eslint-loader',
],
},
{
test: /\.ts/,
exclude: /node_modules/,
use: ['ts-loader'],
},
{
test: /\.css/,
exclude: /node_modules/,
use: [
'style-loader',
'css-loader',
],
},
],
},
plugins: [
new HTMLWebpackPlugin({
filename: indexOutput,
template: indextInput,
}),
],
};module.exports = webpackInitConfig;
PostCSS
Instalamos las siguientes dependencias:
- postcss-loader
- postcss-cssnext
Es una librería que permite manipular el CSSpara, mediante plugins, realizar todo tipo de operaciones. Por ejemplo podemos usarla añadir los vendor-prefixes o dar soporte a funcionalidades nuevas de css en navegadores antiguos. PostCSS sólo es el motor, al igual que @babel/core, necesita de plugins para realizar las transformaciones que deseemos. En nuestro caso usaremos CSSNext.
Al igual que previamente en Babel o ESlint, necesitaremos una librería puente para conectar Postcss con webpack, postcss-loader.
Postcss se configura mediante un archivo de configuración en la raíz.
postcss.config.js
const postcss = require('postcss-cssnext');module.exports = {
plugins: [postcss],
};
webpack.config.js
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');const basePath = __dirname;
const distPath = 'dist';const indextInput = './src/index.html';
const indexOutput = 'index.html';const webpackInitConfig = {
mode: 'development',
resolve: {
extensions: ['.js', '.ts'],
},
entry: {
app: ['@babel/polyfill', './src/index.js'],
},
output: {
path: path.join(basePath, distPath),
filename: '[chunkhash][name].js',
},
module: {
rules: [
{
test: /\.js/,
exclude: /node_modules/,
use: [
'babel-loader',
'eslint-loader',
],
},
{
test: /\.ts/,
exclude: /node_modules/,
use: ['ts-loader'],
},
{
test: /\.css/,
exclude: /node_modules/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
],
},
],
},
plugins: [
new HTMLWebpackPlugin({
filename: indexOutput,
template: indextInput,
}),
],
};module.exports = webpackInitConfig;
LESS (y Sass)
Instalamos las siguientes dependencias:
- less
- less-loader
Para compilar Less necesitaremos tanto el compilador de node como su librería puente con webpack. Necesitaremos añadir una nueva regla a nuestra configuración de webpack para archivos less, usaremos la misma cadena de loaders que previamente hemos usado en css, junto con postcss.
En el caso de Sass es totalmente igual, pero instalando las dependencias de su compilador y su loader.
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');const basePath = __dirname;
const distPath = 'dist';const indextInput = './src/index.html';
const indexOutput = 'index.html';const webpackInitConfig = {
mode: 'development',
resolve: {
extensions: ['.js', '.ts'],
},
entry: {
app: ['@babel/polyfill', './src/index.js'],
},
output: {
path: path.join(basePath, distPath),
filename: '[chunkhash][name].js',
},
module: {
rules: [
{
test: /\.js/,
exclude: /node_modules/,
use: [
'babel-loader',
'eslint-loader',
],
},
{
test: /\.ts/,
exclude: /node_modules/,
use: ['ts-loader'],
},
{
test: /\.css/,
exclude: /node_modules/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
],
},
{
test: /\.less/,
exclude: /node_modules/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader',
],
},
],
},
plugins: [
new HTMLWebpackPlugin({
filename: indexOutput,
template: indextInput,
}),
],
};module.exports = webpackInitConfig;
Extract text plugin
Instalamos las siguientes dependencias:
- mini-css-extract-plugin
Hasta ahora habíamos visto cómo usar CSS e incorporarlos al DOM, pero lo cierto es que podemos no tener DOM y simplemente querer publicar una librería con estilos. Por defecto webpack y style-loader no es capaz de sacar a un archivo aparte los estilos por lo que existe una librería para sacar estos estilos y escribirlos en un archivo.
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const MiniCSSExtract = require('mini-css-extract-plugin');const basePath = __dirname;
const distPath = 'dist';const indextInput = './src/index.html';
const indexOutput = 'index.html';const webpackInitConfig = {
mode: 'development',
resolve: {
extensions: ['.js', '.ts'],
},
entry: {
app: ['@babel/polyfill', './src/index.js'],
},
output: {
path: path.join(basePath, distPath),
filename: '[chunkhash][name].js',
},
module: {
rules: [
{
test: /\.js/,
exclude: /node_modules/,
use: [
'babel-loader',
'eslint-loader',
],
},
{
test: /\.ts/,
exclude: /node_modules/,
use: ['ts-loader'],
},
{
test: /\.css/,
exclude: /node_modules/,
use: [
MiniCSSExtract.loader,
'css-loader',
'postcss-loader',
],
},
{
test: /\.less/,
exclude: /node_modules/,
use: [
MiniCSSExtract.loader,
'css-loader',
'postcss-loader',
'less-loader',
],
},
],
},
plugins: [
new HTMLWebpackPlugin({
filename: indexOutput,
template: indextInput,
}),
new MiniCSSExtract({
filename: '[name].css',
chunkFilename: '[id].css',
}),
],
};module.exports = webpackInitConfig;
05 assets.
Instalamos las siguientes dependencias:
- file-loader
Al igual que todo lo demás, webpack no entiende por defecto cuando importamos una imagen o cualquier otro archivo. File-loader nos permite dos cosas, la primera es meter en nuestro bundle aquellos archivos que importemos desde el JS y lo otro que nos permite es devolvernos la url relativa a la imagen dentro del Javascript o del CSS. Esto nos sirve por ejemplo para cargar una imagen de background-image o una tipografía en el css.
Si incorporáramos un mismo archivo en varios sitios diferentes, webpack es capaz de no importar ambos a la vez y sólo copiarnos el archivo una vez.
Una cosa útil que nos permite este loader es tanto copiar los assets en una carpeta concreta dentro de nuestra carpeta de bundle así como devolver en la url dentro del css o js la url relativa a un path que deseemos, esto es útil cuando estamos generando un bundle que va a ir dentro de un subdominio.
La configuración es bastante sencilla:
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const MiniCSSExtract = require('mini-css-extract-plugin');const basePath = __dirname;
const distPath = 'dist';const indextInput = './src/index.html';
const indexOutput = 'index.html';const webpackInitConfig = {
mode: 'development',
resolve: {
extensions: ['.js', '.ts'],
},
entry: {
app: ['@babel/polyfill', './src/index.js'],
},
output: {
path: path.join(basePath, distPath),
filename: '[chunkhash][name].js',
},
module: {
rules: [
{
test: /\.js/,
exclude: /node_modules/,
use: [
'babel-loader',
'eslint-loader',
],
},
{
test: /\.ts/,
exclude: /node_modules/,
use: ['ts-loader'],
},
{
test: /\.css/,
exclude: /node_modules/,
use: [
MiniCSSExtract.loader,
'css-loader',
'postcss-loader',
],
},
{
test: /\.less/,
exclude: /node_modules/,
use: [
MiniCSSExtract.loader,
'css-loader',
'postcss-loader',
'less-loader',
],
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
// outputPath: 'images/',
// publicPath: 'images/',
},
},
],
},
],
},
plugins: [
new HTMLWebpackPlugin({
filename: indexOutput,
template: indextInput,
}),
new MiniCSSExtract({
filename: '[name].css',
chunkFilename: '[id].css',
}),
],
};module.exports = webpackInitConfig;
06 Entornos.
Los artefactos en desarrollo y en producción no son los mismos, unos requieren sourcemaps para poder depurar mejor y en entornos de producción se requiere de eliminar todo lo que no es necesario.
Lo óptimo es que se pueda lanzar el mismo comando de compilación pero sólo cambiando simplemente una variable.
Desarrollo.
Para desarrollo es necesario proporcionar sourcemaps, son archivos que nos permiten depurar el código compilado y poder verlo y pararlo de una forma más cómoda.
Por defecto webpack tiene la opción de incorporar estos sourcemap dentro del JS, añadiendo el valor devtool.
De todas formas, para el CSS la cosa es un poco diferente, ya que al meter de por medio postcss-loader, necesitamos especificarle a todos los loader de la cadena que necesitamos los sourcemap.
La configuración queda de la siguiente forma:
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const MiniCSSExtract = require('mini-css-extract-plugin');const basePath = __dirname;
const distPath = 'dist';const indextInput = './src/index.html';
const indexOutput = 'index.html';const webpackInitConfig = {
mode: 'development',
devtool: 'inline-source-map',
resolve: {
extensions: ['.js', '.ts'],
},
entry: {
app: ['@babel/polyfill', './src/index.js'],
},
output: {
path: path.join(basePath, distPath),
filename: '[chunkhash][name].js',
},
module: {
rules: [
{
test: /\.js/,
exclude: /node_modules/,
use: [
'babel-loader',
'eslint-loader',
],
},
{
test: /\.ts/,
exclude: /node_modules/,
use: ['ts-loader'],
},
{
test: /\.css/,
exclude: /node_modules/,
use: [
MiniCSSExtract.loader,
{ loader: 'css-loader', options: { sourceMap: true } },
{ loader: 'postcss-loader', options: { sourceMap: true } },
],
},
{
test: /\.less/,
exclude: /node_modules/,
use: [
MiniCSSExtract.loader,
{ loader: 'css-loader', options: { sourceMap: true } },
{ loader: 'postcss-loader', options: { sourceMap: true } },
{ loader: 'less-loader', options: { sourceMap: true } },
],
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
// outputPath: 'images/',
// publicPath: 'images/',
},
},
],
},
],
},
plugins: [
new HTMLWebpackPlugin({
filename: indexOutput,
template: indextInput,
}),
new MiniCSSExtract({
filename: '[name].css',
chunkFilename: '[id].css',
}),
],
};module.exports = webpackInitConfig;
Producción.
Instalamos las siguientes dependencias:
- optimize-css-assets-webpack-plugin
Para producción nos centraremos en minificar el código.
Para el JS no es necesario instalar ninguna dependencia, pues Webpack es capaz de minificarlo y ofuscarlo simplemente estableciendo el modo de producción y eliminando la generación de sourcemap. Sin embargo el CSS requiere de un plugin.
Es esperado que webpack 5 incluya también un minificador para CSS pero la versión 4 todavía no lo trae:
Vamos a crear otro script dentro de package.json, que lance el nuevo archivo:
{
...Mi package.json
"scripts": {
"build": "webpack --config=webpack.config.js",
"build-prod": "webpack --config=webpack.config.prod.js"
}
}Sacaremos la configuración de producción a un archivo aparte:
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const MiniCSSExtract = require('mini-css-extract-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');const basePath = __dirname;
const distPath = 'dist';const indextInput = './src/index.html';
const indexOutput = 'index.html';const webpackInitConfig = {
mode: 'production',
devtool: 'none',
resolve: {
extensions: ['.js', '.ts'],
},
entry: {
app: ['@babel/polyfill', './src/index.js'],
},
output: {
path: path.join(basePath, distPath),
filename: '[chunkhash][name].js',
},
module: {
rules: [
{
test: /\.js/,
exclude: /node_modules/,
use: [
'babel-loader',
'eslint-loader',
],
},
{
test: /\.ts/,
exclude: /node_modules/,
use: ['ts-loader'],
},
{
test: /\.css/,
exclude: /node_modules/,
use: [
MiniCSSExtract.loader,
{ loader: 'css-loader' },
{ loader: 'postcss-loader' },
],
},
{
test: /\.less/,
exclude: /node_modules/,
use: [
MiniCSSExtract.loader,
{ loader: 'css-loader' },
{ loader: 'postcss-loader' },
{ loader: 'less-loader' },
],
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
// outputPath: 'images/',
// publicPath: 'images/',
},
},
],
},
],
},
plugins: [
new HTMLWebpackPlugin({
filename: indexOutput,
template: indextInput,
}),
new MiniCSSExtract({
filename: '[name].css',
chunkFilename: '[id].css',
}),
new OptimizeCssAssetsPlugin(),
],
};module.exports = webpackInitConfig;
Variables de entorno.
A la ejecución de webpack le podemos pasar variables a través del CLI. Una forma muy común de discernir entre una compilación de producción y una de desarrollo es mediante variables de entorno.
La nomenclatura en el CLI para definir las variables de entorno es la siguiente:
webpack --env.NODE_ENV=local --env.production --progressPara nuestra configuración pasaremos el código dentro de webpack.config.js => webpack.config.dev.js y dentro de webpack.config.js pondremos el siguiente código:
const prodConfig = require('./webpack.config.prod.js');
const devConfig = require('./webpack.config.dev.js');function webpackEnviromentSelector(env) {
if (env.production) return prodConfig;
if (env.devConfig) return devConfig;
return devConfig;
}module.exports = webpackEnviromentSelector;
Nuestro package.json quedará de la siguiente forma:
{
...Mi package.json
"scripts": {
"build": "webpack --config=webpack.config.js --env.development=true",
"build-prod": "webpack --config=webpack.config.js --env.production=true"
},
}Las posibilidades de este tipo de configuraciones son infinitas.
De forma adicional, cualquier variable de entorno podemos pasársela a nuestro bundle y sacar provecho de esas variables en el cliente. Por ejemplo, tenemos una URL de assets para producción y otra para desarrollo, podríamos especificar esa URL de forma individual en cada configuración y a la hora de formar las URLs en cliente cada uno tendría las suyas correspondientes a su entorno.
Para hacer esto hay que usar un plugin de Webpack llamado provide plugin. Por ejemplo la configuración en desarrollo quedaría así:
const path = require('path');
const webpack = require('webpack');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const MiniCSSExtract = require('mini-css-extract-plugin');const basePath = __dirname;
const distPath = 'dist';const indextInput = './src/index.html';
const indexOutput = 'index.html';const webpackInitConfig = {
mode: 'development',
devtool: 'inline-source-map',
resolve: {
extensions: ['.js', '.ts'],
},
entry: {
app: ['@babel/polyfill', './src/index.js'],
},
output: {
path: path.join(basePath, distPath),
filename: '[chunkhash][name].js',
},
module: {
rules: [
{
test: /\.js/,
exclude: /node_modules/,
use: [
'babel-loader',
'eslint-loader',
],
},
{
test: /\.ts/,
exclude: /node_modules/,
use: ['ts-loader'],
},
{
test: /\.css/,
exclude: /node_modules/,
use: [
MiniCSSExtract.loader,
{ loader: 'css-loader', options: { sourceMap: true } },
{ loader: 'postcss-loader', options: { sourceMap: true } },
],
},
{
test: /\.less/,
exclude: /node_modules/,
use: [
MiniCSSExtract.loader,
{ loader: 'css-loader', options: { sourceMap: true } },
{ loader: 'postcss-loader', options: { sourceMap: true } },
{ loader: 'less-loader', options: { sourceMap: true } },
],
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
// outputPath: 'images/',
// publicPath: 'images/',
},
},
],
},
],
},
plugins: [
new HTMLWebpackPlugin({
filename: indexOutput,
template: indextInput,
}),
new MiniCSSExtract({
filename: '[name].css',
chunkFilename: '[id].css',
}),
new webpack.DefinePlugin({
ENV: JSON.stringify({ env: { development: true } }),
}),
],
};module.exports = webpackInitConfig;
Dentro de nuestro código podríamos contemplarlo de la siguiente forma:
if(ENV.development) hazAlgo(); // RESULTADO en js final: if(true) hazAlgo()Webpack merge.
Instalamos las siguientes dependencias:
- webpack-merge
Webpack merge es una librería que te permite combinar una configuración de webpack base y una adicional, de tal forma que mantenga el tipo de objeto d e configuración que requiere webpack.
Esto es útil para mantener aquellos parámetros comunes a todas las configuraciones y reducir las configuraciones particulares a lo particular de cada entorno.
La estructura de configuración de webpack será la siguiente:
- webpack.config.js : encargado de descernir entre entornos.
- webpack.base.js : configuración base, común para todos los entornos.
- webpack.dev.js
- webpack.prod.js
const merge = require('webpack-merge');
const baseConfigGenerator = require('./webpack.config.base.js');
const prodConfig = require('./webpack.config.prod.js');
const devConfig = require('./webpack.config.dev.js');function webpackEnviromentSelector(env) {
let config; if (env.production) config = prodConfig;
if (env.development) config = devConfig; const baseConfig = baseConfigGenerator(env); return merge(baseConfig, config);
}module.exports = webpackEnviromentSelector;const path = require('path');
const webpack = require('webpack');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const MiniCSSExtract = require('mini-css-extract-plugin');const basePath = __dirname;
const distPath = 'dist';const indextInput = './src/index.html';
const indexOutput = 'index.html';function webpackConfigGenerator(env) {
const sourcemaps = !!env.development;const webpackInitConfig = {
resolve: {
extensions: ['.js', '.ts'],
},
entry: {
app: ['@babel/polyfill', './src/index.js'],
},
output: {
path: path.join(basePath, distPath),
filename: '[chunkhash][name].js',
},
module: {
rules: [
{
test: /\.js/,
exclude: /node_modules/,
use: ['babel-loader', 'eslint-loader'],
},
{
test: /\.ts/,
exclude: /node_modules/,
use: ['ts-loader'],
},
{
test: /\.css/,
exclude: /node_modules/,
use: [
MiniCSSExtract.loader,
{ loader: 'css-loader', options: { sourceMap: sourcemaps } },
{ loader: 'postcss-loader', options: { sourceMap: sourcemaps } },
],
},
{
test: /\.less/,
exclude: /node_modules/,
use: [
MiniCSSExtract.loader,
{ loader: 'css-loader', options: { sourceMap: sourcemaps } },
{ loader: 'postcss-loader', options: { sourceMap: sourcemaps } },
{ loader: 'less-loader', options: { sourceMap: sourcemaps } },
],
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
// outputPath: 'images/',
// publicPath: 'images/',
},
},
],
},
],
},
plugins: [
new HTMLWebpackPlugin({
filename: indexOutput,
template: indextInput,
}),
new MiniCSSExtract({
filename: '[name].css',
chunkFilename: '[id].css',
}),
new webpack.DefinePlugin({
ENV: JSON.stringify(env),
}),
],
}; return webpackInitConfig;
}module.exports = webpackConfigGenerator;const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');const webpackConfig = {
mode: 'production',
plugins: [
new OptimizeCssAssetsPlugin(),
],
};module.exports = webpackConfig;const webpackConfig = {
mode: 'development',
devtool: 'inline-source-map',
};module.exports = webpackConfig;
07 Webpack dev server.
Instalamos las siguientes dependencias:
- webpack-dev-server
Esta librería permite levantar un servidor de desarrollo sobre el que servir nuestro bundle y recargar en tiempo real la aplicación con cada cambio en el código.
Realmente para hacer funcionar este servidor de pruebas no es necesario nada más que sustituir en el script de lanzamiento webpack por webpack-dev-server, toda la interfaz de la cli es prácticamente igual:
{
...Mi package.json
"scripts": {
"start": "webpack-dev-server --config=webpack.config.js --env.development=true",
"build": "webpack --config=webpack.config.js --env.development=true",
"build-prod": "webpack --config=webpack.config.js --env.production=true"
},
}Conclusión.
Webpack es la herramienta más potente que tenemos para generar paquetes de librerías o empaquetar nuestra aplicación, destaco sobre todo lo fácil que es poder usar diferentes herramientas como Typescript, JSX, CSS-modules (súuper potente junto con Typescript) y que está en continuo desarollo implementando nuevas funcionalidades y mejoras.
Además de la capacidad de aprovechar todo el ecosistema Javascript. Un ejemplo es el loader para TS, por lo general los principales era muy lentos, pero con el nuevo plugin de Babel tenemos una opción para optimizar este proceso y ahorrar mucho tiempo.
Call me maybe:
Soy Alejandro, llevo desarrollando en Front End desde el 2016 y actualmente estoy en Idealista dentro del portal de profesionales, puedes localizarme tanto en Twitter como ver mis contribuciones en Github.
