Dans cet atelier de programmation, vous allez améliorer les performances d'une application simple qui permet aux utilisateurs de noter des chats de manière aléatoire. Découvrez comment optimiser le bundle JavaScript en réduisant la quantité de code transpilé.

Dans l'application exemple, vous pouvez sélectionner un mot ou un emoji pour indiquer à quel point vous aimez chaque chat. Lorsque vous cliquez sur un bouton, l'application affiche la valeur du bouton sous l'image du chat actuel.
Mesurer
Il est toujours judicieux de commencer par inspecter un site Web avant d'ajouter des optimisations :
- Pour prévisualiser le site, appuyez sur View App (Afficher l'application), puis sur Fullscreen (Plein écran)
.
- Appuyez sur Ctrl+Maj+J (ou Cmd+Option+J sur Mac) pour ouvrir les outils de développement.
- Cliquez sur l'onglet Réseau.
- Cochez la case Désactiver le cache.
- Actualisez l'application.

Plus de 80 Ko sont utilisés pour cette application. Il est temps de déterminer si certaines parties du bundle ne sont pas utilisées :
Appuyez sur
Control+Shift+P(ouCommand+Shift+Psur Mac) pour ouvrir le menu Commande.
Saisissez
Show Coverage, puis appuyez surEnterpour afficher l'onglet Couverture.Dans l'onglet Coverage (Couverture), cliquez sur Reload (Actualiser) pour actualiser l'application tout en capturant la couverture.

Voyez la quantité de code utilisée par rapport à la quantité chargée pour le bundle principal :
Plus de la moitié du bundle (44 Ko) n'est même pas utilisée. En effet, une grande partie du code qu'il contient est constituée de polyfills permettant de s'assurer que l'application fonctionne dans les anciens navigateurs.
Utiliser @babel/preset-env
La syntaxe du langage JavaScript est conforme à une norme appelée ECMAScript ou ECMA-262. De nouvelles versions de la spécification sont publiées chaque année et incluent de nouvelles fonctionnalités qui ont été validées par le processus de proposition. Chaque navigateur majeur se trouve toujours à un stade différent de la prise en charge de ces fonctionnalités.
Les fonctionnalités ES2015 suivantes sont utilisées dans l'application :
La fonctionnalité ES2017 suivante est également utilisée :
N'hésitez pas à examiner le code source dans src/index.js pour voir comment tout cela est utilisé.
Toutes ces fonctionnalités sont compatibles avec la dernière version de Chrome, mais qu'en est-il des autres navigateurs qui ne les prennent pas en charge ? Babel, qui est inclus dans l'application, est la bibliothèque la plus populaire utilisée pour compiler du code contenant une syntaxe plus récente en code que les navigateurs et environnements plus anciens peuvent comprendre. Pour ce faire, il utilise deux méthodes :
- Les polyfills sont inclus pour émuler les nouvelles fonctions ES2015+ afin que leurs API puissent être utilisées même si elles ne sont pas compatibles avec le navigateur. Voici un exemple de polyfill de la méthode
Array.includes. - Les plug-ins permettent de transformer le code ES2015 (ou version ultérieure) en une syntaxe ES5 plus ancienne. Comme il s'agit de modifications liées à la syntaxe (comme les fonctions fléchées), elles ne peuvent pas être émulées avec des polyfills.
Examinez package.json pour voir quelles bibliothèques Babel sont incluses :
"dependencies": {
"@babel/polyfill": "^7.0.0"
},
"devDependencies": {
//...
"babel-loader": "^8.0.2",
"@babel/core": "^7.1.0",
"@babel/preset-env": "^7.1.0",
//...
}
@babel/coreest le compilateur Babel principal. Toutes les configurations Babel sont ainsi définies dans un fichier.babelrcà la racine du projet.babel-loaderinclut Babel dans le processus de compilation webpack.
Examinez maintenant webpack.config.js pour voir comment babel-loader est inclus en tant que règle :
module: {
rules: [
//...
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}
]
},
@babel/polyfillfournit tous les polyfills nécessaires pour les nouvelles fonctionnalités ECMAScript afin qu'elles puissent fonctionner dans les environnements qui ne les prennent pas en charge. Il est déjà importé tout en haut desrc/index.js..
import "./style.css";
import "@babel/polyfill";
@babel/preset-envidentifie les transformations et les polyfills nécessaires pour les navigateurs ou les environnements choisis comme cibles.
Examinez le fichier de configuration Babel, .babelrc, pour voir comment il est inclus :
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions"
}
]
]
}
Il s'agit d'une configuration Babel et webpack. Découvrez comment inclure Babel dans votre application si vous utilisez un bundler de modules autre que webpack.
L'attribut targets dans .babelrc identifie les navigateurs ciblés. @babel/preset-env s'intègre à browserslist. Vous trouverez donc une liste complète des requêtes compatibles pouvant être utilisées dans ce champ dans la documentation de browserslist.
La valeur "last 2 versions" transpile le code de l'application pour les deux dernières versions de chaque navigateur.
Débogage
Pour obtenir un aperçu complet de toutes les cibles Babel du navigateur, ainsi que de toutes les transformations et polyfills inclus, ajoutez un champ debug à .babelrc:.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
}
]
]
}
- Cliquez sur Outils.
- Cliquez sur Journaux.
Actualisez l'application et examinez les journaux d'état de Glitch en bas de l'éditeur.
Navigateurs ciblés
Babel consigne un certain nombre de détails dans la console concernant le processus de compilation, y compris tous les environnements cibles pour lesquels le code a été compilé.

