Riduci dimensioni front-end

Come utilizzare webpack per ridurre al minimo le dimensioni dell'app

Una delle prime cose da fare quando ottimizzi un'applicazione è diminuirne le dimensioni possibile. Ecco come fare con webpack.

Utilizzare la modalità di produzione (solo Webpack 4)

Webpack 4 ha introdotto il nuovo flag mode. Potresti impostare questo flag a 'development' o 'production' per suggerire il webpack che stai creando l'applicazione per un ambiente specifico:

// webpack.config.js
module.exports = {
  mode: 'production',
};

Assicurati di attivare la modalità production quando crei la tua app per la produzione. In questo modo Webpack applicherà ottimizzazioni come la minimizzazione e la rimozione del codice solo per lo sviluppo nelle biblioteche e altro ancora.

Per approfondire

Abilita minimizzazione

La minimizzazione si verifica quando comprimi il codice rimuovendo spazi aggiuntivi, abbreviando i nomi delle variabili così via. Esempio:

// 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 supporta due modi per minimizzare il codice: minificazione a livello di bundle e opzioni specifiche del caricatore. Devono essere utilizzati contemporaneamente.

Minimizzazione a livello di bundle

La minimizzazione a livello di bundle comprime l'intero bundle dopo la compilazione. Ecco come funziona:

  1. Scrivi il codice in questo modo:

    // comments.js
    import './comments.css';
    export function render(data, target) {
      console.log('Rendered!');
    }
    
  2. Webpack lo compila approssimativamente in base ai seguenti elementi:

    // 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 minificatore lo comprime approssimativamente in uno dei seguenti modi:

    // 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)
    

In webpack 4, la minimizzazione a livello di bundle viene abilitata automaticamente, sia in produzione e senza. Utilizza il minificatore UglifyJS. dietro le quinte. Se dovessi avere la necessità di disabilitare la minimizzazione, utilizza la modalità di sviluppo oppure passa false all'opzione optimization.minimize.

In webpack 3, devi utilizzare il plug-in UglifyJS strato Add. Il plug-in viene fornito in bundle con il pacchetto webpack, per abilitarlo, aggiungilo a plugins della configurazione:

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

module.exports = {
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
  ],
};

Opzioni specifiche del caricatore

Il secondo modo per minimizzare il codice è rappresentato dalle opzioni specifiche del caricatore (cosa è è). Con le opzioni del caricatore puoi comprimere gli elementi che il minificatore non riesce a minimizzare. Ad esempio, quando importi un file CSS con css-loader, il file viene compilato in una stringa:

/* 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}",""]);

Il minificatore non può comprimere questo codice perché si tratta di una stringa. Per minimizzare i contenuti del file, configura il caricatore per farlo:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          { loader: 'css-loader', options: { minimize: true } },
        ],
      },
    ],
  },
};

Per approfondire

Specifica NODE_ENV=production

Un altro modo per ridurre le dimensioni del front-end è impostare il valore NODE_ENV variabile di ambiente nel codice al valore production.

Le librerie leggono la variabile NODE_ENV per rilevare in quale modalità devono funzionare, nella di sviluppo o produzione. Alcune librerie si comportano in modo diverso in base a questa variabile. Per Ad esempio, se il criterio NODE_ENV non è impostato su production, Vue.js esegue controlli e stampa aggiuntivi avvisi:

// vue/dist/vue.runtime.esm.js
// …
if (process.env.NODE_ENV !== 'production') {
  warn('props must be strings when using array syntax.');
}
// …

La reazione funziona in modo simile: carica una build di sviluppo che include gli avvisi:

// 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.'
);
// …

Tali controlli e avvisi di solito non sono necessari in produzione, ma rimangono nel codice e aumenta le dimensioni della raccolta. In Webpack 4, rimuovili aggiungendo l'opzione optimization.nodeEnv: 'production':

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    nodeEnv: 'production',
    minimize: true,
  },
};

In webpack 3, utilizza invece DefinePlugin:

// 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()
  ]
};

L'opzione optimization.nodeEnv e DefinePlugin funzionano allo stesso modo. sostituiscono tutte le occorrenze di process.env.NODE_ENV con il valore specificato. Con della configurazione precedente:

  1. Webpack sostituirà tutte le occorrenze di process.env.NODE_ENV con "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. Poi il minificatore rimuoverà tutti questi Rami if: perché "production" !== 'production' è sempre falso, e il plug-in comprende che il codice all'interno di questi rami non verrà mai eseguito:

    // 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 };
    }
    

Per approfondire

Utilizzare i moduli ES

Un altro modo per ridurre le dimensioni del front-end è utilizzare ES moduli.

Quando utilizzi i moduli ES, webpack diventa in grado di fare "albero di albero". Per scuotere gli alberi attraversa l'intero albero delle dipendenze, controlla le dipendenze utilizzate e rimuove quelle non utilizzate. Quindi, se utilizzi la sintassi del modulo ES, webpack può eliminare il codice inutilizzato:

  1. Scrivi un file con più esportazioni, ma l'app ne utilizza solo una:

    // comments.js
    export const render = () => { return 'Rendered!'; };
    export const commentRestEndpoint = '/rest/comments';
    
    // index.js
    import { render } from './comments.js';
    render();
    
  2. Webpack riconosce che commentRestEndpoint non viene utilizzato e non genera un punto di esportazione separato nel bundle:

    // 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. Il minificatore rimuove la variabile inutilizzata:

    // bundle.js (part that corresponds to comments.js)
    (function(n,e){"use strict";var r=function(){return"Rendered!"};e.b=r})
    

Questo funziona anche con le librerie se sono scritte con moduli ES.

Tuttavia, non è necessario utilizzare con precisione il minificatore integrato di Webpack (UglifyJsPlugin). Qualsiasi minificatore che supporti la rimozione di codice guasto (ad es. plug-in Babel Minify) o il plug-in Google Closure Compiler). è l'ideale.

Per approfondire

Ottimizza immagini

Le immagini rappresentano più di una metà delle dimensioni della pagina. Mentre non sono critici come JavaScript (ad es., non bloccano il rendering), consumano comunque gran parte e la larghezza di banda. Utilizza url-loader, svg-url-loader e image-webpack-loader per ottimizzarle in e webpack.

url-loader inserisce piccoli file statici nell' dell'app. Senza configurazione, prende un file passato, lo inserisce accanto al bundle compilato e restituisce un URL di quel file. Tuttavia, se specifichiamo l'opzione limit, questa codificherà i file di dimensioni inferiori a questo limite come URL di dati Base64 e restituisce questo URL. Questo in linea l'immagine nel codice JavaScript e salva una richiesta 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 funziona esattamente come url-loader. tranne che codifica i file con l'URL di codifica anziché Base64 uno. Questo è utile per le immagini SVG. Poiché i file SVG sono semplicemente testo, questa codifica viene più efficace in termini di dimensioni.

module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/,
        loader: "svg-url-loader",
        options: {
          limit: 10 * 1024,
          noquotes: true
        }
      }
    ]
  }
};

image-webpack-loader comprime le immagini e lo attraverso. Supporta le immagini JPG, PNG, GIF e SVG, quindi la utilizzeremo per tutti questi tipi.

Questo caricatore non incorpora immagini nell'app, quindi deve funzionare in combinazione con url-loader e svg-url-loader. Per evitare di copiarlo e incollarlo in entrambe le regole (una per le immagini JPG/PNG/GIF e un'altra una per quelle SVG), includeremo questo caricatore come regola separata 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'
      }
    ]
  }
};

Le impostazioni predefinite del caricatore sono già pronte, ma se vuoi configurarlo di più, consulta le opzioni dei plug-in. A scegli le opzioni da specificare, dai un'occhiata all'eccellente guida sull'immagine di Addy Osmani ottimizzazione.

Per approfondire

Ottimizza le dipendenze

Più della metà della dimensione media di JavaScript deriva dalle dipendenze e una parte di questa dimensione potrebbe essere semplicemente inutile.

Ad esempio, Lodash (a partire dalla versione 4.17.4) aggiunge 72 KB di codice minimizzato al bundle. Ma se utilizzi solo ad esempio 20 dei suoi metodi, circa 65 KB di codice minimizzato non fa proprio nulla.

Un altro esempio è Moment.js. La sua versione 2.19.1 prende 223 KB di codice minimizzato, che è enorme – la dimensione media di JavaScript in una pagina era di 452 kB a ottobre 2017. Tuttavia, 170 kB di quelle dimensioni è la localizzazione file. Se non usi Moment.js con più lingue, questi file sovraccaricano il bundle senza che non ha uno scopo specifico.

Tutte queste dipendenze possono essere facilmente ottimizzate. Abbiamo raccolto approcci all'ottimizzazione un repository GitHub. Dai un'occhiata!

Attiva la concatenazione dei moduli per i moduli ES (noto anche come sollevamento degli ambiti)

Quando crei un bundle, webpack aggrega ogni modulo in una funzione:

// 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!');
  }
})

In passato, questa operazione era necessaria per isolare i moduli CommonJS/AMD l'uno dall'altro. Tuttavia, questo ha aggiunto un overhead di dimensioni e prestazioni per ogni modulo.

Webpack 2 ha introdotto il supporto per i moduli ES che, a differenza dei moduli CommonJS e AMD, possono essere in bundle senza racchiudere ciascuno in una funzione. E Webpack 3 ha reso possibile questo raggruppamento – con concatenazione dei moduli. Ecco a cosa serve la concatenazione dei moduli:

// 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();
})

Vedi la differenza? Nel bundle semplice, il modulo 0 richiedeva render del modulo 1. Con concatenazione dei moduli, require viene semplicemente sostituito con la funzione richiesta e il modulo 1 è rimosso. Il bundle ha meno moduli e meno overhead per i moduli.

Per attivare questo comportamento, in webpack 4, abilita l'opzione optimization.concatenateModules:

// webpack.config.js (for webpack 4)
module.exports = {
  optimization: {
    concatenateModules: true
  }
};

In webpack 3, utilizza ModuleConcatenationPlugin:

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

module.exports = {
  plugins: [
    new webpack.optimize.ModuleConcatenationPlugin()
  ]
};

Per approfondire

Utilizza externals se hai sia codice webpack che non webpack

Potresti avere un progetto di grandi dimensioni in cui parte del codice viene compilato con webpack, ma non lo è. Mi piace un sito di hosting video, in cui il widget del player potrebbe essere creato con webpack e la pagina circostante potrebbero non essere:

Uno screenshot di un sito di hosting video
(Un sito di hosting video del tutto casuale)

Se entrambe le parti del codice hanno dipendenze comuni, puoi condividerle per evitare di scaricare il codice più volte. Per farlo, usa le externals del webpack - sostituisce i moduli con variabili da altre importazioni esterne.

Se le dipendenze sono disponibili in window

Se il codice non webpack si basa su dipendenze disponibili come variabili in window, utilizza un alias dai nomi delle dipendenze a quelli delle variabili:

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

Con questa configurazione, webpack non raggruppa react e react-dom pacchetti. Sarà invece sostituito con qualcosa del genere:

// 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;
})

Se le dipendenze vengono caricate come pacchetti AMD

Se il codice non webpack non espone dipendenze in window, il problema è più complesso. Tuttavia, puoi comunque evitare di caricare lo stesso codice due volte se il codice non webpack consuma questi come pacchetti AMD.

Per fare ciò, compila il codice webpack come bundle AMD e moduli alias negli URL della libreria:

// webpack.config.js
module.exports = {
  output: {
    libraryTarget: 'amd'
  },
  externals: {
    'react': {
      amd: '/libraries/react.min.js'
    },
    'react-dom': {
      amd: '/libraries/react-dom.min.js'
    }
  }
};

Webpack aggrega il bundle in define() e lo farà dipendere da questi URL:

// bundle.js (beginning)
define(["/libraries/react.min.js", "/libraries/react-dom.min.js"], function () { … });

Se il codice non webpack utilizza gli stessi URL per caricare le sue dipendenze, questi file verranno caricati solo una volta: le richieste aggiuntive utilizzeranno la cache del caricatore.

Per approfondire

Riepilogo

  • Attiva la modalità di produzione se utilizzi webpack 4
  • Riduci al minimo il codice con le opzioni di minifier e loader a livello di bundle
  • Rimuovi il codice solo per lo sviluppo sostituendo NODE_ENV con production
  • Utilizzare i moduli ES per consentire l'eliminazione degli alberi
  • Comprimi le immagini
  • Applica ottimizzazioni specifiche delle dipendenze
  • Abilita concatenazione dei moduli
  • Usa externals se questo è adatto alle tue esigenze