Présentation

Selon la documentation de webpack :

Au coeur de webpack se trouve un "empaqueteur" (bundler) pour les applications javascript modernes. Lorsque webpack traite votre application, il crée en interne un graphique de dépendances qui cartographie chaque module dont votre projet a besoin et génère un ou plusieurs "bundle".

Pour résumé, webpack vous aide à gérer les imports et exports de fichiers de votre projet et il va regrouper tout votre code dans un seul fichier appelé "bundle". 

Pour comprendre l'intérêt de webpack, il faut avoir conscience qu'à chaque utilisation d'un fichier dans une page html (css, js, images...) une requête http est effectuée. webpack vous permet donc de minimiser le nombre de requêtes.

Principales fonctionnalités

Possibilité d'utilisation de 

  • Babel pour transpiler le JS et le rendre ainsi rétrocompatible
  • compresseur/minificateur de code en vue de réduire la latence réseau
  • compilateur de SASS
  • optimisateur d' images

Premier exemple d'utilisation

imaginons que vous ayez 4 fichiers js

src/first.js

export const first = 1

src/second.js

export const second = 2

src/third.js

import { first } from './first'
import { second } from './second'

export const third = first + second

src/main.js

import { third } from './third'

console.log(third)

Nous comprenons qu'il va falloir faire attention à l'ordre d'importation des fichiers pour que main.js fonctionne correctement.

C'est justement le travail de webpack !

Installation de webpack-cli

npm install --save-dev webpack-cli

Création du fichier de config webpack.config.js

A noter, lorsque que vous utilisez create-react-app, la config de webpack se trouve dans \node_modules\react-scripts\config\webpack.config.js

// require est une fonction native de Node.js qui renvoie un objet utilitaire 
// possède des méthodes comme "resolve" qui permet de générer des chemins 
// absolues sur tous les systèmes d'exploitation 
const path = require("path")
const config = {
    mode: "development",
    // Point d'entrée pour webpack
    entry: {
        myApp: [
            "./src/main.js",
        ],
    },
    // Sortie pour webpack
    output: {
        // Depuis le répertoire courant vers le répertoire dist qui contiendra les bundles
        path: path.resolve(__dirname, "dist/"),
        filename: "bundle.js"
    },
}

module.exports = config

Lancement de webpack

npx webpack build

Vérification

node dist/bundle.js// 3 attendu

Loaders

Par défaut, Webpack ne comprend que le JS et le JSON. Les loaders permettent de charger tous les autres types de fichiers afin que Webpack les ajoute à l’arbre des dépendances

Loader TypeScript

Pour commencer, renommer tous les fichiers js en ts : maintenant, nous codons en typescript !

Installation de typescript et de ts-loader :

npm install --save-dev typescript ts-loader //--save-dev (ou -D)  : packages dont on a besoin uniquement en phase de développement

Configuration de typescript (fichier tsconfig.json)

{
  "compilerOptions": {
    "outDir": "./dist/",
    "noImplicitAny": true,
    "module": "es6",
    "target": "es6",
    "jsx": "react",
    "allowJs": true,
    "moduleResolution": "node"
  }
}

Modification de webpack.config.js

const path = require("path");

const config = {
  mode: "development",
  // Point d'entrée de Webpack
  entry: {
    myApp: ["./src/main.ts"],
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/, // pour les fichiers se terminant pas ts ou tsx
        exclude: /node_modules/, // sans prendre en compte les dépendances
        use: "ts-loader",
      },
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
};

module.exports = config;

Vérification

Changer l'extension de tous les fichiers js en ts.

Relancer le build :

npx webpack build

Exécuter le bundle :

node dist/bundle.js// 3 attendu

sass-loader

Installation

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

Configuration

Modifier le fichier webpack.config.js