Notez que les navigateurs qui ne sont plus disponibles, tels qu'Internet Explorer, sont inclus dans cette liste. Cela pose problème, car les navigateurs non compatibles ne bénéficieront pas des nouvelles fonctionnalités ajoutées, et Babel continuera de transpiler une syntaxe spécifique pour eux. Cela augmente inutilement la taille de votre bundle si les utilisateurs n'utilisent pas ce navigateur pour accéder à votre site.
Babel consigne également la liste des plug-ins de transformation utilisés :

C'est une très longue liste ! Il s'agit de tous les plug-ins dont Babel a besoin pour transformer n'importe quelle syntaxe ES2015+ en une syntaxe plus ancienne pour tous les navigateurs ciblés.
Cependant, Babel n'affiche pas les polyfills spécifiques utilisés :
![]()
En effet, l'intégralité de @babel/polyfill est importée directement.
Charger les polyfills individuellement
Par défaut, Babel inclut tous les polyfills nécessaires à un environnement ES2015+ complet lorsque @babel/polyfill est importé dans un fichier. Pour importer des polyfills spécifiques nécessaires aux navigateurs cibles, ajoutez un useBuiltIns: 'entry' à la configuration.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
"useBuiltIns": "entry"
}
]
]
}
Actualisez l'application. Vous pouvez maintenant voir tous les polyfills spécifiques inclus :

Bien que seuls les polyfills nécessaires pour "last 2 versions" soient désormais inclus, la liste reste très longue. En effet, les polyfills nécessaires aux navigateurs cibles pour chaque nouvelle fonctionnalité sont toujours inclus. Remplacez la valeur de l'attribut par usage pour n'inclure que les éléments nécessaires aux fonctionnalités utilisées dans le code.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true,
"useBuiltIns": "entry"
"useBuiltIns": "usage"
}
]
]
}
Ainsi, les polyfills sont automatiquement inclus là où ils sont nécessaires.
Vous pouvez donc supprimer l'importation @babel/polyfill dans src/index.js..
import "./style.css";
import "@babel/polyfill";
Désormais, seuls les polyfills requis pour l'application sont inclus.

La taille du bundle d'application est considérablement réduite.

Réduire la liste des navigateurs compatibles
Le nombre de navigateurs cibles inclus est encore assez élevé, et peu d'utilisateurs utilisent des navigateurs abandonnés tels qu'Internet Explorer. Modifiez les configurations comme suit :
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"targets": [">0.25%", "not ie 11"],
"debug": true,
"useBuiltIns": "usage",
}
]
]
}
Examinez les détails du bundle récupéré.

