Aller au contenu

d

Webpack

Aux premiers jours, React était quelque peu célèbre pour être très difficile à configurer avec les outils requis pour le développement d'applications. Pour faciliter la situation, Create React App a été développé, ce qui a éliminé les problèmes liés à la configuration. Vite, qui est également utilisé dans le cours, a récemment remplacé Create React App dans les nouvelles applications.

Vite et Create React App utilisent des bundlers (empaqueteurs) pour faire le travail réel. Nous allons maintenant nous familiariser avec l'empaqueteur appelé Webpack utilisé par Create React App. Webpack a été de loin l'empaqueteur le plus populaire pendant des années. Récemment, cependant, il y a eu plusieurs empaqueteurs de nouvelle génération tels que esbuild utilisé par Vite, qui sont significativement plus rapides et plus faciles à utiliser que Webpack. Cependant, par exemple, esbuild manque encore de certaines fonctionnalités utiles (telles que le rechargement à chaud du code dans le navigateur), donc ensuite nous allons connaître l'ancien souverain des empaqueteurs, Webpack.

Empaquetage

Nous avons implémenté nos applications en divisant notre code en modules séparés qui ont été importés aux endroits qui en ont besoin. Même si les modules ES6 sont définis dans la norme ECMAScript, les navigateurs plus anciens ne savent pas comment gérer le code qui est divisé en modules.

Pour cette raison, le code qui est divisé en modules doit être empaqueté pour les navigateurs, ce qui signifie que tous les fichiers de code source sont transformés en un seul fichier qui contient tout le code de l'application. Lorsque nous avons déployé notre frontend React en production dans la partie 3, nous avons effectué l'empaquetage de notre application avec la commande npm run build. Sous le capot, le script npm empaquette la source, et cela produit la collection suivante de fichiers dans le répertoire dist:


├── assets
│   ├── index-d526a0c5.css
│   ├── index-e92ae01e.js
│   └── react-35ef61ed.svg
├── index.html
└── vite.svg

Le fichier index.html situé à la racine du répertoire dist est le "fichier principal" de l'application qui charge le fichier JavaScript empaqueté avec une balise script:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React</title>
    <script type="module" crossorigin src="/assets/index-e92ae01e.js"></script>
    <link rel="stylesheet" href="/assets/index-d526a0c5.css">
  </head>
  <body>
    <div id="root"></div>
    
  </body>
</html>

Comme nous pouvons le voir dans l'application exemple qui a été créée avec Vite, le script de build empaquette également les fichiers CSS de l'application dans un seul fichier /assets/index-d526a0c5.css.

En pratique, l'empaquetage est effectué de manière à définir un point d'entrée pour l'application, qui est typiquement le fichier index.js. Lorsque Webpack empaquette le code, il inclut non seulement le code du point d'entrée mais aussi le code qui est importé par le point d'entrée, ainsi que le code importé par ses instructions d'importation, et ainsi de suite.

Puisque une partie des fichiers importés sont des paquets comme React, Redux et Axios, le fichier JavaScript empaqueté contiendra également le contenu de chacune de ces bibliothèques.

L'ancienne manière de diviser le code de l'application en plusieurs fichiers reposait sur le fait que le fichier index.html chargeait tous les fichiers JavaScript séparés de l'application à l'aide de balises script. Cela résultait en une diminution de performance, puisque le chargement de chaque fichier séparé entraîne certains frais généraux. Pour cette raison, la méthode préférée de nos jours est d'empaqueter le code en un seul fichier.

Ensuite, nous allons créer une configuration Webpack adaptée pour une application React à la main, à partir de zéro.

Créons un nouveau répertoire pour le projet avec les sous-répertoires (build et src) et fichiers suivants:


├── build
├── package.json
├── src
│   └── index.js
└── webpack.config.js

Le contenu du fichier package.json peut par exemple être le suivant:

{
  "name": "webpack-part7",
  "version": "0.0.1",
  "description": "practising webpack",
  "scripts": {},
  "license": "MIT"
}

