Cómo usar webpack para hacer que tu app sea lo más pequeña posible
Una de las primeras cosas que debes hacer cuando optimizas una aplicación es hacer que sea tan pequeña como como sea posible. A continuación, te indicamos cómo hacerlo con webpack.
Usar el modo de producción (solo webpack 4)
Webpack 4 introdujo la nueva marca mode
. Podrías establecer
esta marca a 'development'
o 'production'
para sugerir el webpack que estás compilando
la aplicación para un entorno específico:
// webpack.config.js
module.exports = {
mode: 'production',
};
Asegúrate de habilitar el modo production
cuando compiles tu app para producción.
Esto hará que webpack aplique optimizaciones como la reducción y eliminación del código solo para desarrollo.
en bibliotecas y mucho más.
Lecturas adicionales
Habilitar reducción
La reducción ocurre cuando comprimes el código quitando espacios extra, acortando los nombres de las variables y y así sucesivamente. Para ello, puedes escribir lo siguiente:
// Original code
function map(array, iteratee) {
let index = -1;
const length = array == null ? 0 : array.length;
const result = new Array(length);
while (++index < length) {
result[index] = iteratee(array[index], index, array);
}
return result;
}
↓
// Minified code
function map(n,r){let t=-1;for(const a=null==n?0:n.length,l=Array(a);++t<a;)l[t]=r(n[t],t,n);return l}
Webpack admite dos formas de reducir el código: la reducción a nivel del paquete. opciones específicas del cargador. Deben usarse simultáneamente.
Minificación a nivel del paquete
La reducción a nivel del paquete comprime todo el paquete después de la compilación. Funciona de la siguiente manera:
Debes escribir el código de la siguiente manera:
// comments.js import './comments.css'; export function render(data, target) { console.log('Rendered!'); }
Webpack lo compila en aproximadamente lo siguiente:
// bundle.js (part of) "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony export (immutable) */ __webpack_exports__["render"] = render; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__comments_css__ = __webpack_require__(1); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__comments_css_js___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__comments_css__); function render(data, target) { console.log('Rendered!'); }
Un minificador la comprime en aproximadamente lo siguiente:
// minified bundle.js (part of) "use strict";function t(e,n){console.log("Rendered!")} Object.defineProperty(n,"__esModule",{value:!0}),n.render=t;var o=r(1);r.n(o)
En el paquete web 4, se habilita automáticamente la reducción a nivel del paquete (tanto en el segmento de producción como en el de producción).
y sin uno. Usa el minificador UglifyJS
en detalle. (Si alguna vez necesitas inhabilitar la reducción, usa el modo de desarrollo
o pasa false
a la opción optimization.minimize
).
En webpack 3, debes usar el complemento UglifyJS.
directamente. El complemento viene incluido en el paquete web. para habilitarlo, agrégalo a plugins
de la configuración:
// webpack.config.js
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.optimize.UglifyJsPlugin(),
],
};
Opciones específicas del cargador
La segunda forma de reducir el código son las opciones específicas del cargador (qué es un cargador
es). Con las opciones del cargador, puedes comprimir los elementos que
el minificador no puede reducir. Por ejemplo, cuando importas un archivo CSS con
css-loader
, se compila el archivo en una cadena:
/* comments.css */
.comment {
color: black;
}
// minified bundle.js (part of)
exports=module.exports=__webpack_require__(1)(),
exports.push([module.i,".comment {\r\n color: black;\r\n}",""]);
El minificador no puede comprimir este código porque es una cadena. Para reducir el contenido del archivo, debemos configura el cargador para que haga esto:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{ loader: 'css-loader', options: { minimize: true } },
],
},
],
},
};
Lecturas adicionales
- Documentos de UglifyJsPlugin
- Otros minificadores populares: Babel Minify, Cierre de Google Compilador
Especifica NODE_ENV=production
Otra forma de disminuir el tamaño del frontend es establecer el NODE_ENV
variable de entorno
en tu código con el valor production
.
Las bibliotecas leen la variable NODE_ENV
para detectar en qué modo deberían funcionar, en el
de desarrollo o producción. Algunas bibliotecas se comportan de manera diferente según esta variable. Para
Por ejemplo, cuando NODE_ENV
no se establece como production
, Vue.js realiza verificaciones y realiza impresiones adicionales.
advertencias:
// vue/dist/vue.runtime.esm.js
// …
if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.');
}
// …
React funciona de manera similar: carga una compilación de desarrollo que incluye las advertencias:
// react/index.js
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react.production.min.js');
} else {
module.exports = require('./cjs/react.development.js');
}
// react/cjs/react.development.js
// …
warning$3(
componentClass.getDefaultProps.isReactClassApproved,
'getDefaultProps is only used on classic React.createClass ' +
'definitions. Use a static property named `defaultProps` instead.'
);
// …
Estas verificaciones y advertencias suelen ser innecesarias en la producción, pero permanecen en el código.
aumentar el tamaño de la biblioteca. En el paquete web 4, para quitarlos, agrega lo siguiente:
La opción optimization.nodeEnv: 'production'
:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
nodeEnv: 'production',
minimize: true,
},
};
En el paquete web 3, usa DefinePlugin
en su lugar:
// webpack.config.js (for webpack 3)
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
}),
new webpack.optimize.UglifyJsPlugin()
]
};
La opción optimization.nodeEnv
y DefinePlugin
funcionan de la misma manera:
se reemplazan todos los casos de process.env.NODE_ENV
por el valor especificado. Con la
config de la parte superior:
Webpack reemplazará todos los casos de
process.env.NODE_ENV
por"production"
// vue/dist/vue.runtime.esm.js if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; } else if (process.env.NODE_ENV !== 'production') { warn('props must be strings when using array syntax.'); }
↓
// vue/dist/vue.runtime.esm.js if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; } else if ("production" !== 'production') { warn('props must be strings when using array syntax.'); }
Luego, el reductor quitará todas estas Ramas
if
: debido a que"production" !== 'production'
siempre es falso, y el complemento entiende que nunca se ejecutará el código dentro de estas ramas:// vue/dist/vue.runtime.esm.js if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; } else if ("production" !== 'production') { warn('props must be strings when using array syntax.'); }
↓
// vue/dist/vue.runtime.esm.js (without minification) if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; }
Lecturas adicionales
- Qué son las “variables de entorno”
- Documentación de Webpack sobre:
DefinePlugin
,EnvironmentPlugin
Cómo usar módulos de ES
La siguiente forma de disminuir el tamaño de la interfaz es usar ES módulos.
Cuando usas módulos ES, webpack puede realizar una eliminación de código no utilizado. La sacudida de árboles ocurre cuando un agrupador desvía el árbol de dependencias completo, verifica qué dependencias se usan y quita las que no se usan. Entonces: Si usas la sintaxis del módulo ES, webpack puede eliminar el código que no se usa:
Escribes un archivo con varias exportaciones, pero la app usa solo una de ellas:
// comments.js export const render = () => { return 'Rendered!'; }; export const commentRestEndpoint = '/rest/comments'; // index.js import { render } from './comments.js'; render();
Webpack comprende que
commentRestEndpoint
no se usa y no genera un punto de exportación separado en el paquete:// bundle.js (part that corresponds to comments.js) (function(module, __webpack_exports__, __webpack_require__) { "use strict"; const render = () => { return 'Rendered!'; }; /* harmony export (immutable) */ __webpack_exports__["a"] = render; const commentRestEndpoint = '/rest/comments'; /* unused harmony export commentRestEndpoint */ })
La reducción quita la variable sin usar:
// bundle.js (part that corresponds to comments.js) (function(n,e){"use strict";var r=function(){return"Rendered!"};e.b=r})
Esto funciona incluso con las bibliotecas si están escritas con módulos de ES.
Sin embargo, no es necesario que uses precisamente el minificador integrado de Webpack (UglifyJsPlugin
).
Cualquier reductor que admita la eliminación de código muerto
(p.ej., el complemento Babel Minify
o el complemento de Google Closure Compiler)
hará el truco.
Lecturas adicionales
Documentación sobre el paquete web sobre la eliminación de árboles
Optimiza imágenes
Las imágenes representan más de un
la mitad del tamaño de la página. Si bien
no son tan críticos como JavaScript (p.ej., no bloquean la representación), de todas formas consumen una gran parte del
el ancho de banda. Utiliza url-loader
, svg-url-loader
y image-webpack-loader
para optimizarlas en
webpack.
url-loader
intercala archivos estáticos pequeños en la
. Sin configuración, toma un archivo pasado, lo coloca junto al paquete compilado y muestra
la URL de ese archivo. Sin embargo, si especificamos la opción limit
, codificará los archivos más pequeños que
este límite como una URL de datos Base64 y mostrará esta URL. Esta
alinea la imagen en el código JavaScript y guarda una solicitud HTTP:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png|gif)$/,
loader: 'url-loader',
options: {
// Inline files smaller than 10 kB (10240 bytes)
limit: 10 * 1024,
},
},
],
}
};
// index.js
import imageUrl from './image.png';
// → If image.png is smaller than 10 kB, `imageUrl` will include
// the encoded image: 'data:image/png;base64,iVBORw0KGg…'
// → If image.png is larger than 10 kB, the loader will create a new file,
// and `imageUrl` will include its url: `/2fcd56a1920be.png`
svg-url-loader
funciona igual que url-loader
:
excepto que codifica archivos con el URL
codificación en lugar del Base64
uno. Esto es útil para imágenes SVG. Como los archivos SVG son solo texto sin formato, esta codificación
sean más efectivos.
module.exports = {
module: {
rules: [
{
test: /\.svg$/,
loader: "svg-url-loader",
options: {
limit: 10 * 1024,
noquotes: true
}
}
]
}
};
image-webpack-loader
comprime las imágenes
a través de ella. Admite imágenes JPG, PNG, GIF y SVG, así que lo usaremos para todos estos tipos.
Este cargador no incorpora imágenes en la app, por lo que debe funcionar junto con url-loader
y
svg-url-loader
Para evitar copiarlo y pegarlo en ambas reglas (una para imágenes JPG, PNG o GIF y otra para
una para SVG), incluiremos este cargador como una regla independiente con enforce: 'pre'
:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png|gif|svg)$/,
loader: 'image-webpack-loader',
// This will apply the loader before the other ones
enforce: 'pre'
}
]
}
};
La configuración predeterminada del cargador ya está lista, pero si quieres configurarla consulta las opciones del complemento. Para elige las opciones que quieres especificar, consulta la excelente guía de imágenes de Addy Osmani optimización.
Lecturas adicionales
- "¿Qué se usa la codificación base64 o para los que nunca?".
- Guía sobre la optimización de imágenes de Addy Osmani
Optimiza las dependencias
Más de la mitad del tamaño promedio de JavaScript proviene de dependencias, y una parte de ese tamaño podría ser simplemente innecesarios.
Por ejemplo, Lodash (a partir de la versión 4.17.4) agrega 72 KB de código reducido al paquete. Pero si solo utilizas, 20 de sus métodos, aproximadamente 65 KB de código reducido no hace nada.
Otro ejemplo es Moment.js. La versión 2.19.1 requiere 223 KB de código reducido, lo cual es enorme: el tamaño promedio de JavaScript en una página era de 452 KB en octubre 2017. Sin embargo, 170 KB de ese tamaño es la localización .tfvars. Si si no usas Moment.js en varios idiomas, estos archivos llenarán el paquete sin una que no tiene ningún propósito específico.
Todas estas dependencias se pueden optimizar con facilidad. Recopilamos enfoques de optimización en un repo de GitHub: revísalo.
Habilitar la concatenación de módulos para módulos ES (también conocida como elevación de alcance)
Cuando compilas un paquete, webpack une cada módulo en una función:
// index.js
import {render} from './comments.js';
render();
// comments.js
export function render(data, target) {
console.log('Rendered!');
}
↓
// bundle.js (part of)
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
var __WEBPACK_IMPORTED_MODULE_0__comments_js__ = __webpack_require__(1);
Object(__WEBPACK_IMPORTED_MODULE_0__comments_js__["a" /* render */])();
}),
/* 1 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_exports__["a"] = render;
function render(data, target) {
console.log('Rendered!');
}
})
Antes, esto era necesario para aislar los módulos CommonJS/AMD entre sí. Sin embargo, esto agregó un tamaño y una sobrecarga de rendimiento para cada módulo.
Webpack 2 introdujo compatibilidad con módulos ES que, a diferencia de los módulos CommonJS y AMD, pueden agruparse sin unir cada uno con una función. Y webpack 3 hizo posible esa agrupación, con concatenación de módulos. Aquí tienes lo que hace la concatenación de módulos:
// index.js
import {render} from './comments.js';
render();
// comments.js
export function render(data, target) {
console.log('Rendered!');
}
↓
// Unlike the previous snippet, this bundle has only one module
// which includes the code from both files
// bundle.js (part of; compiled with ModuleConcatenationPlugin)
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
// CONCATENATED MODULE: ./comments.js
function render(data, target) {
console.log('Rendered!');
}
// CONCATENATED MODULE: ./index.js
render();
})
¿Ves la diferencia? En el paquete simple, el módulo 0 requería render
del módulo 1. Con
concatenación de módulos, require
simplemente se reemplaza por la función requerida, y el módulo 1 se
o quitarse. El paquete tiene menos módulos y menos sobrecarga.
Para activar este comportamiento, en Webpack 4, habilita la opción optimization.concatenateModules
:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
concatenateModules: true
}
};
En webpack 3, usa ModuleConcatenationPlugin
:
// webpack.config.js (for webpack 3)
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.optimize.ModuleConcatenationPlugin()
]
};
Lecturas adicionales
- Los documentos de Webpack para las ModuleConcatenationPlugin
- “Breve introducción al alcance elevando”
- Descripción detallada de lo que este complemento debe
Usa externals
si tienes código webpack y código que no lo es.
Es posible que tengas un proyecto grande en el que una parte del código se compila con webpack y otra parte no. Me gusta un sitio de hosting de videos, en el que el widget del reproductor puede compilarse con webpack, y la página que lo rodea podría no ser:
Si ambos fragmentos de código tienen dependencias en común, puedes compartirlos para evitar la descarga de su código.
varias veces. Para ello, utiliza el externals
del paquete web
opción: reemplaza los módulos con variables o
a otras importaciones externas.
Si las dependencias están disponibles en window
Si tu código que no es webpack se basa en dependencias que están disponibles como variables en window
, alias
de dependencias a nombres de variables:
// webpack.config.js
module.exports = {
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
}
};
Con esta configuración, webpack no agrupará los paquetes react
y react-dom
. En cambio, se mostrarán
reemplazado por algo como esto:
// bundle.js (part of)
(function(module, exports) {
// A module that exports `window.React`. Without `externals`,
// this module would include the whole React bundle
module.exports = React;
}),
(function(module, exports) {
// A module that exports `window.ReactDOM`. Without `externals`,
// this module would include the whole ReactDOM bundle
module.exports = ReactDOM;
})
Si las dependencias se cargan como paquetes de AMD
Si el código que no es webpack no expone las dependencias en window
, el proceso se vuelve más complejo.
Sin embargo, puedes evitar cargar el mismo código dos veces si el código que no es webpack los consume.
las dependencias como paquetes de AMD.
Para ello, compila el código del paquete web como un paquete AMD y módulos de alias en las URLs de la biblioteca:
// webpack.config.js
module.exports = {
output: {
libraryTarget: 'amd'
},
externals: {
'react': {
amd: '/libraries/react.min.js'
},
'react-dom': {
amd: '/libraries/react-dom.min.js'
}
}
};
Webpack unirá el paquete en define()
y hará que dependa de estas URLs:
// bundle.js (beginning)
define(["/libraries/react.min.js", "/libraries/react-dom.min.js"], function () { … });
Si el código que no es webpack usa las mismas URLs para cargar sus dependencias, se cargarán estos archivos. solo una vez, las solicitudes adicionales usarán la caché del cargador.
Lecturas adicionales
- Documentación de Webpack en
externals
En resumen
- Habilita el modo de producción si usas webpack 4
- Minimiza el código con las opciones de minificador y cargador a nivel del paquete
- Para quitar el código exclusivo de desarrollo, reemplaza
NODE_ENV
porproduction
. - Cómo usar módulos de ES para habilitar la eliminación de código no utilizado
- Comprime las imágenes
- Aplica optimizaciones específicas de dependencias
- Habilitar la concatenación de módulos
- Usa
externals
si es conveniente para ti