module: {
    rules: [
      {
        test: /\.tsx?$/, // pour les fichiers se terminant pas ts ou tsx
        exclude: /node_modules/, // sans prendre en compte les dépendances
        use: "ts-loader",
      },
      {
        test: /.scss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
    ],
  },

Pour importer vos fichiers scss :

import './css/tasks.scss';

Plugins

Les plugins permettent de réaliser des opérations globales lors de "l'empaquetage" (bundling)

Par exemple HtmlWebpackPlugin simplifie la création de fichier HTML pour servir les bundles webpack.

Installation de HtmlWebpackPlugin

npm install --save-dev html-webpack-plugin

Modification de webpack.config.js :

const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");

const config = {
  mode: "development",
  // Point d'entrée de Webpack
  entry: {
    myApp: ["./src/main.ts"],
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/, // pour les fichiers se terminant pas ts ou tsx
        exclude: /node_modules/, // sans prendre en compte les dépendances
        use: "ts-loader",
      }
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
  //plugins: [new HtmlWebpackPlugin()],
  plugins: [new HtmlWebpackPlugin({ template: __dirname + '/src/index.html' })],
};

module.exports = config;


Vous verrez qu'au build (npx webpack build), le fichier dist/index.html est créé.

Si vous souhaitez utiliser votre propre fichier html, utilisez le paramétrage suivant :

plugins: [new HtmlWebpackPlugin({ template: __dirname + '/src/index.html' })],

Installation de webpack-dev-server

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

Modification de package.json

{
  "devDependencies": {
    "css-loader": "^6.8.1",
    "html-webpack-plugin": "^5.5.3",
    "sass": "^1.66.1",
    "sass-loader": "^13.3.2",
    "style-loader": "^3.3.3",
    "ts-loader": "^9.4.4",
    "typescript": "^5.2.2",
    "webpack": "^5.88.2",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^4.15.1"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack-dev-server",
    "build": "NODE_ENV=production webpack"
  }
}

Lancement du serveur

npm start// lance le serveur sur le port 8080: http://localhost:8080/

Ajouter devServer: {open: true}, dans le fichier webpack.config.json pour que le navigateur s'ouvre automatiquement

React 

Utiliser Babel

Pour ajouter Typescript, deux choix s'offrent à nous.

  1. TypeScript Compiler (tsc) transpile TS en ES5 JS. tsc vérifie aussi les types.
  2. Babel transpile le TS. et tsc vérifie le type.

Babel a plus de flexibilité au niveau de la transpilation et permet le polyfill ( Recréer des fonctions inexistantes en ES5 mais qui existe en ES6 (Exemple : Array.prototype.find). De plus, les responsabilités sont claires :

  • Babel transpile
  • tsc vérifie les types.

Installer React et React-Dom

npm i -D react react-dom

Installer Babel

Babel est un compilateur qui transpile le code :

  • De JSX au Js standard
  • De ESNEXT à ES5
npm i -D @babel/core babel-loader
  • @babel/core : La bibliothèque principale de Babel.
  • babel-loader : permet d'utiliser Babel avec Webpack.

Remplacer ts-loader par babel-loader dans le webpack.config.js

rules: [
      {
        test: /\.tsx?$/, // pour les fichiers se terminant pas ts ou tsx
        exclude: /node_modules/, // sans prendre en compte les dépendances
        use: "babel-loader",
      },

Installer le transpileur

npm i -D @babel/preset-react

Pour faire fonctionner les applications sur de vieux navigateurs, il faut

  1. Transpiler le code de ES6+ à ES5- : Remplacer le nouveau JS par de l'ancien
  2. "Polyfill" : Recréer des fonctions inexistantes en ES5 mais qui existe en ES6 (Exemple : Array.prototype.find)

Installer les modules pour le polyfill

npm i -D @babel/preset-env core-js regenerator-runtime

Le module clé est @babel/preset-env et core-js. L'évolution de JavaScript étant très rapide, des centaines de fonctionnalités doivent être transposées et "polyfill". Mais heureusement, @babel/preset-env est un preset de transpilation intelligent qui couvre la plupart des fonctionnalités. Et core-js est un preset polyfill. Lorsque vous utilisez des fonctionnalités que @babel/preset-env et core-js ne supportent pas, il est temps de penser à utiliser des plugins. regenerator-runtime est pour les promesses.

Créer fichier babel.config.json à la racine

{
  "presets": [
    [
      "@babel/preset-env",
      {
        // ajoute au début de chaque fichier l'importation de polyfills pour les fonctionnalités utilisées 
        // dans ce fichier et non supportées par les environnements cibles
        "useBuiltIns": "usage",
        "corejs": 3 // Numéro de version, la 3 est maintenue
      }
    ],
    "@babel/preset-react"
  ]
}

Installer Typescript

Installer le transpileur Typescript

npm i -D @babel/preset-typescript

Ajouter @babel/preset-typescript dans babel.config.json.

{
  "presets": [
    "@babel/preset-typescript",
    ...
  ]
}

Installer les types de React

npm i -D @types/react @types/react-dom

Ajouter dans le tsconfig.json

Babel se charge de la compilation de typescript, "noEmit" permet de s'assurer que ce n'est pas tsc qui gère la transpilation
esModuleInterop permet de gérer les imports typescript au sein de React

"compilerOptions": {
    "noEmit": true,
    "esModuleInterop": true,
    ...
}

et enlever "outDir" puisque tsc ne s'occupe plus de la transpilation

    "outDir": "./dist/",

Ajouter le script pour le type-checking dans le package.json

"script": {
  ...
  "tscheck": "tsc -w"
}

Modifier le htmlWebpackPlugin dans le webpack.config.js (ajout du template pour pouvoir ajouter son propre html dans index.html)

plugins: [new HtmlWebpackPlugin({template: "./dist/index.html"})]

Ajouter une div avec l'attribut id="root" dans dist/index.html

Créer un index.tsx dans le dossier src

import React from 'react';
import {createRoot} from 'react-dom/client';
import {App} from './App';

const rootNode = document.getElementById('root');
if (rootNode) {
    createRoot(rootNode)
        .render(<App/>);
}

Modifier l'entrée dans le webpack.config.js par index.tsx

Créer App.tsx dans le dossier src

import React from 'react';
import Count from "./Count";

export const App = () => (
    <>
        <h1>Hello React</h1>
        <Count/>
    </>
);

Créer Count.tsx dans le dossier src

import React from 'react';

function Count() {
    return (
        <div>
            1
        </div>
    );
}

export default Count;

Relancer l'application

npm start

Utiliser SASS

Installation 

npm i -D sass style-loader css-loader sass-loader

Modifier le fichier webpack.config.js

module: {
    rules: [
        ...
        {
            test: /\.scss?$/,
            use: ['style-loader', 'css-loader', 'sass-loader']
        }
    ],
},

Créer un fichier scss dans src et l'importer dans main.js

Relancer l'application

npm start

Installer bootstrap

npm install react-bootstrap bootstrap

importer les fichiers sources de bootstrap 

Ajouter dans index.scss par exemple :

$warning: green;
@import "~bootstrap/scss/bootstrap";

Importer votre fichier index.scss dans index.tsx : 

import "./sass/custom.scss";

Emuler un serveur à l'aide de json-server.

Installer json-server

npm i -D json-server

Créer un fichier db.json à la racine

Attention, pour que json-server fonctionne, Il faut que chaque objet possède une propriété id

exemple:

{
  "posts": [
    { "id": 1, "title": "Webpack", "author": "Bob" },
    { "id": 2, "title": "Typescript", "author": "Josianne" },
    { "id": 3, "title": "json-server", "author": "Simon" }
  ]
}

Créer un script dans package.json pour lancer le serveur

"json-server": "json-server --watch db.json"

Lancer json-server

npm run json-server

Executer une requête

Les routes utilisables :

Requête Http Route
GET /posts
GET /posts/1
POST /posts
PUT /posts/1
PATCH /posts/1
DELETE /posts/1

Pas besoin de préciser l'id dans le corps de votre requête POST / PUT / PATCH

POST / PUT / PATCH doivent inclure Content-Type: application/json dans leur header de requête.

Exemple de requête GET : 

fetch("http://localhost:3000/posts")
    .then(response => {
      console.log(`response status`, response.status);
      return response.json();
    })
    .then(data => {
      console.log(`data : `, data);
    })
    .catch(error => {
      console.log(`erreur attrapée : `, error);
    })

Exemple de requête POST : 

fetch("http://localhost:3000/posts",
    {
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      method: "POST",
      body: JSON.stringify({ "title": "Simon", "author": "Yvan" })
    })
    .then(function (res) { console.log(res) })
    .catch(function (res) { console.log(res) })

Exercice 1

Temps : entre 3 et 5 heures.

Par groupe de 3, choisir un exemple d'application (ex : boutique en ligne) et gérer à minima un "CRUD".

Utiliser :

  • react + ts + jsx + bootstrap + sass .Bootstrap est optionnel, vous pouvez vous en passer ou utiliser un équivalent
  • json-server ou des api de votre choix (n'y passez pas plus de 30mn pour les choisir)
  • fetch, await, async
  • les hooks : à minima useState et useEffect mais d'autres hooks comme userRef, useContext, useReducer, ... peuvent être utiles
  • une arborescence claire : src/components, src/services, src/sass, src/interfaces ...
  • Partagez votre code sur un repository github
  • Préparez une présentation qui explique les points clés
  • Entre-aidez vous !!!

Projet noté