Installons webpack avec la commande:

npm install --save-dev webpack webpack-cli

Nous définissons la fonctionnalité de webpack dans le fichier webpack.config.js, que nous initialisons avec le contenu suivant:

const path = require('path')

const config = () => {
  return {
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'build'),
      filename: 'main.js'
    }
  }
}

module.exports = config

Remarque: il serait possible de faire la définition directement comme un objet au lieu d'une fonction:

const path = require('path')

const config = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: 'main.js'
  }
}

module.exports = config

Un objet suffira dans de nombreuses situations, mais nous aurons plus tard besoin de certaines fonctionnalités qui exigent que la définition soit faite sous forme de fonction.

Nous allons ensuite définir un nouveau script npm appelé build qui exécutera l'empaquetage avec webpack:

// ...
"scripts": {
  "build": "webpack --mode=development"
},
// ...

Ajoutons un peu plus de code au fichier src/index.js:

const hello = name => {
  console.log(`hello ${name}`)
}

Lorsque nous exécutons la commande npm run build, notre code d'application sera empaqueté par webpack. L'opération produira un nouveau fichier main.js qui est ajouté sous le répertoire build:

sortie du terminal webpack npm run build

Le fichier contient beaucoup de choses qui semblent assez intéressantes. Nous pouvons également voir le code que nous avons écrit précédemment à la fin du fichier:

eval("const hello = name => {\n  console.log(`hello ${name}`)\n}\n\n//# sourceURL=webpack://webpack-osa7/./src/index.js?");

Ajoutons un fichier App.js sous le répertoire src avec le contenu suivant:

const App = () => {
  return null
}

export default App

Importons et utilisons le module App dans le fichier index.js:

import App from './App';

const hello = name => {
  console.log(`hello ${name}`)
}

App()

Lorsque nous empaquetons à nouveau l'application avec la commande npm run build, nous remarquons que webpack a pris en compte les deux fichiers:

sortie du terminal montrant que webpack a généré deux fichiers

Notre code d'application peut être trouvé à la fin du fichier bundle dans un format plutôt obscur:

sortie du terminal montrant notre code minifié

Fichier de configuration

Examinons de plus près le contenu de notre fichier webpack.config.js actuel:

const path = require('path')

const config = () => {
  return {
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'build'),
      filename: 'main.js'
    }
  }
}

module.exports = config

Le fichier de configuration a été écrit en JavaScript et la fonction retournant l'objet de configuration est exportée en utilisant la syntaxe de module de Node.

Notre définition de configuration minimale s'explique presque d'elle-même. La propriété entry de l'objet de configuration spécifie le fichier qui servira de point d'entrée pour l'empaquetage de l'application.

La propriété output définit l'emplacement où le code empaqueté sera stocké. Le répertoire cible doit être défini comme un chemin absolu, ce qui est facile à créer avec la méthode path.resolve. Nous utilisons aussi __dirname qui est une variable dans Node qui stocke le chemin vers le répertoire courant.

Empaquetage de React

Ensuite, transformons notre application en une application React minimale. Installons les bibliothèques requises:

npm install react react-dom

Et transformons notre application en une application React en ajoutant les définitions familières dans le fichier index.js:

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'

ReactDOM.createRoot(document.getElementById('root')).render(<App />)

Nous apporterons également les modifications suivantes au fichier App.js:

import React from 'react' // we need this now also in component files

const App = () => {
  return (
    <div>
      hello webpack
    </div>
  )
}

export default App

Nous avons encore besoin du fichier build/index.html qui servira de "page principale" de notre application et qui chargera notre code JavaScript empaqueté avec une balise script:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>React App</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/javascript" src="./main.js"></script>
  </body>
</html>

Lorsque nous empaquetons notre application, nous rencontrons le problème suivant:

terminal webpack échec chargeur nécessaire

Chargeurs (Loaders)

