In questo codelab, migliora le prestazioni di questa semplice applicazione che consente agli utenti di valutare gatti casuali. Scopri come ottimizzare il bundle JavaScript riducendo al minimo la quantità di codice sottoposto a transpilazione.

Nell'app di esempio, puoi selezionare una parola o un'emoji per esprimere quanto ti piace ogni gatto. Quando fai clic su un pulsante, l'app mostra il valore del pulsante sotto l'immagine del gatto corrente.
Misura
È sempre buona norma iniziare ispezionando un sito web prima di aggiungere eventuali ottimizzazioni:
- Per visualizzare l'anteprima del sito, premi Visualizza app, quindi premi Schermo intero
.
- Premi "Ctrl+Maiusc+J" (o "Cmd+Opzione+J" su Mac) per aprire DevTools.
- Fai clic sulla scheda Rete.
- Seleziona la casella di controllo Disattiva cache.
- Ricarica l'app.

Per questa applicazione vengono utilizzati più di 80 kB. È il momento di scoprire se alcune parti del bundle non vengono utilizzate:
Premi
Control+Shift+P(oCommand+Shift+Psu Mac) per aprire il menu Comando.
Inserisci
Show Coveragee premiEnterper visualizzare la scheda Copertura.Nella scheda Copertura, fai clic su Ricarica per ricaricare l'applicazione durante l'acquisizione della copertura.

Dai un'occhiata alla quantità di codice utilizzata rispetto a quella caricata per il bundle principale:
Oltre la metà del bundle (44 kB) non viene nemmeno utilizzata. Questo perché gran parte del codice all'interno è costituito da polyfill per garantire che l'applicazione funzioni nei browser meno recenti.
Utilizza @babel/preset-env
La sintassi del linguaggio JavaScript è conforme a uno standard noto come ECMAScript o ECMA-262. Ogni anno vengono rilasciate nuove versioni della specifica, che includono nuove funzionalità che hanno superato il processo di proposta. Ogni browser principale si trova sempre in una fase diversa del supporto di queste funzionalità.
Nell'applicazione vengono utilizzate le seguenti funzionalità di ES2015:
Viene utilizzata anche la seguente funzionalità ES2017:
Non esitare a esaminare il codice sorgente in src/index.js per vedere come viene utilizzato tutto questo.
Tutte queste funzionalità sono supportate nell'ultima versione di Chrome, ma che dire degli altri browser che non le supportano? Babel, che è incluso nell'applicazione, è la libreria più utilizzata per compilare il codice che contiene una sintassi più recente in un codice comprensibile per i browser e gli ambienti più datati. In due modi:
- I polyfill sono inclusi per emulare le funzioni ES2015+ più recenti in modo che le relative API possano essere utilizzate anche se non sono supportate dal browser. Ecco un esempio di polyfill del metodo
Array.includes. - I plug-in vengono utilizzati per trasformare il codice ES2015 (o versioni successive) nella sintassi ES5 precedente. Poiché si tratta di modifiche relative alla sintassi (come le funzioni freccia), non possono essere emulate con i polyfill.
Controlla package.json per vedere quali librerie Babel sono incluse:
"dependencies": {
"@babel/polyfill": "^7.0.0"
},
"devDependencies": {
//...
"babel-loader": "^8.0.2",
"@babel/core": "^7.1.0",
"@babel/preset-env": "^7.1.0",
//...
}
@babel/coreè il compilatore Babel principale. In questo modo, tutte le configurazioni di Babel vengono definite in un file.babelrcnella directory principale del progetto.babel-loaderinclude Babel nel processo di build di webpack.
Ora guarda webpack.config.js per vedere come babel-loader è incluso come regola:
module: {
rules: [
//...
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}
]
},
@babel/polyfillfornisce tutti i polyfill necessari per le nuove funzionalità di ECMAScript, in modo che possano funzionare in ambienti che non le supportano. È già importato nella parte superiore disrc/index.js.
import "./style.css";
import "@babel/polyfill";
@babel/preset-envidentifica quali trasformazioni e polyfill sono necessari per i browser o gli ambienti scelti come target.
Dai un'occhiata al file di configurazione di Babel, .babelrc, per vedere come è incluso:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions"
}
]
]
}
Questa è una configurazione di Babel e webpack. Scopri come includere Babel nella tua applicazione se utilizzi un bundler di moduli diverso da webpack.
L'attributo targets in .babelrc identifica i browser scelti come target. @babel/preset-env
si integra con browserslist, il che significa che puoi trovare un elenco completo di query compatibili che possono essere utilizzate in questo campo nella documentazione di browserslist.
Il valore "last 2 versions" traspila il codice nell'applicazione per le ultime due versioni di ogni browser.
Debug
Per avere un quadro completo di tutte le destinazioni Babel del browser, nonché di tutte le trasformazioni e i polyfill inclusi, aggiungi un campo debug a .babelrc:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
}
]
]
}
- Fai clic su Strumenti.
- Fai clic su Log.
Ricarica l'applicazione e dai un'occhiata ai log di stato di Glitch nella parte inferiore dell'editor.
Browser scelti come target
Babel registra nella console una serie di dettagli sul processo di compilazione, tra cui tutti gli ambienti di destinazione per cui è stato compilato il codice.

