In questo codelab, migliora le prestazioni di questa semplice applicazione che consente agli utenti di valutare i gatti a caso. Scopri come ottimizzare il bundle JavaScript riducendo al minimo la quantità di codice transpilato.
Nell'app di esempio, puoi selezionare una parola o un'emoji per indicare quanto ti piace ogni gatto. Quando fai clic su un pulsante, l'app mostra il valore del pulsante sotto l'immagine corrente del gatto.
Misura
È sempre consigliabile iniziare ispezionando un sito web prima di aggiungere eventuali ottimizzazioni:
- Per visualizzare l'anteprima del sito, premi Visualizza app, quindi Schermo intero .
- Premi "Control+Maiusc+J" (o "Comando+Opzione+J" su Mac) per aprire DevTools.
- Fai clic sulla scheda Rete.
- Seleziona la casella di controllo Disabilita cache.
- Ricarica l'app.
Per questa applicazione vengono utilizzati più di 80 kB. È il momento di scoprire se parti del bundle non vengono utilizzate:
Premi
Control+Shift+P
(oCommand+Shift+P
su Mac) per aprire il menu Comando.Inserisci
Show Coverage
e premiEnter
per visualizzare la scheda Copertura.Nella scheda Copertura, fai clic su Ricarica per ricaricare l'applicazione durante l'acquisizione della copertura.
Dai un'occhiata a quanto codice è stato utilizzato e a quanto è stato caricato per il bundle principale:
Oltre la metà del bundle (44 kB) non è nemmeno utilizzato. Questo perché gran parte del codice al suo 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. Le versioni più recenti della specifica vengono rilasciate ogni anno e includono nuove funzionalità che hanno superato il processo di proposta. Ogni browser principale si trova sempre in una fase diversa di supporto di queste funzionalità.
L'applicazione utilizza le seguenti funzionalità ES2015:
Viene utilizzata anche la seguente funzionalità ES2017:
Analizza il codice sorgente in src/index.js
per vedere come viene utilizzato.
Tutte queste funzionalità sono supportate nell'ultima versione di Chrome, ma per quanto riguarda gli altri browser che non le supportano? Babel, inclusa nell'applicazione, è la libreria più diffusa utilizzata per compilare codice contenente una sintassi più recente nel codice, comprensibile ai browser e agli ambienti meno recenti. Puoi farlo in due modi:
- I Polyfill sono inclusi per emulare le funzioni più recenti di ES2015 e versioni successive 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 meno recente. Poiché si tratta di modifiche relative alla sintassi (ad esempio le funzioni a freccia), non possono essere emulate con polyfill.
Guarda 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 di base di Babel. In questo modo, tutte le configurazioni di Babel vengono definite in un.babelrc
alla radice del progetto.babel-loader
include Babel nel processo di compilazione del webpack.
Ora guarda webpack.config.js
per vedere come babel-loader
viene incluso come
regola:
module: { rules: [ //... { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" } ] },
@babel/polyfill
fornisce tutti i polyfill necessari per le funzionalità ECMAScript più recenti, 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-env
identifica le trasformazioni e i polyfill 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 l'elenco dei browser, il che significa che puoi trovare un elenco completo delle query compatibili che possono essere utilizzate in questo campo nella
documentazione relativa all'elenco dei browser.
Il valore "last 2 versions"
esegue il trapilo del codice nell'applicazione per le
ultime due versioni di ciascun browser.
Debug
Per una panoramica completa di tutti i target 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 dello stato di Glitch nella parte inferiore dell'editor.
Browser scelti come target
Babel registra nella console una serie di dettagli relativi al processo di compilazione, inclusi tutti gli ambienti di destinazione per cui è stato compilato il codice.
Osserva come i browser non più disponibili, come Internet Explorer, sono inclusi in questo elenco. Questo è un problema perché ai browser non supportati non verranno aggiunte funzionalità più recenti e Babel continua a trascrivere la sintassi specifica per questi browser. Ciò aumenta inutilmente le dimensioni del bundle se gli utenti non utilizzano questo browser per accedere al tuo sito.
Babel registra anche un elenco di plug-in per la trasformazione utilizzati:
È un elenco piuttosto lungo. Questi sono tutti i plug-in che Babel deve utilizzare per trasformare qualsiasi sintassi ES2015+ in una sintassi meno recente per tutti i browser scelti come target.
Tuttavia, Babel non mostra polyfill specifici utilizzati:
Il motivo è che l'intera risorsa @babel/polyfill
viene importata direttamente.
Carica singolarmente i polyfill
Per impostazione predefinita, Babel include tutti i polyfill necessari per un ambiente ES2015+ completo quando
@babel/polyfill
viene importato in un file. Per importare 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é sono ancora inclusi i polyfill necessari per i browser di destinazione per ogni funzionalità più recente. 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 richiesti necessari per l'applicazione.
Le dimensioni del bundle dell'applicazione sono ridotte in modo significativo.
Restringere l'elenco dei browser supportati
Il numero di target dei browser inclusi è ancora molto elevato e non molti utenti 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.
Dal momento che l'applicazione è così piccola, in realtà non c'è molta differenza in queste modifiche. Tuttavia, l'utilizzo di una percentuale di quota di mercato dei browser (come ">0.25%"
) ed l'esclusione di browser specifici che con certezza i tuoi utenti non stanno utilizzando è l'approccio consigliato. Per saperne di più, dai un'occhiata all'articolo "Ultime 2 versioni" considerate dannose
di James Kyle.
Utilizza <script type="module">
C'è ancora qualche margine di miglioramento. Anche se sono stati rimossi alcuni polyfill inutilizzati, molti di questi vengono spediti e non sono necessari per alcuni browser. Grazie ai moduli, è possibile scrivere una sintassi più recente e inviarla direttamente ai browser senza utilizzare 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 consentono di eseguire l'importazione e l'esportazione 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 in ambienti che supportano i moduli JavaScript (anziché avere bisogno di Babel). Ciò significa che la configurazione di Babel può essere modificata per inviare al browser due versioni diverse della tua applicazione:
- Una versione che funziona nei browser più recenti che supportano i moduli e che include un modulo in gran parte non transcompilato, ma con dimensioni del file inferiori
- Una versione che include uno script trasversale più grande che funziona in qualsiasi browser legacy
Utilizzo di ES Modules con Babel
Per avere impostazioni @babel/preset-env
separate per le due versioni dell'applicazione, rimuovi il file .babelrc
. Le impostazioni Babel possono essere aggiunte alla configurazione 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 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 oggetti entry
, cssRule
e corePlugins
all'inizio del
file webpack.config.js
. Questi elementi sono 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"})
];
Analogamente, crea un oggetto config per lo script del modulo sotto dove viene definito 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 viene utilizzata un'estensione del file .mjs
per il nome file di output. Qui il valore esmodules
è impostato su true, il che significa che il codice
output in questo modulo è uno script più piccolo e meno compilato che
non viene sottoposto ad 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
];
Ora viene creato sia un modulo più piccolo per i browser che lo supportano, sia uno script con 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 di script con type="module"
. Ciò significa che puoi includere un modulo e un modulo di riserva compilato. Idealmente, le due versioni dell'applicazione dovrebbero essere in index.html
come segue:
<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, quelli di 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 qui è aggiungere gli attributi module
e nomodule
rispettivamente al modulo e allo script precedente, 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 dei plug-in aggiungono un attributo type="module"
per tutti gli elementi script .mjs
e un attributo nomodule
per tutti i moduli script .js
.
Moduli di pubblicazione nel documento HTML
L'ultima cosa da fare è generare gli elementi di script legacy e moderni nel file HTML. Sfortunatamente, il plug-in che crea il file HTML finale, HTMLWebpackPlugin
, al momento non supporta l'output degli script modulo e nomodule. Anche se esistono soluzioni alternative e plug-in separati creati per risolvere questo problema, ad esempio BabelMultiTargetPlugin e HTMLWebpackMultiBuildPlugin, ai fini di questo tutorial viene utilizzato un approccio più semplice per aggiungere manualmente l'elemento di 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 dimensioni del bundle molto minori perché in gran parte non transcompilate. L'altro elemento di script viene completamente ignorato dal browser.
Se carichi l'applicazione su un browser meno recente, viene recuperato solo lo script più grande con traslato, 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 scelti come target. Sai anche come i moduli JavaScript possono migliorare ulteriormente le prestazioni inviando due diverse versioni di un'applicazione con transcodifica. Con una buona conoscenza di come entrambe queste tecniche
consentono di ridurre notevolmente le dimensioni del bundle, procedi con l'ottimizzazione.