Le mode watch
Si l'on n'utilise pas webpack dev server mais qu'on veut de Webpack qu'il recompile à chaque changement, on peut le faire avec le mode Watch.
Pour cela, il nous suffit d'utiliser :
{"language":"shell","content":"npx webpack --watch","filename":""}
On peut également utiliser npm run build simplement mais en précisant dans notre webpack.config.js "watch" à true, comme suit :
{"language":"text/javascript","content":"module.exports = {\n mode: 'development',\n watch:true,\n entry: {\n app:'./src/app.js', \n },\n\t...\n}","filename":"webpack.config.js"}
Utiliser plusieurs scripts
Comme on a vu au début, il est plus simple de centraliser les scripts dans le package.json. Pas besoin de se rappeler de npx je ne sais quoi, simplement de npm run "nom du script".
Voici plusieurs scripts qui peuvent être utiles :
{"language":"application/json","content":"\"scripts\": {\n \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n \"build\": \"webpack --config webpack.config.js\",\n \"watch\": \"webpack --config webpack.config.js --watch\",\n \"serve\": \"webpack serve --open\"\n },","filename":"package.json"}
![Arthur, l'apprenti développeur](/Img/Shared/arthur.png)
Autant que tu veux !
Utiliser plusieurs fichiers de configuration
Une bonne pratique consiste à utiliser plusieurs fichiers de configuration. On peut ainsi appliquer différents paramètres pour la compilation selon si l'on est en développement ou en production. En effet, en développement on va vouloir utiliser ESLint, mais quand on compilera en production, on n'a plus besoin de ce plugin. On utilisera aussi webpack-dev-server mais jamais en production. On peut aussi préciser comment Webpack gèrera nos fichiers, et en mode développement il peut être bon lui indiquer de nous laisser les fichiers générés "lisibles". Tu l'auras peut être remarqué, durant tout le cours en mode développement, les fichiers générés étaient difficilement lisibles car ils utilisaient "eval". On va changer ça.
Voici par exemple deux bons fichiers de configuration. Le premier en mode développement, le second pour la production.
{"language":"text/javascript","content":"const path = require('path')\nconst webpack = require('webpack')\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nconst { CleanWebpackPlugin } = require('clean-webpack-plugin');\nconst ESLintPlugin = require('eslint-webpack-plugin');\n\nmodule.exports = {\n mode: 'production',\n entry: {\n app:'./src/app.js', \n },\n devtool: 'inline-source-map',\n output: {\n filename:'[name].bundle.js',\n path: path.resolve(__dirname, 'public')\n },\n module: {\n rules: [\n {\n test: /\\.js$/,\n exclude: /node_modules/,\n use: {\n loader: \"babel-loader\",\n options: {\n presets: ['@babel/preset-env']\n }\n }\n }\n ]\n },\n plugins: [\n new ESLintPlugin(),\n new CleanWebpackPlugin(),\n new HtmlWebpackPlugin({\n name:\"Gestion de la sortie\"\n })\n ],\n devServer: {\n contentBase: path.join(__dirname, './public'),\n hot:true,\n }\n}","filename":"webpack.dev.js"}
{"language":"text/javascript","content":"const path = require('path')\nconst webpack = require('webpack')\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nconst { CleanWebpackPlugin } = require('clean-webpack-plugin');\nconst ESLintPlugin = require('eslint-webpack-plugin');\n\nmodule.exports = {\n mode: 'production',\n entry: {\n app:'./src/app.js', \n },\n output: {\n filename:'[name].[contenthash].js',\n path: path.resolve(__dirname, 'public')\n },\n module: {\n rules: [\n {\n test: /\\.js$/,\n exclude: /node_modules/,\n use: {\n loader: \"babel-loader\",\n options: {\n presets: ['@babel/preset-env']\n }\n }\n }\n ]\n },\n plugins: [\n new CleanWebpackPlugin(),\n new HtmlWebpackPlugin({\n name:\"Gestion des sorties\"\n })\n ]\n}","filename":"webpack.prod.js"}
![Arthur, l'apprenti développeur](/Img/Shared/arthur.png)
Excellente question. Pensons en terme de navigateur et de cache navigateur dans un premier temps. Tu n'es peut être pas sans savoir que les navigateurs mettent en cache les assets (images, scripts, feuilles de style). Ainsi, quand on visite un site une deuxième fois, il se charge beaucoup plus rapidement. Le problème pour un développeur est donc quand il fait des mises à jour sur le site. Le contenu des assets aura changé, mais c'est le cache qui sera utilisé ! Une solution pour palier à ce problème est de changer le nom de l'asset. Le navigateur ira alors le chercher lui plutôt que celui qu'il a en cache.
Du coup, ce qu'il faut, c'est changer le nom du fichier dès qu'une modification est effectuée. Et pour ça, on peut utiliser un algorithme de hashage.
![Arthur, l'apprenti développeur](/Img/Shared/arthur.png)
Les algorithmes de hashage font partie des algorithmes de chiffrement. Le hashage est couramment utilisé pour vérifier l'intégrité des données. Il est aussi utilisé pour stocker des mots de passe dans une base de données. Lorsqu'on utilise une fonction de hashage sur une entité (une chaine de caractère, un document, un fichier entier...), on obtient une chaine de caractères (le hash) de taille fixe qui dépend du contenu de l'entité : dès qu'un seul bit change, dès qu'un seul caractère change, le hash est totalement différent.
![Arthur, l'apprenti développeur](/Img/Shared/arthur.png)
Tu as tout compris ;)
![Arthur, l'apprenti développeur](/Img/Shared/arthur.png)
En effet. Heureusement, dans la partie précédente on a vu l'utilisation de HtmlWebpackPlugin. Ce que je ne t'ai pas dit, c'est comment agit ce plugin. En réalité, il modifie pour nous le index.html et en régénère un à notre place à chaque fois. Donc avec webpack-dev-server, c'est super si on veut que notre résultat dans le navigateur change. Mais c'est aussi super quand on utilise des hash. Car le plugin va lui même insérer les scripts avec le bon nom ;)
![Arthur, l'apprenti développeur](/Img/Shared/arthur.png)
Cette ligne permet de répondre à la la phrase que j'ai dite juste avant : "On peut aussi préciser comment Webpack gèrera nos fichiers, et en mode développement il peut être bon lui indiquer de nous laisser les fichiers générés "lisibles". Tu l'auras peut être remarqué, durant tout le cours en mode développement, les fichiers générés étaient difficilement lisibles car ils utilisaient "eval". On va changer ça.". Avec inline-source-map, le code sera plus lisible en développement. Plus pratique pour le déboggage !
![Arthur, l'apprenti développeur](/Img/Shared/arthur.png)
Ce plugin sert à nettoyer notre dossier de sortie. Dans notre cas, "public". En effet, si on rajoute des points d'entrée, des librairies etc, qu'on change de nom à nos sorties, des fichiers .js vont être créés puis plus jamais utilisés. C'est encore plus vrai si on utilise un nom de sortie composé du hash du fichier . Car n'oublions pas que à chaque changement le hash va être modifié, donc un nouveau fichier sera créé avec le nouveau nom. Mais l'ancien ne sera pas supprimé ! CleanWebpackPlugin va le supprimer pour nous. N'oublie pas de l'installer si tu désires l'utiliser :
{"language":"shell","content":"npm install --save-dev clean-webpack-plugin","filename":""}
![Arthur, l'apprenti développeur](/Img/Shared/arthur.png)
Bon, du coup il ne reste plus qu'à créer deux nouveaux scripts dans notre package.json.
{"language":"application/json","content":"\"scripts\": {\n \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n \"build\": \"webpack --config webpack.dev.js\",\n \"watch\": \"webpack --config webpack.dev.js --watch\",\n \"serve\": \"webpack serve --config webpack.dev.js --open\",\n \"prod\": \"webpack --config webpack.prod.js\"\n },","filename":"package.json"}
Éviter la duplication dans les fichiers de configuration
Utiliser plusieurs fichiers de configuration c'est vraiment top. Par contre, c'est dommage que tant de lignes soient dupliquées entre les deux fichiers... On peut évidemment palier à ce problème. Avec webpack-merge.
{"language":"shell","content":"npm install --save-dev webpack-merge","filename":""}
Ensuite on crée un fichier de configuration commun qui reprend ce que nos deux fichiers partageaient.
{"language":"text/javascript","content":"const path = require('path')\nconst webpack = require('webpack')\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nconst { CleanWebpackPlugin } = require('clean-webpack-plugin');\nconst ESLintPlugin = require('eslint-webpack-plugin');\n\nmodule.exports = {\n entry: {\n app:'./src/app.js', \n },\n module: {\n rules: [\n {\n test: /\\.js$/,\n exclude: /node_modules/,\n use: {\n loader: \"babel-loader\",\n options: {\n presets: ['@babel/preset-env']\n }\n }\n }\n ]\n },\n plugins: [\n new CleanWebpackPlugin(),\n new HtmlWebpackPlugin({\n name:\"Gestion des sorties\"\n })\n ]\n}","filename":"webpack.common.js"}
Et on réécrit nos deux fichiers :
{"language":"text/javascript","content":"const path = require('path')\nconst { merge } = require('webpack-merge')\nconst common = require('./webpack.common.js');\n\nmodule.exports = merge(common, {\n mode: 'production',\n output: {\n filename:'[name].[contenthash].js',\n path: path.resolve(__dirname, 'public')\n }\n})","filename":"webpack.prod.js"}
{"language":"text/javascript","content":"const path = require('path')\nconst ESLintPlugin = require('eslint-webpack-plugin');\nconst { merge } = require('webpack-merge')\nconst common = require('./webpack.common.js');\n\nmodule.exports = merge(common, {\n mode: 'development',\n devtool: 'inline-source-map',\n output: {\n filename:'[name].bundle.js',\n path: path.resolve(__dirname, 'public')\n },\n plugins: [\n new ESLintPlugin(),\n ],\n devServer: {\n contentBase: path.join(__dirname, './public'),\n hot:true,\n }\n})","filename":"webpack.dev.js"}
![Arthur, l'apprenti développeur](/Img/Shared/arthur.png)
Tu l'as dit !
Minifier le CSS
Un dernier point que j'aimerais te montrer est la minification du CSS. En production, Webpack minifie les fichiers JS. Mais pas les fichiers CSS ! C'est pourtant hyper important. Nous avons encore besoin de deux plugins :
{"language":"shell","content":"npm install --save-dev mini-css-extract-plugin","filename":""}
{"language":"shell","content":"npm install css-minimizer-webpack-plugin --save-dev","filename":""}
Et du coup notre fichier de production :
{"language":"text/javascript","content":"const { merge } = require('webpack-merge')\nconst common = require('./webpack.common.js');\nconst path = require('path')\nconst CssMinimizerPlugin = require('css-minimizer-webpack-plugin');\nconst MiniCssExtractPlugin = require('mini-css-extract-plugin');\n\nmodule.exports = {\n mode: 'production',\n output: {\n filename:'[name].[contenthash].js',\n path: path.resolve(__dirname, 'public')\n },\n module: {\n rules: [\n {\n test: /\\.css$/,\n use: [MiniCssExtractPlugin.loader, 'css-loader'],\n }\n ]\n },\n plugins: [\n new MiniCssExtractPlugin({\n filename: '[name].css',\n chunkFilename: '[id].css',\n }),\n ],\n optimization: {\n minimizer: [\n new CssMinimizerPlugin(),\n ],\n },\n}","filename":"webpack.prod.js"}
Et voilà, on a fait le tour de ce dont je voulais te montrer pour Webpack. Tu as encore plein de choses à paramétrer si tu le désires, et le mieux pour cela sera directement de voir la documentation associée.
![Arthur, l'apprenti développeur](/Img/Shared/arthur.png)