Comme l'application est très petite, ces modifications ne font pas vraiment de différence. Toutefois, l'approche recommandée consiste à utiliser un pourcentage de part de marché des navigateurs (tel que ">0.25%") et à exclure les navigateurs spécifiques que vos utilisateurs n'utilisent pas. Pour en savoir plus, consultez l'article de James Kyle intitulé "Last 2 versions" considered harmful (Les deux dernières versions considérées comme nuisibles).
Utiliser <script type="module">
Il reste encore des points à améliorer. Bien qu'un certain nombre de polyfills inutilisés aient été supprimés, de nombreux polyfills sont fournis alors qu'ils ne sont pas nécessaires pour certains navigateurs. En utilisant des modules, une syntaxe plus récente peut être écrite et envoyée directement aux navigateurs sans utiliser de polyfills inutiles.
Les modules JavaScript sont une fonctionnalité relativement nouvelle, compatible avec tous les principaux navigateurs.
Vous pouvez créer des modules à l'aide d'un attribut type="module" pour définir des scripts qui importent et exportent depuis d'autres modules. Exemple :
// math.mjs
export const add = (x, y) => x + y;
<!-- index.html -->
<script type="module">
import { add } from './math.mjs';
add(5, 2); // 7
</script>
De nombreuses fonctionnalités ECMAScript plus récentes sont déjà prises en charge dans les environnements compatibles avec les modules JavaScript (au lieu d'avoir besoin de Babel). Cela signifie que la configuration Babel peut être modifiée pour envoyer deux versions différentes de votre application au navigateur :
- Une version qui fonctionnerait dans les navigateurs plus récents compatibles avec les modules et qui inclut un module qui n'est pas transcompilé, mais dont la taille de fichier est plus petite
- Une version qui inclut un script transpilé plus volumineux, qui fonctionnerait dans n'importe quel ancien navigateur
Utiliser des modules ES avec Babel
Pour avoir des paramètres @babel/preset-env distincts pour les deux versions de l'application, supprimez le fichier .babelrc. Les paramètres Babel peuvent être ajoutés à la configuration webpack en spécifiant deux formats de compilation différents pour chaque version de l'application.
Commencez par ajouter une configuration pour l'ancien script à 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
}
Notez qu'au lieu d'utiliser la valeur targets pour "@babel/preset-env", esmodules est utilisé avec la valeur false. Cela signifie que Babel inclut toutes les transformations et tous les polyfills nécessaires pour cibler tous les navigateurs qui ne sont pas encore compatibles avec les modules ES.
Ajoutez les objets entry, cssRule et corePlugins au début du fichier webpack.config.js. Ils sont tous partagés entre le module et les anciens scripts diffusés dans le navigateur.
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"})
];
De même, créez un objet de configuration pour le script du module ci-dessous où legacyConfig est défini :
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 principale différence ici est qu'une extension de fichier .mjs est utilisée pour le nom du fichier de sortie. La valeur esmodules est définie sur "true" ici, ce qui signifie que le code généré dans ce module est un script plus petit et moins compilé qui ne subit aucune transformation dans cet exemple, car toutes les fonctionnalités utilisées sont déjà compatibles avec les navigateurs qui acceptent les modules.
À la toute fin du fichier, exportez les deux configurations dans un seul tableau.
module.exports = [
legacyConfig, moduleConfig
];
Cela permet de créer à la fois un module plus petit pour les navigateurs compatibles et un script transpilé plus volumineux pour les anciens navigateurs.
Les navigateurs compatibles avec les modules ignorent les scripts dotés d'un attribut nomodule.
À l'inverse, les navigateurs qui ne sont pas compatibles avec les modules ignorent les éléments de script avec type="module". Cela signifie que vous pouvez inclure un module ainsi qu'un remplacement compilé. Idéalement, les deux versions de l'application doivent se trouver dans index.html, comme suit :
<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js"></script>
Les navigateurs compatibles avec les modules récupèrent et exécutent main.mjs, et ignorent main.bundle.js.. Les navigateurs qui ne sont pas compatibles avec les modules font l'inverse.
Il est important de noter que, contrairement aux scripts standards, les scripts de module sont toujours différés par défaut.
Si vous souhaitez que le script nomodule équivalent soit également différé et exécuté uniquement après l'analyse, vous devez ajouter l'attribut defer :
<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js" defer></script>
La dernière chose à faire ici est d'ajouter respectivement les attributs module et nomodule au module et à l'ancien script. Importez ScriptExtHtmlWebpackPlugin tout en haut de 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");
Mettez maintenant à jour le tableau plugins dans les configurations pour inclure ce 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: ''
},
]
})
];
Ces paramètres de plug-in ajoutent un attribut type="module" pour tous les éléments de script .mjs, ainsi qu'un attribut nomodule pour tous les modules de script .js.
Diffusion de modules dans le document HTML
La dernière chose à faire est d'ajouter les éléments de script anciens et modernes au fichier HTML. Malheureusement, le plug-in qui crée le fichier HTML final, HTMLWebpackPlugin, n'est pas compatible avec la sortie des scripts module et nomodule. Bien qu'il existe des solutions de contournement et des plug-ins distincts créés pour résoudre ce problème, tels que BabelMultiTargetPlugin et HTMLWebpackMultiBuildPlugin, une approche plus simple consistant à ajouter manuellement l'élément de script du module est utilisée dans ce tutoriel.
Ajoutez ce qui suit à src/index.js à la fin du fichier :
...
</form>
<script type="module" src="main.mjs"></script>
</body>
</html>
Chargez maintenant l'application dans un navigateur compatible avec les modules, tel que la dernière version de Chrome.

Seul le module est récupéré, avec une taille de bundle beaucoup plus petite, car il n'est pas transcompilé. L'autre élément de script est complètement ignoré par le navigateur.
Si vous chargez l'application sur un navigateur plus ancien, seul le script transpilé plus volumineux, contenant tous les polyfills et transformations nécessaires, est récupéré. Voici une capture d'écran de toutes les requêtes effectuées sur une ancienne version de Chrome (version 38).

Conclusion
Vous savez maintenant comment utiliser @babel/preset-env pour ne fournir que les polyfills nécessaires aux navigateurs ciblés. Vous savez également comment les modules JavaScript peuvent améliorer davantage les performances en fournissant deux versions transpilées différentes d'une application. Maintenant que vous avez compris comment ces deux techniques peuvent réduire considérablement la taille de votre bundle, lancez-vous et optimisez !