Reduce el tamaño del frontend

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:

  1. Debes escribir el código de la siguiente manera:

    // comments.js
    import './comments.css';
    export function render(data, target) {
      console.log('Rendered!');
    }
    
  2. 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!');
    }
    
  3. 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

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:

  1. 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.');
    }
    
  2. 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

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:

  1. 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();
    
  2. 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 */
    })
    
  3. 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

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

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

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:

Captura de pantalla de un sitio de hosting de videos
(Un sitio de hosting de videos completamente aleatorio)

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

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 por production.
  • 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