Loupe

Javascript : à la découverte de Babel

Dans le cadre de la rédaction d’un article parlant de CSS-in-JS, je me suis rapidement retrouvé à vouloir utiliser Babel ainsi que Typescript dans un projet de test. Et j’ai eu rapidement des soucis… Ça m’a donc donné envie d’écrire un peu sur Babel qui est un incontournable d’une stack front aujourd’hui.

Je vous propose donc une série d’articles parlant de Babel sous différents cas :

  • Présentation de Babel - gérer la compatibilité JS avec IE
  • La mise en place de Babel et typescript dans un même projet
  • Qu’est-ce que les macros Babel et pourquoi Babel est promu à un bel avenir dans l’écosystème javascript

Nous parlerons donc dans cet article de ce magnifique outil qu’est Babel, pourquoi il est aujourd’hui l’un des outils les plus importants de l’écosystème Javascript et pourquoi désormais la compatibilité Javascript entre navigateurs n’est plus un souci.

Babel : et si la compatibilité javascript avec Internet Explorer n’était plus un problème ?

Babel est un Transpileur, un type de compilateur qui sert compiler du code source d’un certain langage de programmation en du code source d’un autre langage de programmation. En ce qui concerne Babel, il permet de convertir du Javascript en du…Javascript ! Pour être plus précis, il permet de convertir du code javascript récent (syntaxe ES2015+) en du code javascript capable d’être interprété par des vieux navigateurs.

Mise en place :

Initialisons premièrement notre projet :

> mkdir babel-ie
> cd ./babel-ie
> npm init -y #-y permet de passer les questions
Ensuite installons Webpack ainsi que sa CLI :
> npm install --save-dev webpack webpack-cli

Nous allons maintenant mettre dans un fichier Webpack.config.js une configuration basique de Webpack :

//webpack.config.js
    
//Webpack requires this to work with directories
const path = require('path');

module.exports = {
    //path to entry paint
    entry: './src/index.js',

    //path and filename of the final output
    output: {
	    path: path.resolve(__dirname, 'dist'),
	    filename: 'bundle.js'
    },

    //default mode is production
    mode: 'development'
}

Avec ceci, et afin de vérifier que Webpack fonctionne, vous pouvez écrire du code js dans un fichier situé dans src/index.js (pensez à créer le fichier src), ensuite vous pouvez lancer Webpack en rajoutant ces lignes dans le fichier package.json :

"scripts": {
	"start":"webpack --mode development",
}

Et faire :

> npm run start

Une fois cela fait, lorsque nous arrivons à avoir un bundle, nous pouvons désormais installer Babel ! :

> npm install --save-dev babel-loader @babel/core

Ensuite rajoutons dans le fichier de configuration de Webpack ces lignes :

//webpack.config.js

module: {
  rules: [
    { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }
  ]
}

Avec ces quelques lignes, nous venons de dire à Webpack que lorsqu’il compilera des fichiers terminant par l’extension .js, il demandera à Babel de faire son traitement sur les fichiers avant de bundler le tout.

Cependant, nous n’avons encore donné aucune consigne à Babel ! Rajoutons donc de la configuration à Babel pour lui dire quoi faire. Dans cet article, nous allons évidemment mettre en place son preset le plus connu : preset-env. Ce preset sert à cibler spécifiquement les navigateurs avec lesquels on veut que notre code js soit compatible.

Installons donc le preset :

> npm install @babel/preset-env --save-dev

Ensuite, pour ajouter de la config à Babel, nous devons créer un fichier nommé .babelrc dans lequel nous collerons ces lignes :

{
	"presets":  [
		["@babel/preset-env",
			{
				"targets":  {
					"browsers":  [">0.25%",  "ie >= 11"]
				}
			}
		]
	]
}

Dans ce code, nous avons dit à Babel d’utiliser le preset preset-env pour transformer la syntaxe de notre code javascript en une syntaxe compatible avec les navigateurs que nous ciblons, à savoir : Tous les navigateurs qui ont au moins 25% de part de marché et IE 11 ! (Je vous renvoie ici pour ce qui est de la syntaxe de définition des targets)
A noter que si l’on utilise le preset preset-env sans spécifier de target, babel convertira tout le code ES2015+ par default.

Maintenant, mettons du code JS ES2015+ dans notre fichier index.js :

console.log("Test de Babel");

["toto",  "tutu",  'titi'].map((item)  =>  {
	let numberArray = [1,  2,  3,  4];
	
	numberArray.find((number)  => number ===  4);
	console.log(item,  ...numberArray);
});

Une fois cela fait, vous pouvez lancer le bundling en faisant :

> npm run start

Vous devriez vous retrouver avec un fichier bundle.js dans le dossier /dist, contenant votre code js bundlé.

