Les loaders se mettent dans un objet nommé "module" de notre configuration. Étant donné que les loaders ne s'appliquent pas à tous les fichiers, il faut préciser des règles qui indiquent à Webpack : tu utiliseras ce loader pour tels fichiers, celui-là pour tels autres fichiers...
Nous allons tester les loaders en en utilisant un très connu : Babel. Babel est un transpileur. Il permet de modifier du code ES6 et ESNEXT en code ES5 qui est mieux compris par les navigateurs.
Super !
Notre premier loader : babel
D'abord, il nous faut installer les dépendances de Babel.
{"language":"shell","content":"npm install --save-dev babel-loader @babel/core","filename":""}
Ensuite, on va écrire notre loader dans l'objet nommé "module". Chaque loader aura la forme suivante :
{"language":"text/javascript","content":"module: {\n \trules: [\n \t\t{\n \t\ttest: , // Indique les fichiers sur lesquels utiliser le loader\n \t\texclude: , // Optionnel. Indique les fichiers à exclure de notre test\n \t\tloader: // Nom du loader\n \t\t}\n \t]\n \t}","filename":""}
Dans test, on va donc indiquer les fichiers sur lesquels appliquer notre loader. Pour ça, nous utiliserons les expressions régulières. Tout porte sur l'extension du fichier. Ainsi, la regex portera également dessus. Voici une liste non exhaustive de règles :
- Pour les fichiers js : /\.js$/
- Pour les fichiers css : /\.css$/
- Pour les fichiers vue : /\.vue$/
- Pour les fichiers sass : /\.scss$/
Ensuite, on a le paramètre exclude. En effet, parmi les fichiers ciblés par notre règle dans "test", on peut vouloir en exclure certains. Par exemple, il serait bon d'enlever tous les fichiers compris dans /node_modules/ si on cible les fichiers .js...
Et enfin, on précise le nom du loader. Pour Babel, on aura donc :
{"language":"text/javascript","content":"const path = require('path')\nmodule.exports = {\n\tmode: 'development',\n \tentry: {\n \t\tapp:'./src/app.js', \n \t\tproducts: './src/products.js'\n \t},\n \toutput: {\n \t\tfilename:'[name].bundle.js',\n \tpath: path.resolve(__dirname, 'public')\n \t},\n \tmodule: {\n \trules: [\n \t\t{\n \t\ttest: /\\.js$/,\n \t\texclude: /node_modules/,\n \t\tloader:\"babel-loader\"\n \t\t}\n \t]\n \t}\n}","filename":"webpack.config.js"}
Donc là si on lance webpack, notre code ES6 sera transformé en ES5 ?
Je te laisse essayer. Bilan ?
Hé bien, il ne se passe rien de spécial...
En effet. Babel est un peu compliqué. Il faut, en plus de l'installer et de le renseigner dans Webpack, installer un preset. C'est à dire les règles qui transformations qui seront appliquées sur ES6 pour aller vers ES5. Du coup, on est reparti pour un tour :
{"language":"shell","content":"npm install @babel/preset-env --save-dev","filename":""}
Et après ça, comme souvent dans les loaders, il faut renseigner un fichier de configuration propre. Dans notre cas, il s'appelle babel.config.json. On met ce fichier à la racine de notre dossier.
{"language":"application/json","content":"{\n \"presets\": [\"@babel/preset-env\"]\n}","filename":"babel.config.json"}
Et maintenant, si on essaie avec le code suivant :
{"language":"text/javascript","content":"let a = \"bonjour\"\nconsole.log(a)","filename":"app.js"}
En sortie on a :
{"language":"text/javascript","content":"/******/ (() => { // webpackBootstrap\n/*!********************!*\\\n !*** ./src/app.js ***!\n \\********************/\neval(\"var a = \\\"bonjour\\\";\\nconsole.log(a);\\n\\n//# sourceURL=webpack://tuto-webpack/./src/app.js?\");\n/******/ })()\n;","filename":"app.js"}
Notre let a = ... s'est bien transformé en var a = ...
Mais c'est quoi tout ce qu'il y a autour ? Et eval() ?
Tout ça c'est parce qu'on est en mode development. Si tu mets le mode production, tout ça disparait ;). On verra dans le dernier chapitre comment agir sur la façon dont Webpack génère les fichiers bundlés pour avoir, par exemple, des fichiers en mode développement plus lisibles.
Toi qui lis ce tutoriel, j'interromps ta lecture quelques instants. Il y a 1h j'ai eu un rdv en visioconférence avec un étudiant pour l'aider sur la configuration de Webpack. En effet, il avait des soucis lors de la compilation car il utilisation des "class properties" et le mot clé "statique". Autrement dit, des fonctionnalités d'ESNext. Il a été difficile de trouver la solution pour débogguer tout ça, je souhaite donc t'en faire part. Si ça t'intéresse, voici donc la solution et quelques explications. Si tu n'utilises pas les class properties, tu peux passer à la suite, juste après le deuxième bloc bleu !
Avec notre configuration de Webpack et de Babel, les codes de ce type ne passeront pas :
{"language":"text/javascript","content":"export default class Tutorial {\n\tstatic amount = 0;\n\n\tconstructor(name) {\n\t\tthis.name = name\n\t\tTutorial.amount++\n\t}\n\n\t...\n}","filename":""}
Nous allons devoir rajouter un plugin dans un premier temps (voir partie d'après pour comprendre les plugins).
{"language":"shell","content":"npm install --save-dev @babel/plugin-proposal-class-properties","filename":""}
Ensuite, il faut que Babel génère des polyfill : des bouts de code que nous souhaitons mais que le navigateur ne comprend pas et qu'on doit totalement réécrire pour que leur comportement soit compris nativement.
Pour ça, il nous faut installer également core-js 3 :
{"language":"shell","content":"npm install --save core-js@3","filename":""}
Enfin, nous devons modifier notre fichier de configuration babel pour ajouter ce plugin et indiquer la génération des polyfills :
{"language":"application/json","content":"{\n\t\"plugins\": [\n\t\t[\"@babel/plugin-proposal-class-properties\", { \"loose\" : true }]\n\t],\n\t\"presets\": [\n\t\t[\"@babel/preset-env\", {\n\t\t\t\"useBuiltIns\": \"usage\",\n\t\t\t\"corejs\":3\n\t\t}]\n\t]\n}","filename":"babel.config.json"}
Super, maintenant il faut importer les polyfill générés directement dans notre point d'entrée et une seule fois dans notre application. Dans app.js on va donc rajouter au tout début :
{"language":"text/javascript","content":"import \"core-js/stable\";\nimport \"regenerator-runtime/runtime\";","filename":"app.js"}
Et voilà, on peut maintenant npm run build et notre code fonctionnera sans soucis !
Voilà, cet aparté s'arrête ici. Tu peux poursuivre la lecture du cours normalement !
Il faut savoir qu'il y a également une autre syntaxe pour utiliser un loader qui nous évite d'écrire un fichier de configuration supplémentaire. Dans le cas de babel, tu pourras écrire webpack.config.js comme suit sans écrire de fichier babel.config.json :
{"language":"text/javascript","content":"const path = require('path')\nmodule.exports = {\n\tmode: 'development',\n \tentry: {\n \t\tapp:'./src/app.js', \n \t},\n \toutput: {\n \t\tfilename:'[name].bundle.js',\n \tpath: path.resolve(__dirname, 'public')\n \t},\n \tmodule: {\n \trules: [\n \t\t{\n \t\ttest: /\\.js$/,\n \t\texclude: /node_modules/,\n \t\tuse: {\n \t\t\tloader: \"babel-loader\",\n \t\t\toptions: {\n \t\t\tpresets: ['@babel/preset-env']\n \t\t\t}\n \t\t}\n \t\t}\n \t]\n \t}\n}","filename":"webpack.config.js"}
En effet, ça marche toujours ! Mais au fait, comme tu sais tout ça ? Je veux dire, comment je trouve les loaders et si'l faut un fichier de configuration ?
En général il faut regarder la documentation de ce que tu cherches. Par exemple taper "SASS webpack" dans ton moteur de recherches. Et tu as la solution qui s'offre à toi !
D'ailleurs, tu pourrais me montrer pour SASS ?
Un loader pour SASS permettra à Webpack de lire les fichiers de type .scss ou .sass qui sont importés dans des fichiers JS.
Hé bien, en recherchant sur un moteur de recherches, j'obtiens le lien suivant https://webpack.js.org/loaders/sass-loader/.
D'après le guide, je dois exécuter la commande suivante :
{"language":"shell","content":"npm install sass-loader sass webpack --save-dev","filename":""}
Il est également recommandé d'installer 2 autres loaders : css-loader, qui permet de comprendre dans un fichier css les url() ou import() et style-loader, qui injecte le CSS dans le DOM.
Du coup, c'est parti :
{"language":"shell","content":"npm install --save-dev style-loader css-loader","filename":""}
Et je modifie webpack config selon la doc :
{"language":"text/javascript","content":"const path = require('path')\nmodule.exports = {\n\tmode: 'development',\n \tentry: {\n \t\tapp:'./src/app.js', \n \t},\n \toutput: {\n \t\tfilename:'[name].bundle.js',\n \tpath: path.resolve(__dirname, 'public')\n \t},\n \tmodule: {\n \trules: [\n \t\t{\n \t\ttest: /\\.js$/,\n \t\texclude: /node_modules/,\n \t\tuse: {\n \t\t\tloader: \"babel-loader\",\n \t\t\toptions: {\n \t\t\tpresets: ['@babel/preset-env']\n \t\t\t}\n \t\t}\n \t\t},\n \t\t{\n \t\ttest: /\\.s[ac]ss$/i,\n \t\tuse: [\n \t\t\t// Creates `style` nodes from JS strings\n \t\t\t \"style-loader\",\n \t\t\t// Translates CSS into CommonJS\n \t\t\t\"css-loader\",\n \t\t\t// Compiles Sass to CSS\n \t\t\t\"sass-loader\",\n \t\t],\n \t\t},\n \t]\n \t}\n}","filename":"webpack.config.js"}
Tout va bien ?
Je te suis oui !
Alors maintenant, place aux tests.
Créons un fichier style.scss dans "src". Pour ma part j'ai fait ça :
{"language":"text/css","content":"$main-blue: blue;\n\nbody {\n background-color: $main-blue;\n div {\n \tcolor: $main-blue;\n \tbackground:gray;\n }\n}","filename":"style.scss"}
Ensuite, il faut importer le style dans un fichier JS :
{"language":"text/javascript","content":"import \"./style.scss\"\n\nlet a = \"bonjour\"\nconsole.log(a)","filename":"app.js"}
Et mon HTML :
{"language":"text/html","content":"<!DOCTYPE html>\n<html lang=\"fr\">\n<head>\n\t<meta charset=\"utf-8\">\n\t<title>Exemple loader</title>\n</head>\n<body>\n\t<div>Du texte</div>\n\t<script src=\"app.bundle.js\"></script>\n</body>\n</html>","filename":"index.html"}
Le résultat : une page avec un fond bleu et une div avec un fond gris et un texte bleu !
Super ça marche !
Allez, un petit bonus, pour utiliser Vue.JS correctement avec Webpack (même s'il est recommandé d'utiliser vue-cli, l'outil de build dédié de Vue.JS)
{"language":"shell","content":"npm install -D vue-style-loader vue-loader@next vue-template-compiler css-loader && npm install vue@next","filename":""}
{"language":"text/javascript","content":"const path = require('path')\nconst webpack = require('webpack')\n\nconst { VueLoaderPlugin } = require(\"vue-loader\");\n\nmodule.exports = {\n\tmode: 'development',\n \tentry: {\n \t\tapp:'./src/app.js', \n \t},\n \toutput: {\n \t\tfilename:'[name].bundle.js',\n \tpath: path.resolve(__dirname, 'public')\n \t},\n \tmodule: {\n \trules: [\n \t\t{\n \t\ttest: /\\.js$/,\n \t\texclude: /node_modules/,\n \t\tuse: {\n \t\t\tloader: \"babel-loader\",\n \t\t\toptions: {\n \t\t\tpresets: ['@babel/preset-env']\n \t\t\t}\n \t\t}\n \t\t},\n \t\t{\n \t\ttest: /\\.vue$/,\n \t\tloader: 'vue-loader'\n \t\t},\n \t\t{\n \t\ttest: /\\.s[ac]ss$/i,\n \t\tuse: [\n \t\t\t// Creates `style` nodes from JS strings\n \t\t\t \"style-loader\",\n \t\t\t// Translates CSS into CommonJS\n \t\t\t\"css-loader\",\n \t\t\t// Compiles Sass to CSS\n \t\t\t\"sass-loader\",\n \t\t],\n \t\t},\n \t\t{\n \t\ttest: /\\.css$/i,\n \t\tuse: [\n \t\t\t\"vue-style-loader\",\n \t\t\t\"css-loader\",\n \t\t],\n \t\t},\n\n \t]\n \t},\n \tplugins: [\n \t// make sure to include the plugin for the magic\n \tnew VueLoaderPlugin(),\n \tnew webpack.DefinePlugin({\n \t\t\t'__VUE_OPTIONS_API__': JSON.stringify(true),\n \t\t\t'__VUE_PROD_DEVTOOLS__': JSON.stringify(false)\n\t\t})\n \t]\n}","filename":"webpack.config.js"}
Merci beaucoup ! Mais c'est quoi ça, plugins ? Je vois que tu en as utilisés.
C'est le titre de la prochaine partie ! Alors nous allons voir ça juste après. J'ai terminé cette partie