Le message d'erreur de webpack indique que nous pourrions avoir besoin d'un chargeur approprié pour empaqueter correctement le fichier App.js. Par défaut, webpack ne sait traiter que le JavaScript pur. Bien que nous ne nous en rendions peut-être plus compte, nous utilisons JSX pour rendre nos vues dans React. Pour illustrer cela, le code suivant n'est pas du JavaScript ordinaire:

const App = () => {
  return (
    <div>
      hello webpack
    </div>
  )
}

La syntaxe utilisée ci-dessus provient de JSX et nous offre une manière alternative de définir un élément React pour une balise HTML div.

Nous pouvons utiliser des chargeurs (loaders) pour informer webpack des fichiers qui doivent être traités avant d'être empaquetés.

Configurons un chargeur pour notre application qui transforme le code JSX en JavaScript ordinaire:

const path = require('path')

const config = () => {
  return {
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'build'),
      filename: 'main.js'
    },
    module: {      rules: [        {          test: /\.js$/,          loader: 'babel-loader',          options: {            presets: ['@babel/preset-react'],          },        },      ],    },  }
}

module.exports = config

Les chargeurs sont définis sous la propriété module dans le tableau rules (règles).

La définition d'un seul chargeur se compose de trois parties:

{
  test: /\.js$/,
  loader: 'babel-loader',
  options: {
    presets: ['@babel/preset-react']
  }
}

La propriété test spécifie que le chargeur est destiné aux fichiers dont les noms se terminent par .js. La propriété loader spécifie que le traitement de ces fichiers sera effectué avec babel-loader. La propriété options est utilisée pour spécifier des paramètres pour le chargeur, qui configurent sa fonctionnalité.

Installons le chargeur et ses paquets requis en tant que dépendance de développement:

npm install @babel/core babel-loader @babel/preset-react --save-dev

L'empaquetage de l'application réussira désormais.

Si nous apportons des modifications au composant App et examinons le code empaqueté, nous remarquons que la version empaquetée du composant ressemble à ceci:

const App = () =>
  react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(
    'div',
    null,
    'hello webpack'
  )

Comme nous pouvons le voir dans l'exemple ci-dessus, les éléments React qui ont été écrits en JSX sont maintenant créés avec du JavaScript ordinaire en utilisant la fonction createElement de React.

Vous pouvez tester l'application empaquetée en ouvrant le fichier build/index.html avec la fonctionnalité ouvrir le fichier de votre navigateur:

navigateur hello webpack

Il convient de noter que si le code source de l'application empaquetée utilise async/await, le navigateur ne rendra rien sur certains navigateurs. Rechercher le message d'erreur dans la console éclairera le problème. Avec la solution précédente étant obsolète, nous devons maintenant installer deux dépendances manquantes, à savoir core-js et regenerator-runtime:

npm install core-js regenerator-runtime

Vous devez importer ces dépendances en haut du fichier index.js:

import 'core-js/stable/index.js'
import 'regenerator-runtime/runtime.js'

Notre configuration contient presque tout ce dont nous avons besoin pour le développement React.

Transpilers

Le processus de transformation du code d'une forme de JavaScript à une autre est appelé transpilation. La définition générale du terme est de compiler du code source en le transformant d'un langage à un autre.

En utilisant la configuration de la section précédente, nous sommes en train de transpiler le code contenant JSX en JavaScript ordinaire avec l'aide de babel, qui est actuellement l'outil le plus populaire pour ce travail.

Comme mentionné dans la partie 1, la plupart des navigateurs ne prennent pas en charge les dernières fonctionnalités introduites dans ES6 et ES7, et pour cette raison, le code est généralement transpilé vers une version de JavaScript qui implémente la norme ES5.

Le processus de transpilation exécuté par Babel est défini avec des plugins. En pratique, la plupart des développeurs utilisent des presets prêts à l'emploi qui sont des groupes de plugins préconfigurés.

Actuellement, nous utilisons le preset @babel/preset-react pour transpiler le code source de notre application:

{
  test: /\.js$/,
  loader: 'babel-loader',
  options: {
    presets: ['@babel/preset-react']  }
}

Ajoutons le plugin @babel/preset-env qui contient tout ce dont nous avons besoin pour prendre le code utilisant toutes les dernières fonctionnalités et le transpiler en code compatible avec la norme ES5:

{
  test: /\.js$/,
  loader: 'babel-loader',
  options: {
    presets: ['@babel/preset-env', '@babel/preset-react']  }
}

Installons le preset avec la commande:

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

Lorsque nous transpilons le code, il est transformé en JavaScript à l'ancienne. La définition du composant App transformé ressemble à ceci:

var App = function App() {
  return _react2.default.createElement('div', null, 'hello webpack')
};

Comme nous pouvons le voir, les variables sont déclarées avec le mot-clé var, car JavaScript ES5 ne comprend pas le mot-clé const. Les fonctions fléchées ne sont également pas utilisées, c'est pourquoi la définition de fonction utilise le mot-clé function.

CSS

Ajoutons du CSS à notre application. Créons un nouveau fichier src/index.css:

.container {
  margin: 10px;
  background-color: #dee8e4;
}

Ensuite, utilisons le style dans le composant App:

const App = () => {
  return (
    <div className="container">
      hello webpack
    </div>
  )
}

Et nous importons le style dans le fichier index.js:

import './index.css'

Cela provoquera l'échec du processus de transpilation:

échec de webpack manquant chargeur pour css/style

Lors de l'utilisation de CSS, nous devons utiliser les chargeurs css et style:

{
  rules: [
    {
      test: /\.js$/,
      loader: 'babel-loader',
      options: {
        presets: ['@babel/preset-react', '@babel/preset-env'],
      },
    },
    {      test: /\.css$/,      use: ['style-loader', 'css-loader'],    },  ],
}

Le rôle du css loader est de charger les fichiers CSS et celui du style loader est de générer et d'injecter un élément style contenant tous les styles de l'application.

Avec cette configuration, les définitions CSS sont incluses dans le fichier main.js de l'application. Pour cette raison, il n'est pas nécessaire d'importer séparément les styles CSS dans le fichier principal index.html de l'application.

Si nécessaire, le CSS de l'application peut également être généré dans son propre fichier séparé en utilisant le mini-css-extract-plugin.

Lorsque nous installons les chargeurs:

npm install style-loader css-loader --save-dev

L'empaquetage réussira à nouveau et l'application recevra de nouveaux styles.

Webpack-dev-server

La configuration actuelle permet de développer notre application, mais le flux de travail est terrible (au point qu'il ressemble au flux de travail de développement avec Java). Chaque fois que nous apportons un changement au code, nous devons l'empaqueter et rafraîchir le navigateur pour tester le code.

Le webpack-dev-server offre une solution à nos problèmes. Installons-le avec la commande:

npm install --save-dev webpack-dev-server

Définissons un script npm pour démarrer le serveur de développement:

{
  // ...
  "scripts": {
    "build": "webpack --mode=development",
    "start": "webpack serve --mode=development"  },
  // ...
}

Ajoutons également une nouvelle propriété devServer à l'objet de configuration dans le fichier webpack.config.js:

const config = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: 'main.js',
  },
  devServer: {    static: path.resolve(__dirname, 'build'),    compress: true,    port: 3000,  },  // ...
};

La commande npm start va maintenant démarrer le serveur de développement sur le port 3000, ce qui signifie que notre application sera accessible en visitant http://localhost:3000 dans le navigateur. Lorsque nous apportons des modifications au code, le navigateur rafraîchira automatiquement la page.

Le processus de mise à jour du code est rapide. Lorsque nous utilisons le serveur de développement, le code n'est pas empaqueté de la manière habituelle dans le fichier main.js. Le résultat de l'empaquetage existe uniquement en mémoire.

Étendons le code en changeant la définition du composant App comme indiqué ci-dessous:

import React, { useState } from 'react'
import './index.css'

const App = () => {
  const [counter, setCounter] = useState(0)

  return (
    <div className="container">
      hello webpack {counter} clicks
      <button onClick={() => setCounter(counter + 1)}>
        press
      </button>
    </div>
  )
}