Maintenant, pour le tester dans un navigateur il va nous falloir un template html qui va loader ce bundle. Créons donc un fichier index.html à la racine de votre projet qui va loader notre bundle js.

<!doctype html>

<html lang="en">
<head>
	<meta charset="utf-8">

	<title>Babel test</title>
</head>

<body>
  <script src="dist/bundle.js"></script>
</body>
</html>

Si vous chargez ce fichier dans chrome, firefox ou edge et que vous affichez la console js, vous devriez voir correctement notre code javascript exécuté ! :)

Maintenant, si on test dans IE… patatra, c’est tout cassé ! On se retrouve avec une belle erreur :

Object doesn't support property or method 'find'

Et oui, la méthode find des tableaux n’est pas un prototype implémenté sur IE ! Nous pouvons donc voir que notre code ES2015+ a bien été transformé mais aucun polyfill n’a été mis en place, ce qui reste cependant normal puisque ce n’est pas le job de @babel/preset-env

Babel et Core-js : le duo de choc 

Si on regarde la documentation de Babel, on peut remarquer que Babel met à disposition un paquet nommé @babel/polyfill et qui fait exactement ce que l’on veut, à savoir polyfill nos fonctions js non-supportés. Sauf que ce paquet nous est présenté comme déprécié… pourquoi ?

Si on continue de lire la documentation, on peut lire que @babel/polyfill est simplement un alias et l’importer revient en effet à importer l’intégralité de core-js ainsi que regenerator (paquet qui permet d’exécuter des fonctions generator client-side). Ainsi, importer @babel/polyfill revient à faire :

import 'core-js/stable';
import 'regenerator-runtime/runtime';

Avant de rentrer dans le détail, installons ces paquets :

> npm i --save core-js regenerator-runtime

Pour présenter core-js rapidement, il s’agit d’une librairie qui inclut les polyfills de la syntaxe proposée par les différentes versions d’ECMAScript. L’avantage est que c’est un projet très maintenu qui intègre rapidement les dernières fonctionnalités standardisées par ECMAScript.
Sauf qu’il se trouve que cette librairie importe énormément de modules qui ne sont pas tous forcement nécessaires à notre projet. Et c’est là que Babel intervient ! En effet, Babel et core-js s’intègrent parfaitement ensemble grâce au travail des développeurs des deux projets. (On peut d’ailleurs remarquer que Babel est le premier contributeur au projet core-js :p).

@babel/preset-env va nous permettre d’intégrer spécifiquement les paquets core-js en fonction des targets spécifiées dans notre configuration de Babel. Ainsi, on importe seulement les polyfills dont on a besoin, ce qui nous permet d’optimiser la taille de notre bundle ! \o/

Ainsi, pour reprendre l’exemple de la documentation, si l’on cible uniquement chrome 72, le code suivant :

import "core-js/stable";
import "regenerator-runtime/runtime";

est transformé en :

import "core-js/modules/es.array.unscopables.flat";
import "core-js/modules/es.array.unscopables.flat-map";
import "core-js/modules/es.object.from-entries";
import "core-js/modules/web.immediate";

Pour en revenir au début de ce chapitre et pourquoi @babel/polyfill est déprécié au profit d’un import direct de core-js. La raison à cela est que @babel/polyfill importe l’intégralité de core-js sans possibilité d’optimisation et ça, ce n’est vraiment pas terrible car core-js dans son intégralité alourdit réellement le bundle final.

Cependant, pour faire fonctionner ces imports ciblés des modules core-js, une petite modification est nécessaire du côté de notre .babelrc. Notre fichier ressemblera donc à ça :

{
	"presets":  [
		["@babel/preset-env",
			{
			"targets":  {
				"browsers":  [">0.25%",  "ie >= 11"]
			},
			"corejs":  3,
			"useBuiltIns":  "entry"
			}
		]
	]
}

Ici, on spécifie simplement par le paramètre corejs que l’on souhaite utiliser la version 3 de core-js. Le paramètre useBuiltIns à la valeur entry permet de dire à Babel de remplacer les imports directs de core-js par les imports seulement des modules core-js dont on a besoin, soit exactement le comportement que l’on veut.

Une fois cela fait, vous pouvez vous amuser à changer la liste des targets dans votre babel.rc et voir les répercussions que ça a sur la taille de votre bundle final. On peut remarquer que ça varie du tout au tout, notamment si l’on spécifie IE, qui a besoin d’énormément de polyfills.

Maintenant que vous avez régénéré un bundle avec les polyfills intégrés, il ne vous reste plus qu’à re-tester sous IE et voir votre code javascript être exécuté sans erreurs ! Elle est Babel la vie ? :P

Happy Coding !

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus