Technologies Web synchrones et multi-dispositifs
This project is maintained by aurelient
L’objectif du TP est de mettre en place “l’enveloppe” d’une application Web avec un serveur Node/Express léger, et un framework TS côté client. Pour l’UE le client sera développé avec React, mais la “stack” que nous allons voir dans ce TP serait peu ou presque la même pour Angular ou Vue.
Nous allons voir :
Ce TP fera l’objet d’un premier rendu individuel et d’une note binaire (PASS/FAIL). Voir les critères d’évaluation en bas de la page.
Vous ferez le rendu sur la forge.
Nous vous recommandons chaudement de vous servir d’un version manager
pour NodeJS comme Node Version Manager
NVM afin de pouvoir changer de version de Node facilement ou installer la dernière version long term support (LTS)
avec nvm install --lts
. Ce conseille s’applique à toutes les autres technologies peu importe le langage (venv pour python, sdkman! pour le sdk android, …)
Créez un projet git sur la forge dès maintenant. Remplissez le champ Tomuss associé.
Installer Node dans sa dernière version lts
et Express si ce n’est pas déjà fait. Si c’est le cas, pensez à les mettre à jour.
(Optionel) Selon votre OS, la version de node et d’Express que vous allez installer, il sera peut être nécessaire d’installer express-generator
qui gère le “cli” d’Express (la possibilité de l’invoquer depuis la ligne de commande). Installez le globalement avec npm ou yarn.
À la racine de votre projet, créez deux dossiers:
server
avec un dossier src
qui contiendra le code Nodejs + Express côté serveurclient
avec un dossier src
qui contiendra le code React côté navigateurCe sont deux projets distincts.
Pensez régulièrement à ajouter les fichiers qui n’ont pas à être versionnés à votre .gitignore (a minima : node_modules & dist), vous. pouvez vous servir de gitignore.io pour en généré un via des tags (Visual Studio Code, nodejs, …)
Poussez le projet sur la forge.
On va en premier configurer le serveur.
Allez dans le dossier server
.
Créez un projet node (yarn init
), en le liant à votre dépôt Git sur la forge via le champs repository
du package.json
.
Pour pouvoir travailler avec Typescript, installez quelques dépendances supplémentaires:
# Ajoute typescript à votre projet
yarn add typescript ts-node express --dev
# Ajoute les types d'un module à typescript
yarn add @types/node @types/express
# Installe le compilateur Typescript de façon globale
npm i -g tsc
# Créé un fichier de configuration pour Typescript
tsc --init
Trouvez le fichier automatiquement généré tsconfig.json
:
outDir
et décommentez là pour mettre comme valeur ./dist
.rootDir
et décommentez là pour mettre comme valeur ./src
.package.json
, dans la partie scripts
, mettre:"scripts": {
"start": "tsc && node dist/index.js"
}
Voici l’architecture du projet express que nous allons créer:
| - dist
| - src
| | - index.ts
| | - routes
| | - hello.router.ts
| - package.json
| - yarn.lock
| - tsconfig.json
| - .gitignore
Dans notre index.ts
, nous allons créer le serveur:
import express from 'express';
import { HelloRouteur } from './routes/hello.router';
const app = express();
const port = process.env.PORT || 3000;
app.listen(port, () => {
process.stdout.write(`Server started on port: ${port}\n`);
});
app.use('/hello', HelloRouteur);
Dans hello.router.ts
, nous allons créer un des routeurs de notre API:
import express from 'express';
const helloRouteur = express.Router();
// With middlewares you can ensure the user is authenticated
// before requesting secured API routes
helloRouteur.use((request, response, next) => {
process.stdout.write('HelloRouter Middleware\n');
if (request.ip.endsWith('127.0.0.1')) {
process.stdout.write('Request from local IP\n');
next();
} else {
next();
}
});
helloRouteur.get('/', (request, response) => {
response.send('Hello TIW8 !');
});
export {
helloRouteur as HelloRouteur
};
Testez votre serveur avec la commande yarn start
Vérifier que le serveur fonctionne et versionnez le sur la forge.
Allez maintenant dans le dossier client
.
Nous verrons plus en détail le fonctionnement de React lors de la prochaine séance. Pour le moment nous allons créer un projet simple.
Comme pour le projet serveur, il faut installer quelques dépendances avant tout:
yarn add typescript --dev
yarn add react-dom react
yarn add @types/react-dom @types/react --dev
Dans le dossier src
, créez un index.html
.
Ce sera le seul fichier HTML du projet, il sera “peuplé” dynamiquement par React.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1"
/>
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>React TP1</title>
</head>
<body>
<!-- L'id de ce div est important -->
<div id="root"></div>
</body>
</html>
Dans le même dossier nous allons créer un premier composant React, on l’appellera index.tsx
(l’extension de fichier est très importante):
import * as React from "react";
import { createRoot } from 'react-dom/client'
const Index = () => (
<div className="container">
<h1>Hello World</h1>
</div>
)
const container = document.getElementById('root')
const root = createRoot(container)
root.render(<Index />)
Installer Webpack en dev (pas la peine d’avoir les dépendances pour le déploiement) :
webpack
(Le bundler)webpack-cli
(Command Line Interface pour lancer les commandes webpack)Installez également le module html-webpack-plugin pour faciliter la création de fichier HTML avec Webpack.
yarn add --dev ts-loader css-loader html-webpack-plugin mini-css-extract-plugin source-map-loader webpack webpack-cli
Même si les dernières versions de webpack peuvent fonctionner sans fichier de configuration (avec des défauts), vous aurez de toutes façons à spécifier une config dans ce TP. Mettez donc en place un fichier webpack.config.js
avec une configuration minimale (entry, output), que vous allez modifier par la suite.
Dans ce fichier de configuration, pointez vers le point d’entée React (le fichier index.tsx) et indiquez ou l’appliquer (template: "./src/index.html"
). Ci-dessous une partie de ce fichier, qui sera complétée par la suite :
const HtmlWebPackPlugin = require("html-webpack-plugin");
const path = require('path');
const htmlPlugin = new HtmlWebPackPlugin({
template: "./src/index.html",
filename: "./index.html"
});
module.exports = (env, argv) => {
console.log(argv.mode);
return {
entry: "./src/index.tsx",
output: {
path: path.join(__dirname, 'dist'),
filename: "bundle.js"
},
plugins: [htmlPlugin],
module: {
rules: [
{
...
}
]
}
};
};
React s’appuie “normalement” sur JSX pour lier la logique de rendu, la gestion d’évènement et les changements d’états pour un élément donné. Ces éléments seraient normalement séparés entre langages et technos différentes. Babel permet de traduire ce code (et au passage de transformer du ES6 en ES5).
Nous allons reprendre la même logique mais avec du Typescript (un fichier typescript simple se termine en .ts, nous allons créer des .tsx). JSX, Typescript ou TSX ne sont pas interprétés par les navigateurs, nous devons donc le “traduire” ou transpiler en HTML+JS pour que le code devienne compréhensible.
Nous avons normalement déjà installé les dépendances typescript, reste à installer un dépendance (de développement) pour l’intégration à Webpack: ts-loader
Configurez le transpileur Typescript à l’aide d’un fichier tsconfig.json
à la racine de votre projet, en indiquant les pré-configurations utilisées pour le reste du projet.
{
"compilerOptions": {
"jsx": "react",
"module": "commonjs",
"noImplicitAny": true,
"outDir": "./dist",
"preserveConstEnums": true,
"removeComments": true,
"sourceMap": true,
"target": "es5"
},
"include": ["src"],
"exclude": ["node_modules", "**/*.spec.ts", "**/*.test.ts"]
}
Il faut spécifier à Webpack la transpilation des fichiers .ts et .tsx du projet lors du build.
Cela se fait dans le fichier webpack.config.js
:
module.exports = {
module: {
rules: [
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: "ts-loader",
},
},
],
},
};
Il faut aussi spécifier à votre bundler (Webpack) comment résoudre les liens vers les modules, il faut lui dire de quelles extensions de fichier rajouter et dans quel ordre les traiter. Pour cela on utilise la directive resolve de webpack.
resolve: {
extensions: [".js", ".jsx", ".json", ".ts", ".tsx"],
},
Il faut maintenant assembler le code React.
Dans package.json
ajouter une commande production
et une commande dev
(dans scripts
)
"scripts": {
"production": "webpack --mode production",
"dev": "webpack --mode development"
}
Cette commande doit vous générer un fichier HTML et un fichier JS dans dist
.
Il faut maintenant dire à Express où aller chercher le contenu.
Pour cela il faut lui dire que sa route /
est maintenant ../../client/dist/index.html
Rajouter les constantes suivantes (selon vos noms de fichiers et de dossiers) :
const path = require("path");
const DIST_DIR = path.join(__dirname, "../../client/dist");
const HTML_FILE = path.join(DIST_DIR, "index.html");
// TODO Modifier la route '/' pour qu'elle pointe sur HTML_FILE
Pour que Express trouve plus tard son chemin “de base” et les fichiers statiques générés par Webpack (images, css…) rajouter la ligne suivante:
app.use(express.static(DIST_DIR));
Installez le module file-loader
(toujours en dev).
Et rajoutez la règle suivante dans la partie rules
du fichier webpack.config.js:
pour que webpack place les images dans un dossier /static/
.
{
test: /\.(png|svg|jpg|gif)$/,
loader: "file-loader",
options: { name: '/static/[name].[ext]' }
}
Note: depuis Webpack 5, vous pouvez préférer Asset Modules qui évite d’utiliser un loader externe.
Il faudra les importer dans vos composants. Voici comment cela se fait au sein d’un composant React:
// Import de l'image
import LOGO from "./logo.png";
// Utilisation
<img src={LOGO} alt="Logo" />;
Pour que l’import marche, il faut spécifier à Typescript et Webpack de traiter les images comme des modules:
index.d.ts
qui contient la définition des modules/types associé aux extensions de fichiers :declare module '*.png';
declare module '*.jpg';
tsconfig.json
, modifier la ligne d’include en rajoutant le fichier créé : ["client", "index.d.ts"],
Je conseille d’utiliser une surcouche à Tailwind CSS. Par exemple shadcn/ui.
Pour vérifier que votre code se conforme aux bonnes pratiques, nous allons utiliser eslint, et son plugin react.
Pour créer votre fichier de configuration eslint
taper yarn run eslint --init
Vous pouvez tester eslint à la “main” avec
yarn run eslint src/*.tsx
Ajouter ensuite eslint à Webpack. Installez le module eslint-webpack-plugin
en dev. Importez le dans votre webpack config et rajouter les lignes suivantes au blog plugin. eslint se lancera maintenant lors du build (vous pouvez rajouter une erreur dans votre index.tsx et tester le build).
plugins: [
...,
new ESLintPlugin({
extensions: ["js", "jsx", "ts", "tsx"],
}),
],
Si vous utilisez Prettier dans votre editeur de code vous risquez de rencontrer des conflits avec ESlint . J’ai suivi la documentation de Prettier, et ces deux posts 1, 2 pour corriger ça.
Afin de rendre notre application disponible sur le Web, nous allons la déployer sur le GitLab Pages de votre projet.
Depuis la page de votre projet GitLab, allez dans settings > pages
et suivez les indications pour héberger le site.
N’oubliez pas de désactiver l’option watch
de webpack si vous lancez Webpack en --mode production
voir ici.
Créer deux composants basiques sans aucune logique. Le premier affichera un titre. Le deuxième affichera des images prises dans un dossier statique.
On placera ces composants dans un dossier components
.
import React from "react";
import ReactDOM from "react-dom";
import Header from "./components/Header.tsx";
import Content from "./components/Content.tsx";
const Index = () => {
return (
<div className="container">
<Header />
<Content />
</div>
);
};
ReactDOM.render(<Index />, document.getElementById("root"));
Installez l’extension React Developer Tools dans votre navigateur préféré. Inspectez l’application.
Le TP est individuel. Il est évalué sur une base binaire REUSSI/RATE et compte pour 10% de la note de Controle Continu (CC) totale. Il est à rendre pour vendredi 12 23h59.
Les critères d’évaluation sont les suivants pour avoir un REUSSI (=20), si un des critères n’est pas rempli c’est un RATE (=0):
git clone
sans aucune modification de l’urlyarn run build
construit le projetyarn run start
lance le serveur.eslint
ne retourne pas d’erreur