export default App

L'application fonctionne bien et le flux de travail de développement est assez fluide.

Cartes sources (Source maps)

Extrayons le gestionnaire de clic dans sa propre fonction et stockons la valeur précédente du compteur dans son propre état values:

const App = () => {
  const [counter, setCounter] = useState(0)
  const [values, setValues] = useState()
  const handleClick = () => {    setCounter(counter + 1)    setValues(values.concat(counter))  }
  return (
    <div className="container">
      hello webpack {counter} clicks
      <button onClick={handleClick}>        press
      </button>
    </div>
  )
}

L'application ne fonctionne plus et la console affichera l'erreur suivante :

console devtools ne peut pas concaténer sur undefined dans handleClick

Nous savons que l'erreur se trouve dans la méthode onClick, mais si l'application était plus grande, le message d'erreur serait assez difficile à localiser:


App.js:27 Uncaught TypeError: Cannot read property 'concat' of undefined
    at handleClick (App.js:27)

L'emplacement de l'erreur indiqué dans le message ne correspond pas à l'emplacement réel de l'erreur dans notre code source. Si nous cliquons sur le message d'erreur, nous remarquons que le code source affiché ne ressemble pas à notre code d'application:

la source devtools n'affiche pas notre code source

Bien sûr, nous voulons voir notre véritable code source dans le message d'erreur.

Heureusement, corriger le message d'erreur à cet égard est assez facile. Nous demanderons à webpack de générer une soi-disant carte source pour le bundle, ce qui rend possible de mapper les erreurs qui surviennent pendant l'exécution du bundle à la partie correspondante dans le code source original.

La carte source peut être générée en ajoutant une nouvelle propriété devtool à l'objet de configuration avec la valeur 'source-map':

const config = {
  entry: './src/index.js',
  output: {
    // ...
  },
  devServer: {
    // ...
  },
  devtool: 'source-map',  // ..
};

Webpack doit être redémarré lorsque nous apportons des modifications à sa configuration. Il est également possible de faire en sorte que webpack surveille les modifications apportées à lui-même, mais nous ne le ferons pas cette fois.

Le message d'erreur est maintenant beaucoup mieux

console devtools montrant une erreur de concat à une ligne différente

puisqu'il se réfère au code que nous avons écrit:

source devtools montrant notre vrai code avec values.concat

Générer la carte source permet également d'utiliser le débogueur Chrome:

débogueur devtools en pause juste avant la ligne en cause

Corrigeons le bug en initialisant l'état de values comme un tableau vide:

const App = () => {
  const [counter, setCounter] = useState(0)
  const [values, setValues] = useState([])
  // ...
}

Minification du code

Lorsque nous déployons l'application en production, nous utilisons le bundle de code main.js généré par webpack. La taille du fichier main.js est de 1009487 octets même si notre application ne contient que quelques lignes de notre code. La grande taille du fichier est due au fait que le bundle contient également le code source de toute la bibliothèque React. La taille du code empaqueté est importante car le navigateur doit charger le code lorsque l'application est utilisée pour la première fois. Avec des connexions internet à haute vitesse, 1009487 octets ne posent pas de problème, mais si nous continuons à ajouter plus de dépendances externes, la vitesse de chargement pourrait devenir un problème, en particulier pour les utilisateurs mobiles.

Si nous inspectons le contenu du fichier bundle, nous remarquons qu'il pourrait être grandement optimisé en termes de taille de fichier en supprimant tous les commentaires. Il n'est pas utile d'optimiser manuellement ces fichiers, car il existe de nombreux outils pour cela.

Le processus d'optimisation pour les fichiers JavaScript s'appelle la minification. L'un des outils principaux destinés à cet effet est UglifyJS.

À partir de la version 4 de webpack, le plugin de minification ne nécessite pas de configuration supplémentaire pour être utilisé. Il suffit de modifier le script npm dans le fichier package.json pour spécifier que webpack exécutera l'empaquetage du code en mode production:

{
  "name": "webpack-part7",
  "version": "0.0.1",
  "description": "practising webpack",
  "scripts": {
    "build": "webpack --mode=production",    "start": "webpack serve --mode=development"
  },
  "license": "MIT",
  "dependencies": {
    // ...
  },
  "devDependencies": {
    // ...
  }
}

Lorsque nous empaquetons à nouveau l'application, la taille du fichier main.js résultant diminue considérablement:

$ ls -l build/main.js
-rw-r--r--  1 mluukkai  ATKK\hyad-all  227651 Feb  7 15:58 build/main.js

Le résultat du processus de minification ressemble à du code C à l'ancienne; tous les commentaires et même les espaces blancs inutiles et les caractères de nouvelle ligne ont été supprimés, et les noms de variables ont été remplacés par un seul caractère.

function h(){if(!d){var e=u(p);d=!0;for(var t=c.length;t;){for(s=c,c=[];++f<t;)s&&s[f].run();f=-1,t=c.length}s=null,d=!1,function(e){if(o===clearTimeout)return clearTimeout(e);if((o===l||!o)&&clearTimeout)return o=clearTimeout,clearTimeout(e);try{o(e)}catch(t){try{return o.call(null,e)}catch(t){return o.call(this,e)}}}(e)}}a.nextTick=function(e){var t=new Array(arguments.length-1);if(arguments.length>1)

Configuration de développement et de production

Ensuite, ajoutons un backend à notre application en réutilisant le backend de l'application de notes désormais familier.

Stockons le contenu suivant dans le fichier db.json:

{
  "notes": [
    {
      "important": true,
      "content": "HTML is easy",
      "id": "5a3b8481bb01f9cb00ccb4a9"
    },
    {
      "important": false,
      "content": "Mongo can save js objects",
      "id": "5a3b920a61e8c8d3f484bdd0"
    }
  ]
}

Notre objectif est de configurer l'application avec webpack de telle manière que, lorsqu'elle est utilisée localement, l'application utilise le json-server disponible sur le port 3001 comme backend.

Le fichier empaqueté sera ensuite configuré pour utiliser le backend disponible à l'URL https://notes2023.fly.dev/api/notes.

Nous installerons axios, démarrerons le json-server, puis apporterons les modifications nécessaires à l'application. Pour varier un peu les choses, nous récupérerons les notes du backend avec notre hook personnalisé appelé useNotes:

import React, { useState, useEffect } from 'react'import axios from 'axios'const useNotes = (url) => {  const [notes, setNotes] = useState([])  useEffect(() => {    axios.get(url).then(response => {      setNotes(response.data)    })  }, [url])  return notes}
const App = () => {
  const [counter, setCounter] = useState(0)
  const [values, setValues] = useState([])
  const url = 'https://notes2023.fly.dev/api/notes'  const notes = useNotes(url)
  const handleClick = () => {
    setCounter(counter + 1)
    setValues(values.concat(counter))
  }

  return (
    <div className="container">
      hello webpack {counter} clicks
      <button onClick={handleClick}>press</button>
      <div>{notes.length} notes on server {url}</div>    </div>
  )
}

export default App

L'adresse du serveur backend est actuellement codée en dur dans le code de l'application. Comment pouvons-nous changer l'adresse de manière contrôlée pour qu'elle pointe vers le serveur backend de production lorsque le code est empaqueté pour la production ?

La fonction de configuration de Webpack a deux paramètres, env et argv. Nous pouvons utiliser ce dernier pour connaître le mode défini dans le script npm:

const path = require('path')

const config = (env, argv) => {  console.log('argv.mode:', argv.mode)
  return {
    // ...
  }
}

module.exports = config

Maintenant, si nous le souhaitons, nous pouvons configurer Webpack pour fonctionner différemment selon que l'environnement d'exploitation de l'application, ou mode, est défini sur production ou développement.

Nous pouvons également utiliser le DefinePlugin de webpack pour définir des constantes globales par défaut qui peuvent être utilisées dans le code empaqueté. Définissons une nouvelle constante globale BACKEND_URL qui obtient une valeur différente selon l'environnement pour lequel le code est empaqueté:

const path = require('path')
const webpack = require('webpack')
const config = (env, argv) => {
  console.log('argv', argv.mode)

  const backend_url = argv.mode === 'production'    ? 'https://notes2023.fly.dev/api/notes'    : 'http://localhost:3001/notes'
  return {
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'build'),
      filename: 'main.js'
    },
    devServer: {
      static: path.resolve(__dirname, 'build'),
      compress: true,
      port: 3000,
    },
    devtool: 'source-map',
    module: {
      // ...
    },
    plugins: [      new webpack.DefinePlugin({        BACKEND_URL: JSON.stringify(backend_url)      })    ]  }
}