Tieni presente che i browser non più disponibili, come Internet Explorer, sono inclusi in questo elenco. Questo è un problema perché ai browser non supportati non verranno aggiunte nuove funzionalità e Babel continuerà a transpilare la sintassi specifica per questi browser. In questo modo, le dimensioni del bundle aumentano inutilmente se gli utenti non utilizzano questo browser per accedere al tuo sito.
Babel registra anche un elenco dei plug-in di trasformazione utilizzati:

È un elenco piuttosto lungo. Questi sono tutti i plug-in che Babel deve utilizzare per trasformare qualsiasi sintassi ES2015+ in una sintassi precedente per tutti i browser di destinazione.
Tuttavia, Babel non mostra i polyfill specifici utilizzati:
![]()
Questo perché l'intero @babel/polyfill viene importato direttamente.
Carica i polyfill singolarmente
Per impostazione predefinita, Babel include tutti i polyfill necessari per un ambiente ES2015+ completo quando @babel/polyfill viene importato in un file. Per importare i polyfill specifici necessari per i browser di destinazione, aggiungi un useBuiltIns: 'entry' alla configurazione.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
"useBuiltIns": "entry"
}
]
]
}
Ricarica l'applicazione. Ora puoi vedere tutti i polyfill specifici inclusi:

Anche se ora sono inclusi solo i polyfill necessari per "last 2 versions", l'elenco è ancora molto lungo. Questo perché i polyfill necessari per i browser di destinazione per ogni funzionalità più recente sono ancora inclusi. Modifica il valore dell'attributo in usage per includere solo quelli necessari per le funzionalità utilizzate nel codice.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true,
"useBuiltIns": "entry"
"useBuiltIns": "usage"
}
]
]
}
In questo modo, i polyfill vengono inclusi automaticamente dove necessario.
Ciò significa che puoi rimuovere l'importazione @babel/polyfill in src/index.js.
import "./style.css";
import "@babel/polyfill";
Ora sono inclusi solo i polyfill necessari per l'applicazione.

Le dimensioni del bundle dell'applicazione sono notevolmente ridotte.

Restringimento dell'elenco dei browser supportati
Il numero di browser di destinazione inclusi è ancora piuttosto elevato e non sono molti gli utenti che utilizzano browser non più disponibili come Internet Explorer. Aggiorna le configurazioni come segue:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"targets": [">0.25%", "not ie 11"],
"debug": true,
"useBuiltIns": "usage",
}
]
]
}
Dai un'occhiata ai dettagli del bundle recuperato.

Poiché l'applicazione è molto piccola, non c'è molta differenza con queste modifiche. Tuttavia, l'approccio consigliato è utilizzare una percentuale di quota di mercato del browser (ad esempio ">0.25%") ed escludere i browser specifici che sai con certezza che i tuoi utenti non utilizzano. Per saperne di più, consulta l'articolo di James Kyle "Last 2 versions" considered harmful.
Utilizza <script type="module">
C'è ancora margine di miglioramento. Sebbene siano stati rimossi alcuni polyfill inutilizzati, ne vengono forniti molti che non sono necessari per alcuni browser. Utilizzando i moduli, è possibile scrivere una sintassi più recente e inviarla direttamente ai browser senza l'uso di polyfill non necessari.
I moduli JavaScript sono una funzionalità relativamente nuova supportata in tutti i principali browser.
I moduli possono essere creati utilizzando un attributo type="module" per definire gli script che importano ed esportano da altri moduli. Ad esempio:
// math.mjs
export const add = (x, y) => x + y;
<!-- index.html -->
<script type="module">
import { add } from './math.mjs';
add(5, 2); // 7
</script>
Molte funzionalità ECMAScript più recenti sono già supportate negli ambienti che supportano i moduli JavaScript (anziché richiedere Babel). Ciò significa che la configurazione di Babel può essere modificata per inviare due versioni diverse della tua applicazione al browser:
- Una versione che funziona nei browser più recenti che supportano i moduli e che include un modulo che non è stato sottoposto a transpilazione, ma ha dimensioni del file inferiori
- Una versione che include uno script transpilato più grande che funzionerebbe in qualsiasi browser legacy
Utilizzo dei moduli ES con Babel
Per avere impostazioni @babel/preset-env separate per le due versioni dell'applicazione, rimuovi il file .babelrc. Le impostazioni di Babel possono essere aggiunte alla configurazione di webpack specificando due diversi formati di compilazione per ogni versione dell'applicazione.
Inizia aggiungendo una configurazione per lo script precedente a webpack.config.js:
const legacyConfig = {
entry,
output: {
path: path.resolve(__dirname, "public"),
filename: "[name].bundle.js"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env", {
useBuiltIns: "usage",
targets: {
esmodules: false
}
}]
]
}
},
cssRule
]
},
plugins
}
Tieni presente che, anziché utilizzare il valore targets per "@babel/preset-env",
viene utilizzato esmodules con un valore false. Ciò significa che Babel include tutte le trasformazioni e i polyfill necessari per scegliere come target ogni browser che non supporta ancora i moduli ES.
Aggiungi gli oggetti entry, cssRule e corePlugins all'inizio del file webpack.config.js. Questi vengono condivisi tra il modulo e gli script legacy pubblicati nel browser.
const entry = {
main: "./src"
};
const cssRule = {
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
};
const plugins = [
new ExtractTextPlugin({filename: "[name].css", allChunks: true}),
new HtmlWebpackPlugin({template: "./src/index.html"})
];
Ora, allo stesso modo, crea un oggetto di configurazione per lo script del modulo sotto la definizione di legacyConfig:
const moduleConfig = {
entry,
output: {
path: path.resolve(__dirname, "public"),
filename: "[name].mjs"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env", {
useBuiltIns: "usage",
targets: {
esmodules: true
}
}]
]
}
},
cssRule
]
},
plugins
}
La differenza principale è che per il nome file di output viene utilizzata un'estensione del file .mjs. Il valore esmodules è impostato su true, il che significa che il codice
di output in questo modulo è uno script più piccolo e meno compilato che non
subisce alcuna trasformazione in questo esempio, poiché tutte le funzionalità utilizzate
sono già supportate nei browser che supportano i moduli.
Alla fine del file, esporta entrambe le configurazioni in un unico array.
module.exports = [
legacyConfig, moduleConfig
];
In questo modo, viene creato sia un modulo più piccolo per i browser che lo supportano sia uno script transpilato più grande per i browser meno recenti.
I browser che supportano i moduli ignorano gli script con un attributo nomodule.
Al contrario, i browser che non supportano i moduli ignorano gli elementi script con
type="module". Ciò significa che puoi includere un modulo e un fallback compilato. Idealmente, le due versioni dell'applicazione dovrebbero trovarsi in index.html
in questo modo:
<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js"></script>
I browser che supportano i moduli recuperano ed eseguono main.mjs e ignorano main.bundle.js.. I browser che non supportano i moduli fanno il contrario.
È importante notare che, a differenza degli script normali, gli script dei moduli vengono sempre differiti per impostazione predefinita.
Se vuoi che anche lo script nomodule equivalente venga differito ed eseguito solo dopo l'analisi, devi aggiungere l'attributo defer:
<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js" defer></script>
L'ultima cosa da fare è aggiungere gli attributi module e nomodule rispettivamente al modulo e allo script legacy. Importa ScriptExtHtmlWebpackPlugin nella parte superiore di webpack.config.js:
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");
Ora aggiorna l'array plugins nelle configurazioni per includere questo plug-in:
const plugins = [
new ExtractTextPlugin({filename: "[name].css", allChunks: true}),
new HtmlWebpackPlugin({template: "./src/index.html"}),
new ScriptExtHtmlWebpackPlugin({
module: /\.mjs$/,
custom: [
{
test: /\.js$/,
attribute: 'nomodule',
value: ''
},
]
})
];
Queste impostazioni del plug-in aggiungono un attributo type="module" per tutti gli elementi di script .mjs e un attributo nomodule per tutti i moduli di script .js.
Moduli di pubblicazione nel documento HTML
L'ultima cosa da fare è generare gli elementi di script legacy e moderni nel file HTML. Purtroppo, il plug-in che crea il file HTML finale, HTMLWebpackPlugin, al momento non supporta l'output degli script module e nomodule. Sebbene esistano soluzioni alternative e plug-in separati creati per risolvere questo problema, come BabelMultiTargetPlugin e HTMLWebpackMultiBuildPlugin, ai fini di questo tutorial viene utilizzato un approccio più semplice per aggiungere manualmente l'elemento script del modulo.
Aggiungi quanto segue a src/index.js alla fine del file:
...
</form>
<script type="module" src="main.mjs"></script>
</body>
</html>
Ora carica l'applicazione in un browser che supporta i moduli, ad esempio l'ultima versione di Chrome.

Viene recuperato solo il modulo, con una dimensione del bundle molto più piccola perché non è stato sottoposto a transpilazione. L'altro elemento script viene completamente ignorato dal browser.
Se carichi l'applicazione su un browser meno recente, viene recuperato solo lo script transpilato più grande con tutti i polyfill e le trasformazioni necessari. Ecco uno screenshot di tutte le richieste effettuate su una versione precedente di Chrome (versione 38).

Conclusione
Ora sai come utilizzare @babel/preset-env per fornire solo i polyfill necessari per i browser target. Inoltre, sai come i moduli JavaScript possono migliorare ulteriormente le prestazioni inviando due diverse versioni transpiled di un'applicazione. Ora che hai una buona conoscenza di come entrambe queste tecniche possono ridurre
significativamente le dimensioni del bundle, vai avanti e ottimizza.