module.exports = config

La constante globale est utilisée de la manière suivante dans le code:

const App = () => {
  const [counter, setCounter] = useState(0)
  const [values, setValues] = useState([])
  const notes = useNotes(BACKEND_URL)
  // ...
  return (
    <div className="container">
      hello webpack {counter} clicks
      <button onClick={handleClick} >press</button>
      <div>{notes.length} notes on server {BACKEND_URL}</div>    </div>
  )
}

Si la configuration pour le développement et la production diffère beaucoup, il peut être judicieux de séparer la configuration des deux dans leurs propres fichiers.

Maintenant, si l'application est lancée avec la commande npm start en mode développement, elle récupère les notes depuis l'adresse http://localhost:3001/notes. La version empaquetée avec la commande npm run build utilise l'adresse https://notes2023.fly.dev/api/notes pour obtenir la liste des notes.

Nous pouvons inspecter localement la version de production empaquetée de l'application en exécutant la commande suivante dans le répertoire build:

npx static-server

Par défaut, l'application empaquetée sera disponible à http://localhost:9080.

Polyfill

Notre application est terminée et fonctionne avec toutes les versions relativement récentes des navigateurs modernes, à l'exception d'Internet Explorer. La raison en est que, à cause d'axios, notre code utilise les Promesses, et aucune version existante d'IE ne les prend en charge:

tableau de compatibilité des navigateurs soulignant à quel point Internet Explorer est mauvais

Il y a beaucoup d'autres choses dans la norme qu'IE ne prend pas en charge. Quelque chose d'aussi inoffensif que la méthode find des tableaux JavaScript dépasse les capacités d'IE:

tableau de compatibilité des navigateurs montrant qu'IE ne prend pas en charge la méthode find

Dans ces situations, il ne suffit pas de transpiler le code, car la transpilation transforme simplement le code d'une version plus récente de JavaScript en une version plus ancienne avec une prise en charge plus large par les navigateurs. IE comprend syntaxiquement les Promesses mais n'a tout simplement pas implémenté leur fonctionnalité. La propriété find des tableaux dans IE est simplement undefined.

Si nous voulons que l'application soit compatible avec IE, nous devons ajouter un polyfill, qui est un code qui ajoute la fonctionnalité manquante aux navigateurs plus anciens.

Les polyfills peuvent être ajoutés à l'aide de webpack et Babel ou en installant l'une des nombreuses bibliothèques de polyfill existantes.

Le polyfill fourni par la bibliothèque promise-polyfill est facile à utiliser. Nous devons simplement ajouter ce qui suit à notre code d'application existant:

import PromisePolyfill from 'promise-polyfill'

if (!window.Promise) {
  window.Promise = PromisePolyfill
}

Si l'objet global Promise n'existe pas, ce qui signifie que le navigateur ne prend pas en charge les Promesses, la Promise polyfillée est stockée dans la variable globale. Si la Promise polyfillée est suffisamment bien implémentée, le reste du code devrait fonctionner sans problèmes.

Une liste exhaustive des polyfills existants peut être trouvée ici.

La compatibilité des différents API avec les navigateurs peut être vérifiée en visitant https://caniuse.com ou le site web de Mozilla.