Skip to content
Snippets Groups Projects
Commit 276eff54 authored by Thomas Fritsch's avatar Thomas Fritsch
Browse files

maj readme

- fork
- nouveau dom (maj skin css)
- passage PageRenderer -> Router
- suppression classe Page (inutile à ce stade avec le Router)
- fusion avec readme cours-react/tp2
parent 522136ab
Branches
No related tags found
No related merge requests found
Showing
with 452 additions and 411 deletions
# TP 2 : A. Préparatifs <!-- omit in toc --> <img src="images/readme/header-small.jpg" >
# A. Préparatifs <!-- omit in toc -->
## Sommaire <!-- omit in toc --> ## Sommaire <!-- omit in toc -->
- [A.1. Configuration du projet](#a1-configuration-du-projet) - [A.1. Récupération du projet](#a1-récupération-du-projet)
- [A.2. Extensions de VSCodium](#a2-extensions-de-vscodium) - [A.2. Configuration de Prettier](#a2-configuration-de-prettier)
- [A.3. Lancement de l'application](#a3-lancement-de-lapplication) - [A.3. Lancement de l'application](#a3-lancement-de-lapplication)
- [A.4. Le fichier `package.json`](#a4-le-fichier-packagejson) - [A.4. Le fichier `package.json`](#a4-le-fichier-packagejson)
- [A.5. Créer un script de build personnalisé](#a5-créer-un-script-de-build-personnalisé) - [A.5. Créer un script de build personnalisé](#a5-créer-un-script-de-build-personnalisé)
- [Étape suivante](#Étape-suivante)
## A.1. Configuration du projet ## A.1. Récupération du projet
Avant toute chose, ajoutez ces lignes à votre fichier `~/.bashrc` : **Ce repo contient une solution commentée du précédent TP.** <br>
```bash Pour ce TP vous pouvez soit repartir de vos fichiers du précédent TP (si vous l'aviez terminé et que le formateur a validé que tout était correct notamment au niveau de la config Babel et webpack) ou bien cloner ce repo et vous en servir comme base pour ce TP.
export http_proxy=http://cache.univ-lille1.fr:3128
export https_proxy=http://cache.univ-lille1.fr:3128
export PATH=$PATH:chemin/vers/VSCodium/bin
```
(n'oubliez pas d'adapter le chemin vers vscodium et de mettre `/bin` à la fin)
Fermez le terminal et relancez le pour prendre en compte les modifications. *Si vous repartez de vos fichiers, **pensez à faire un `git pull`** pour récupérer les dernières modifications du repo (probablement des modifs de CSS ou des corrections de dernière minute).*
**Ce repo contient une solution commentée du précédent TP. Il servira de base au TP2 :** 1. **Commencez par faire un fork du TP :**
```bash - soit en cliquant sur le bouton `"Créer une divergence"` (_`"Fork"` sur la version anglaise de gitlab_)
mkdir -p ~/ws-js/tp2 - soit en vous rendant directement sur https://gitlab.univ-lille.fr/js/tp2/-/forks/new
git clone https://gitlab.univ-lille.fr/js/tp2.git ~/ws-js/tp2
``` Choisissez de placer le fork dans votre profil utilisateur et vérifiez que le repo est **bien en mode "privé"**
Ouvrez ensuite le projet dans VSCodium : 2. **Ajoutez moi en tant que "reporter" pour que j'ai accès à votre code :** dans le menu de gauche, cliquez que `"Membres"`, et entrez `@thomas.fritsch` comme nom d'utilisateur, et donc `"reporter"` comme rôle.
3. **Récupérez ensuite les fichiers de ce TP grâce à Git : clonez votre fork dans un dossier de votre choix** :
```bash ```bash
codium ~/ws-js/tp2 mkdir ~/tps-js
git clone https://gitlab.univ-lille.fr/<votre-username>/tp2.git ~/tps-js/tp2
``` ```
> _**NB :** Comme pour le TP1, ici je clone dans mon dossier `/home/thomas/tps-js/tp2`. **Si vous êtes sous windows faites attention au sens des slashs et au caractère `"~"`** qui représente le dossier de l'utilisateur sur système unix. Si vous êtes sous windows utilisez **Git bash** (qui comprend cette syntaxe) ou si vous tenez vraiment à utiliser **cmd** pensez à adapter !_
Il reste maintenant à **installer les paquets npm nécessaires au bon fonctionnement du projet** (notamment le compilateur [Babel](https://babeljs.io)). > _**NB2 :** Comme pour le TP1 aussi, si vous préférez **cloner en SSH** pour ne pas avoir à taper votre mot de passe à chaque fois, renseignez votre clé SSH dans votre [compte utilisateur gitlab](https://gitlab.univ-lille.fr/profile/keys) et clonez à partir de cette URL : `git@gitlab-ssh.univ-lille.fr:js/tp2.git`_
Ouvrez un terminal directement dans VSCodium à l'aide du raccourci <kbd>CTRL</kbd>+<kbd>SHIFT</kbd>+<kbd>²</kbd> (ce terminal intégré utilise le terminal par défaut du système, mais vous pouvez le configurer pour utiliser un autre terminal comme Git bash sous windows par exemple).
L'avantage du terminal intégré, c'est qu'il s'ouvre directement dans le dossier du projet dans lequel vous travaillez (ici ça doit être ~/ws-js/tp2).Tapez ensuite : 4. **Ouvrez le projet dans VSCodium** (pour les différentes façon d'ouvrir le projet relisez les [instructions du TP1](https://gitlab.univ-lille.fr/js/tp1/-/blob/master/A-preparatifs.md#a3-ouvrir-le-projet-dans-vscodium) )
```bash
codium ~/tps-js/tp2
```
5. **Installez les paquets npm nécessaires au projet** notamment le compilateur [Babel](https://babeljs.io).<br>
Ouvrez un terminal intégré à VSCodium (<kbd>CTRL</kbd>+<kbd>J</kbd> *(PC)* / <kbd>CMD</kbd>+<kbd>J</kbd> *(Mac)*) et tapez juste :
```bash ```bash
npm install npm install
``` ```
Vous noterez qu'on ne précise pas les paquets à installer, npm va en effet les déterminer automatiquement à partir du contenu du fichier `package.json` et plus particulièrement à partir des sections `"dependencies"` et `"devDependencies"` qui indiquent quels sont les paquets qui ont été installés précédemment. > _**NB :** Vous noterez qu'on ne précise pas les paquets à installer. npm va en effet les déterminer **automatiquement** à partir du contenu du fichier `package.json` et plus particulièrement à partir des sections `"dependencies"` et `"devDependencies"` qui indiquent quels sont les paquets qui ont été installés précédemment._
>
> **Magique !** 🙌
## A.2. Configuration de Prettier
## A.2. Extensions de VSCodium _**Lors du précédent TP, vous avez en principe installé l'extension Prettier dans VSCodium.**_
Avant de commencer à coder, on va installer plusieurs extensions de VSCodium qui vont nous être utiles par la suite : Prettier est un formateur de code automatique qui est le plus populaire à l'heure actuelle dans l'écosystème JavaScript.
1. Ouvrez le panneau des extensions à l'aide du raccourci <kbd>CTRL</kbd>+<kbd>SHIFT</kbd>+<kbd>X</kbd> **C'est le moment de configurer cette extension** pour l'utiliser dans notre projet.
2. Recherchez et installez les extensions suivantes :
- **Prettier - Code formatter** : un formateur de code automatique : (presque) plus possible d'oublier un point-virgule, ou de mal indenter son code !
- **Visual Studio IntelliCode** : améliore les suggestions lors de la frappe
- **es6-string-html** : rend possible la coloration syntaxique du code html contenu dans des template strings
3. Pour configurer Prettier, ajoutez un fichier `.vscode/settings.json` dans le tp avec le contenu suivant : 1. **Ajoutez un dossier `.vscode` dans le dossier du TP et placez y un fichier nommé `settings.json`** avec le contenu suivant :
```json ```json
{ {
...@@ -69,55 +72,63 @@ Avant de commencer à coder, on va installer plusieurs extensions de VSCodium qu ...@@ -69,55 +72,63 @@ Avant de commencer à coder, on va installer plusieurs extensions de VSCodium qu
"singleQuote": true, "singleQuote": true,
"trailingComma": "es5", "trailingComma": "es5",
"endOfLine": "lf", "endOfLine": "lf",
"useTabs": true "useTabs": true,
"arrowParens": "avoid"
} }
``` ```
Enfin, installez prettier avec npm : Enfin, installez le paquet npm `prettier` dans le projet (_nécessaire pour que l'extension fonctionne_) :
```bash ```bash
npm install --save-dev prettier npm install --save-dev prettier
``` ```
Avec cette configuration, vos fichiers JS seront automatiquement formatés à chaque sauvegarde !<br>Pour voir la liste des configurations possibles, rendez vous sur https://prettier.io/docs/en/configuration.html Avec cette configuration, vos fichiers JS seront maintenant automatiquement formatés à chaque sauvegarde !
> _**NB :** si vous souhaitez en savoir plus sur la liste des configurations possibles, rendez vous sur https://prettier.io/docs/en/configuration.html_
## A.3. Lancement de l'application ## A.3. Lancement de l'application
1. **Lancez la compilation de votre projet** à l'aide de la commande : Comme dans le précédent TP lancez un serveur HTTP et la compilation du projet **dans deux terminaux côte à côte** ([terminaux splittés](https://code.visualstudio.com/docs/editor/integrated-terminal#_terminal-splitting)) :
1. **Lancez un serveur http** dans un terminal intégré de VSCodium (<kbd>CTRL</kbd>+<kbd>J</kbd> *(PC)* / <kbd>CMD</kbd>+<kbd>J</kbd> *(Mac)*) :
```bash ```bash
./node_modules/.bin/babel js -d build --verbose --watch --source-maps npx serve -l 8000
``` ```
4. **Lancez un serveur http** dans le dossier de votre TP : 2. **Lancez la compilation de votre projet** dans un **deuxième** [terminal splitté](https://code.visualstudio.com/docs/editor/integrated-terminal#_terminal-splitting) (*le `watch` et `npx serve` doivent tourner en parallèle*) :
```bash ```bash
npx serve -l 8000 ./node_modules/.bin/babel src -d build --verbose --watch --source-maps
``` ```
5. **Vérifiez dans le navigateur que la page index.html s'affiche correctement** en ouvrant l'url http://localhost:8000. Le résultat attendu est le suivant : <br><a href="images/pizzaland-00.jpg"><img src="images/readme/pizzaland-00.jpg" width="80%"></a> 3. **Vérifiez dans le navigateur que la page index.html s'affiche correctement** en ouvrant l'url http://localhost:8000.
Le résultat attendu est le suivant :
***NB: Si la page ne s'affiche pas correctement**, vérifiez que vous avez bien lancé le serveur http dans le dossier du projet, c'est à dire celui où se trouve le fichier `index.html`. Puis vérifiez dans la `Console` ou dans l'onglet `Sources` (Chrome) ou `Debugger` (Firefox) qu'l n'y a pas d'erreur JS lorsque la page se charge.* <img src="images/readme/pizzaland-00.png" >
6. **Effacez le contenu du fichier `js/main.js`** et ne conservez dedans que la déclaration du tableau `data` contenant les 3 objets littéraux de pizzas : > _**NB : Si la page ne s'affiche pas correctement**, vérifiez que vous avez bien lancé le serveur http dans le dossier du projet, c'est à dire celui où se trouve le fichier `index.html`. Puis vérifiez dans la `Console` ou dans l'onglet `Sources` (Chrome) ou `Debugger` (Firefox) qu'l n'y a pas d'erreur JS lorsque la page se charge._
4. **Effacez le contenu du fichier `src/main.js`** et ne conservez dedans que la déclaration du tableau `data` contenant les 3 objets littéraux de pizzas :
```js ```js
const data = [ const data = [
{ {
nom: 'Regina', name: 'Regina',
base: 'tomate', base: 'tomate',
prix_petite: 6.5, price_small: 6.5,
prix_grande: 9.95, price_large: 9.95,
image: 'https://images.unsplash.com/photo-1532246420286-127bcd803104?fit=crop&w=500&h=300' image: 'https://images.unsplash.com/photo-1532246420286-127bcd803104?fit=crop&w=500&h=300'
}, },
{ {
nom: 'Napolitaine', name: 'Napolitaine',
base: 'tomate', base: 'tomate',
prix_petite: 6.5, price_small: 6.5,
prix_grande: 8.95, price_large: 8.95,
image: 'https://images.unsplash.com/photo-1562707666-0ef112b353e0?&fit=crop&w=500&h=300' image: 'https://images.unsplash.com/photo-1562707666-0ef112b353e0?&fit=crop&w=500&h=300'
}, },
{ {
nom: 'Spicy', name: 'Spicy',
base: 'crème', base: 'crème',
prix_petite: 5.5, price_small: 5.5,
prix_grande: 8, price_large: 8,
image: 'https://images.unsplash.com/photo-1458642849426-cfb724f15ef7?fit=crop&w=500&h=300' image: 'https://images.unsplash.com/photo-1458642849426-cfb724f15ef7?fit=crop&w=500&h=300',
} }
]; ];
``` ```
...@@ -128,62 +139,77 @@ Avant de commencer à coder, on va installer plusieurs extensions de VSCodium qu ...@@ -128,62 +139,77 @@ Avant de commencer à coder, on va installer plusieurs extensions de VSCodium qu
Ce fichier sert à plusieurs choses et notamment : Ce fichier sert à plusieurs choses et notamment :
1. **Il permet de conserver l'historique de tous les paquets qui sont installés dans le projet.** C'est en quelque sorte l'équivalent du fichier `pom.xml` de maven. Vérifiez que dans la section `devDependencies` sont bien listés les paquets suivants : 1. **Il permet de conserver l'historique de tous les paquets qui sont installés dans le projet.** C'est en quelque sorte l'équivalent du fichier `pom.xml` de maven. Vérifiez que dans la section `devDependencies` sont bien listés les paquets suivants :
- @babel/cli - `@babel/cli`
- @babel/core - `@babel/core`
- @babel/preset-env - `@babel/preset-env`
- prettier - `prettier`
À chaque fois qu'on installe un paquet npm :
À chaque fois qu'on installe un paquet npm, le paquet en question se télécharge dans le dossier `node_modules`, puis le nom du paquet ainsi que sa version sont automatiquement ajoutés dans le fichier `package.json`. <br>Le dossier `node_modules` n'est **jamais** versionné (c'est en général un dossier relativement volumineux) mais le `package.json` lui l'est car il servira de "recette" pour indiquer aux développeurs qui rejoindraient le projet quels sont les paquets nécessaires. 1. le paquet en question se télécharge dans le dossier `node_modules`
2. puis le nom du paquet ainsi que sa version sont automatiquement ajoutés dans le fichier `package.json`.
En effet, avec un `package.json` renseigné, un nouveau développeur n'a qu'à exécuter la commande `npm install` (sans préciser de nom de paquet) pour installer automatiquement toutes les dépendances du projet (ce que vous avez fait plus haut) ! > _**NB :** Le dossier **`node_modules` n'est jamais versionné** (c'est en général un dossier relativement volumineux) mais le **`package.json` lui l'est** car il servira de "recette" pour indiquer aux développeurs qui rejoindraient le projet quels sont les paquets nécessaires._
>
> _En effet, grâce au `package.json`, un nouveau développeur n'a qu'à exécuter la commande `npm install` (sans préciser de nom de paquet) pour installer automatiquement toutes les dépendances du projet (c'est d'ailleurs ce que vous avez fait plus haut) !_
2. **Dans ce fichier on va également pouvoir ajouter des "scripts personnalisés" que l'on pourra lancer à l'aide de la commande `npm run xxxxx`.** C'est cette dernière possibilité que l'on va maintenant utiliser pour nous simplifier la vie dans la suite du TP. 2. **Dans ce fichier on va également pouvoir ajouter des "scripts personnalisés" que l'on pourra lancer à l'aide de la commande `npm run xxxxx`.** C'est cette dernière possibilité que l'on va maintenant exploiter pour nous simplifier la vie dans la suite du TP.
## A.5. Créer un script de build personnalisé ## A.5. Créer un script de build personnalisé
Jusque là pour lancer la compilation avec [Babel](https://babeljs.io), nous lancions une des deux commandes suivantes : Jusque là pour lancer la compilation avec [Babel](https://babeljs.io), nous lancions un des deux commandes suivantes :
```bash ```bash
./node_modules/.bin/babel js -d build ./node_modules/.bin/babel src -d build
``` ```
ou ou
```bash ```bash
./node_modules/.bin/babel js -d build --verbose --watch --source-maps ./node_modules/.bin/babel src -d build --verbose --watch --source-maps
``` ```
Avec le `package.json` on va créer des "raccourcis" pour lancer ces commandes plus facilement. Avec le `package.json` **on va créer des "raccourcis" pour lancer ces commandes plus facilement.**
1. **Stoppez d'abord la commande "babel --watch" que vous aviez lancée au point A.2.2.** 1. **Stoppez d'abord la commande "./node_modules/.bin/babel ... --watch ..." que vous aviez lancée au point A.3.2.**
2. Dans VSCodium, **ouvrez le fichier `package.json`** en tapant <kbd>CTRL</kbd>+<kbd>P</kbd> puis le nom du fichier ( <kbd>Enter</kbd> pour ouvrir le fichier) 2. Dans VSCodium, **ouvrez le fichier `package.json`** en tapant <kbd>CTRL</kbd>+<kbd>P</kbd> puis le nom du fichier ( <kbd>Enter</kbd> _pour ouvrir le fichier_)
3. **Localisez la section "scripts" du fichier**. Elle doit ressembler à : 3. **Localisez la section "scripts" du fichier**. Elle doit ressembler à :
```json ```json
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
``` ```
4. **Cette section permet d'indiquer des tâches qui pourront être lancées à l'aide de la commande `npm run <nom-du-script>`.** Par défaut le `package.json` contient une tâche `"test"`. Lancez le script `"test"` en tapant : 4. **Cette section permet d'indiquer des tâches qui pourront être lancées à l'aide de la commande `npm run <nom-du-script>`.** Par défaut le `package.json` contient une tâche `"test"`. Lancez donc ce script `"test"` en tapant :
```bash ```bash
npm run test npm run test
``` ```
Vous verrez la commande `"echo \"Error: no test specified\" && exit 1"` s'exécuter dans le terminal. Vous verrez la commande `"echo \"Error: no test specified\" && exit 1"` s'exécuter dans le terminal :
<img src="images/readme/npm-run-test.gif" style="width:80%"/><br>
`"test"` est donc une sorte d'alias permettant de lancer une commande plus complexe. <img src="images/readme/npm-run-test.gif" />
`"test"` est donc une sorte d'**alias**, de **"raccourci"**, permettant de lancer une commande plus complexe.
5. **Ajoutez maintenant dans le `package.json` un nouveau script qu'on appellera "build"** et qui permettra de lancer la compilation Babel : 5. **Ajoutez maintenant dans le `package.json` un nouveau script qu'on appellera "build"** et qui permettra de lancer la compilation Babel :
```json ```json
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"build": "babel js -d build" "build": "babel src -d build"
}, },
``` ```
*Vous noterez que le chemin `./node_modules/.bin/' n'est pas nécessaire !* > _**NB :** Vous noterez que le chemin `./node_modules/.bin/' que l'on utilisait jusque là dans notre commande de compilation n'est ici plus nécessaire !_
6. **Lancez la commande `npm run build`** et constatez avec émerveillement que la compilation babel se lance ! 6. **Lancez la commande `npm run build`** et constatez avec émerveillement que la compilation babel se lance !
*Si la compilation ne se lance pas, plusieurs raisons possibles :* <img src="images/readme/npm-run-build.gif" />
- *soit Babel n'est pas correctement installé,*
- *soit la section "scripts" n'est pas correctement formatée (pensez qu'il s'agit d'un fichier JSON, par conséquent l'oubli d'une virgule entre chaque script ou au contraire l'ajout d'une virgule à la fin du dernier script, sont considérés comme des erreurs de syntaxe).* > _**NB :** Si la compilation ne se lance pas, plusieurs raisons sont possibles :_
> - _soit Babel n'est pas correctement installé,_
> - _soit la section "scripts" n'est pas correctement formatée (pensez qu'il s'agit d'un fichier JSON, par conséquent l'oubli d'une **virgule** entre chaque script ou au contraire l'ajout d'une virgule à la fin du dernier script, sont considérés comme des **erreurs** de syntaxe)._
7. **Ajoutez un nouveau script nommé `"watch"`** qui permettra de lancer la commande : 7. **Ajoutez un nouveau script nommé `"watch"`** qui permettra de lancer la commande :
```bash ```bash
./node_modules/.bin/babel js -d build --verbose --watch --source-maps ./node_modules/.bin/babel src -d build --verbose --watch --source-maps
``` ```
Lancez la commande `npm run watch` dans votre terminal et vérifiez que lorsque vous modifiez le fichier `js/main.js`, le fichier `build/main.js` est bien mis à jour. Lancez la commande `npm run watch` dans votre terminal et vérifiez que lorsque vous modifiez le fichier `src/main.js`, le fichier `build/main.js` est bien mis automatiquement à jour.
<img src="images/readme/npm-run-watch.gif" />
Vous voyez que le watch ne vous rend pas la main sur le terminal, il faut le laisser ouvert pour que la recompilation automatique à chaque sauvegarde continue de fonctionner.
## Étape suivante ## Étape suivante <!-- omit in toc -->
Si la compilation fonctionne, vous pouvez passer à l'étape suivante : [B. La POO](B-poo.md) Si la compilation fonctionne, vous pouvez passer à l'étape suivante : [B. La POO](B-poo.md)
\ No newline at end of file
# TP 2 : B. POO <!-- omit in toc --> <img src="images/readme/header-small.jpg" >
# B. POO <!-- omit in toc -->
## Sommaire <!-- omit in toc --> ## Sommaire <!-- omit in toc -->
- [B.1. Rappels de syntaxe](#b1-rappels-de-syntaxe) - [B.1. Rappels de syntaxe](#b1-rappels-de-syntaxe)
- [B.1.1. class & propriétés publiques](#b11-class--propriétés-publiques) - [B.1.1. class & propriétés publiques](#b11-class-propriétés-publiques)
- [B.1.2. méthodes](#b12-méthodes) - [B.1.2. méthodes](#b12-méthodes)
- [B.2. Compiler avec Babel](#b2-compiler-avec-babel) - [B.2. Support des syntaxes modernes](#b2-support-des-syntaxes-modernes)
- [B.3. La classe Component](#b3-la-classe-component) - [B.3. La classe Component](#b3-la-classe-component)
- [B.4. Héritage : La classe Img](#b4-héritage--la-classe-img) - [B.4. Héritage : La classe Img](#b4-héritage-la-classe-img)
- [Étape suivante](#Étape-suivante)
**NB : Dans ce TP vous coderez dans un premier temps vos classes directement dans le fichier `src/main.js` sans passer par des fichiers (modules) séparés.**
***NB* : Dans ce TP vous coderez dans un premier temps vos classes directement dans le fichier `main.js` sans passer par des fichiers (modules) séparés.** Dans la suite du TP on organisera notre code plus proprement en séparant les classes dans des modules différents mais pour le moment on va simplifier la mise en place en remettant ça à plus tard. Dans la suite du TP on organisera notre code plus proprement en séparant les classes dans des modules différents.
Mais pour le moment on va simplifier la mise en place en remettant ça à plus tard (*ne faites pas ça dans la vraie vie !*).
## B.1. Rappels de syntaxe ## B.1. Rappels de syntaxe
### B.1.1. class & propriétés publiques ### B.1.1. class & propriétés publiques
Comme vu dans le cours (*procurez vous le support pdf sur moodle !*) ES6 a introduit une nouvelle syntaxe pour la création de classes. Finis les `prototypes`, désormais le mot clé `class` fait son apparition et permet une d'utiliser syntaxe plus proche de ce qui se fait dans les autres langages objets : Comme vu dans le cours (*procurez vous le support pdf !*) ES6 a introduit une nouvelle syntaxe pour la création de classes. Finis les `prototypes`, désormais le mot clé `class` fait son apparition et permet d'utiliser une syntaxe plus proche de ce qui se fait dans les autres langages objets :
```js ```js
class Animal { class Character {
constructor( name ){ constructor(firstName) { // constructeur de la classe
this.name = name; this.firstName = firstName; // création de propriété
} }
} }
const threeEyedRaven = new Animal( 'Bran' ); const heisenberg = new Character('Walter');
``` ```
La création de propriétés d'instances se fait par l'utilisation du mot clé `this` dans le constructeur : `this.name = name;` permet de créer une propriété `name` sur l'instance en cours de création et de lui assigner la valeur passée au constructeur via l'instruction `new Animal( 'Bran' );`. La création de propriétés d'instances se fait par l'utilisation du mot clé `this` dans le constructeur : `this.firstName = firstName;` permet de créer une propriété `firstName` sur l'instance en cours de création et de lui assigner la valeur passée au constructeur via l'instruction `new Character('Walter');`.
On peut ensuite accéder aux propriétés de l'objet en utilisant la notation pointée : On peut ensuite accéder aux propriétés de l'objet en utilisant la notation pointée :
```js ```js
console.log( threeEyedRaven.name ); console.log( heisenberg.firstName );
``` ```
Il est possible également de déclarer les propriétés d'instance en dehors du constructeur de cette manière : Il est possible également de déclarer les propriétés d'instance en dehors du constructeur de cette manière :
```js ```js
class Animal { class Character {
name; firstName;
constructor( name ){ constructor(firstName) {
this.name = name; this.firstName = firstName;
} }
} }
``` ```
Attention cependant, cette notation n'est pas encore dans la spec officielle d'EcmaScript (la spec suivie par JavaScript) mais a des chances d'être intégrée dans la version 2020 d'EcmaScript (ES11) : cf. https://github.com/tc39/proposal-class-fields et https://tc39.github.io/proposal-class-fields/ Attention cependant, cette notation n'est **pas encore dans la spec officielle** d'EcmaScript (_la spec suivie par JavaScript_) mais a des chances d'être intégrée dans la version 2021 d'EcmaScript (ES12) : cf. https://github.com/tc39/proposal-class-fields et https://tc39.github.io/proposal-class-fields/
Pour pouvoir l'utiliser, il faudra modifier légèrement la configuration de Babel (cf. chapitre suivant). Pour pouvoir l'utiliser, il faudra modifier légèrement la configuration de Babel (cf. chapitre suivant).
### B.1.2. méthodes ### B.1.2. méthodes
La création de méthodes d'une classe se fait de la manière suivante : La création de méthodes d'une classe se fait de la manière suivante :
```js ```js
class Animal { class Character {
constructor( name ){ firstName;
this.name = name; lastName;
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
} }
fly() { // déclaration de méthode fullName(){ // déclaration de méthode
console.log(`${this.name} is flying !`); return `${this.firstName} ${this.lastName}`;
} }
} }
const threeEyedRaven = new Animal( 'Bran' );
``` ```
Pour appeler la méthode, on utilise simplement la notation pointée : Pour appeler la méthode, on utilise simplement la notation pointée :
```js ```js
threeEyedRaven.fly(); heisenberg.fullName();
``` ```
## B.2. Compiler avec Babel ## B.2. Support des syntaxes modernes
Comme vu dans le [chapitre précédent](#B.1.1.-class-&-propriétés-publiques), certaines syntaxes que nous allons utiliser dans le TP ne sont pas encore dans la spec officielle (c'est le cas pour la déclaration de propriétés d'instance en dehors du constructeur, les propriétés ou méthodes privées ou encore les propriétés et méthodes statiques). **Comme vu dans le [chapitre précédent](#b11-class-propriétés-publiques), certaines syntaxes que nous allons utiliser dans le TP ne sont pas encore dans la spec officielle** (_c'est le cas pour la déclaration de propriétés d'instance en dehors du constructeur, les propriétés ou méthodes privées ou encore les propriétés et méthodes statiques_).
Ces fonctionnalités du langages sont dans un stade relativement avancé de discussion (niveau 3 sur 4) et ont désormais de grandes chances d'arriver dans la spécification officielle prochainement. Pas de raison de s'en priver donc. Ces fonctionnalités du langages sont **dans un stade relativement avancé de discussion (niveau 3 sur 4)** et ont désormais de grandes chances d'arriver dans la spécification officielle prochainement. Pas de raison de s'en priver donc.
Pour pouvoir utiliser ces syntaxes, nous allons modifier la configuration de Babel qui par défaut n'est capable de compiler que les syntaxes officielles : Pour pouvoir utiliser ces syntaxes, nous allons **modifier la configuration de Babel** qui par défaut n'est capable de compiler que les syntaxes officielles :
1. **Installez le paquet npm ["@babel/plugin-proposal-class-properties"](https://babeljs.io/docs/en/babel-plugin-proposal-class-properties)** : 1. **Installez le paquet npm ["@babel/plugin-proposal-class-properties"](https://babeljs.io/docs/en/babel-plugin-proposal-class-properties)** :
```bash ```bash
npm install --save-dev @babel/plugin-proposal-class-properties npm install --save-dev @babel/plugin-proposal-class-properties
...@@ -77,78 +84,110 @@ Pour pouvoir utiliser ces syntaxes, nous allons modifier la configuration de Bab ...@@ -77,78 +84,110 @@ Pour pouvoir utiliser ces syntaxes, nous allons modifier la configuration de Bab
"plugins": ["@babel/plugin-proposal-class-properties"] "plugins": ["@babel/plugin-proposal-class-properties"]
} }
``` ```
3. **Stoppez pui relancez la compilation à l'aide de la commande `npm run watch`** et vérifiez qu'aucune erreur n'est remontée dans le terminal. 3. **Stoppez puis relancez la compilation à l'aide de la commande `npm run watch`** et vérifiez qu'aucune erreur n'est remontée dans le terminal.
4. **Codez la classe `Animal` dans le fichier main.js** et vérifiez que la syntaxe employée pour la déclaration de la propriété `name` est correctement prise en compte par le compilateur et que la ligne `threeEyedRaven.fly()` affiche bien le message `Bran is flying !` dans la console. 4. **Copiez-collez le code suivant directement dans le fichier `main.js`** :
```js
class Character {
firstName;
lastName;
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
const heisenberg = new Character('Walter', 'White');
console.log(
heisenberg.firstName,
heisenberg.fullName();
);
```
Vérifiez que la syntaxe employée pour la déclaration des propriétés `firstName` et `lastName` sont correctement prises en compte par le compilateur et que le `console.log` affiche bien les bonnes valeurs dans la console.
***Si c'est bon, vous êtes prêt pour la suite !*** ***Si c'est bon, vous êtes prêt pour la suite !***
## B.3. La classe Component ## B.3. La classe Component
1. Effacez la classe Animal et **créez une classe `Component` ayant les caractéristiques suivantes** : 1. **Effacez le code que vous aviez copié/collé dans l'exercice précédent** (_ne conservez que le tableau `data`_)
+ **le constructeur** prend en paramètre 2. **Dans le fichier `src/main.js` créez une classe `Component` qui s'utilise de la manière suivante :**
- une chaîne nommée `tag` ```js
- une chaîne nommé `children` const title = new Component( 'h1' );
document.querySelector('.pageTitle').innerHTML = title.render();
```
+ **le constructeur** prend en paramètre une chaîne nommée `tagName` pour le moment simplement sauvegardée dans une propriété de l'instance: `this.tagName`
+ **la classe dispose d'une méthode `render()`**.
Ces deux paramètres vont être pour le moment simplement sauvegardés dans des propriétés de l'instance: `this.tag` et `this.children`. Cette méthode retourne une chaîne de caractères au format html qui correspond à une balise dont le type dépend de l'attribut `tagName` passé au constructeur.
+ **la classe dispose d'une méthode `render()`**. Cette méthode retourne une chaîne de caractères au format html qui correspond à une balise du type de l'attribut `tag` passé au constructeur.
Par exemple si `tag` vaut `'div'` alors `render()` retournera la chaîne de caractères : Par exemple si `tagName` vaut `'div'` alors `render()` retournera la chaîne de caractères :
```js ```js
'<div></div>' '<div></div>'
``` ```
Dans notre exemple plus haut, `tagName` vaut `'h1'`, `render()` retourne donc :
Si le paramètre `children` n'est pas vide, le contenu retourné entre les balises ouvrantes et fermantes correspond à la chaîne contenue dans `children`. Par exemple : si `tag` vaut `'div'` et que `children` vaut `'youpi ça marche'` alors `render()` retournera la chaîne :
```js ```js
`<div> '<h1></h1>'
youpi ça marche
</div>`
``` ```
> _**NB :** Je vous conseille d'utiliser les **template strings** dans cette méthode, cela vous permettra facilement d'injecter des valeurs dans votre chaîne et en plus de passer des lignes dans la chaîne de caractères pour rendre votre code plus lisible._
***NB :** Utilisez les template strings pour cette méthode (cf. cours numéro 1) et pensez à passer des lignes dans la chaîne de caractères pour rendre votre code plus lisible.* **Vérifiez que votre classe fonctionne correctement en inspectant le code généré par votre application avec l'Inspecteur d'éléments des devtools du navigateur.**
2. **Pour tester le bon fonctionnement de la classe, instanciez un Component de type `h1`** avec comme `children` la chaîne `'La carte'`. Puis afficher le résultat de l'appel à la méthode `render()` de cette instance dans la balise de classe CSS `'pageTitle'` : 3. **Ajoutez un second paramètre au constructeur, nommé `children`.** Modifiez le code de la méthode render() de manière à ce que le code suivant :
```js ```js
const title = new Component( 'h1', 'La carte' ); const title = new Component( 'h1', 'La carte' );
document.querySelector('.pageTitle').innerHTML = title.render(); document.querySelector('.pageTitle').innerHTML = title.render();
``` ```
Utilisez l'inspecteur d'éléments des devtools du navigateur (clic-droit > inspecter) pour contrôler que le résultat est bien celui attendu, puis contrôlez visuellement que le rendu est bien conforme à la capture suivante : <br><a href="images/readme/pizzaland-01.jpg"><img src="images/readme/pizzaland-01.jpg" width="80%"></a> Injecte dans la page le code suivant :
```js
'<h1>La carte</h1>'
```
Contrôlez que le rendu est bien conforme à la capture suivante :
<img src="images/readme/pizzaland-01.png" >
3. **Modifiez le fonctionnement de la méthode render pour prendre en compte le cas où `children` est vide** (`null` ou `undefined`). Par exemple si je crée un Component de ce style : 3. **Modifiez le fonctionnement de la méthode render pour prendre en compte le cas où `children` est vide** (`null` ou `undefined`). Par exemple si je crée un Component de ce style :
```js ```js
const img = new Component( 'img' ); const img = new Component( 'img' );
``` ```
`render()` doit retourner un code du type : `render()` doit retourner `<img />` et pas `<img></img>` (_car ce n'est pas un code HTML valide selon la spec du W3C_).
```html
<img />
```
et pas
```html
<img></img>
```
(car ce n'est pas un code HTML valide selon la spec du W3C)
4. **Testez à nouveau la classe Component en instanciant cette fois un nouveau Component de type img** : **Testez votre classe comme ceci** :
```js ```js
const img = new Component( 'img' ); const img = new Component( 'img' );
document.querySelector( '.pizzasContainer' ).innerHTML = img.render(); document.querySelector( '.pageContent' ).innerHTML = img.render();
``` ```
Le résultat obtenu doit être le suivant (notez qu'aucune image ne s'affiche -on a juste un rectangle blanc- car on n'a pas précisé ni de source ni de taille à l'image !): <br><a href="images/readme/pizzaland-02.jpg"><img src="images/readme/pizzaland-02.jpg" width="80%"></a> Vérifiez dans **l'inspecteur d'éléments** que votre image est bien ajoutée dans `pageContent`.
> _**NB :** On passe par l'inspecteur d'éléments car visuellement à l'écran, c'est difficile de contrôler le rendu : aucune image ne s'affiche car on n'a pas précisé ni de source ni de taille à l'image !_
> _**NB2 :** Selon votre navigateur il est possible que l'inspecteur d'éléments n'affiche que `<img>` et pas `<img />`. C'est une simplification faite par les devtools, mais ça ne veut pas dire que votre code ne fonctionne pas. Testez donc votre code avec `console.log(img.render())`, là vous saurez avec certitude si votre méthode retourne bien `<img />`._
5. **Ajoutez un paramètre `attribute` en 2e position du constructeur de la classe `Component` : enregistrer ce paramètre dans une propriété d'instance `this.attribute`.** La signature du constructeur sera désormais : 5. **Ajoutez un paramètre `attribute` en 2e position du constructeur de la classe `Component` : enregistrez ce paramètre dans une propriété d'instance `this.attribute`.**
La signature du constructeur sera désormais :
```js ```js
constructor( tagName, attribute, children ) { constructor( tagName, attribute, children ) {
``` ```
6. **Modifiez la méthode `render()` pour prendre en compte le paramètre `attribute`**. On considère que ce paramètre aura toujours la forme d'un objet littéral avec deux propriétés : `name` et `value`. Si le paramètre `attribute` a été fourni au constructeur comme ceci :
**Modifiez la méthode `render()` pour prendre en compte le paramètre `attribute`**. On considère que ce paramètre aura toujours la forme d'un objet littéral avec deux propriétés : `name` et `value`. C'est à dire que si le paramètre `attribute` a été fourni au constructeur comme ceci :
```js ```js
const img = new Component( 'img', {name:'src', value:'https://images.unsplash.com/photo-1532246420286-127bcd803104?fit=crop&w=500&h=300'} ); const img = new Component( 'img', {name:'src', value:'https://images.unsplash.com/photo-1532246420286-127bcd803104?fit=crop&w=500&h=300'} );
``` ```
`render()` doit retourner un code du type :
`render()` doit retourner le code suivant :
```html ```html
<img src="https://images.unsplash.com/photo-1532246420286-127bcd803104?fit=crop&w=500&h=300" /> <img src="https://images.unsplash.com/photo-1532246420286-127bcd803104?fit=crop&w=500&h=300" />
``` ```
*Pour ne pas alourdir trop le code de la méthode render() je vous recommande de créer une nouvelle méthode `renderAttribute()` -appelée dans la méthode `render()`- qui va être en charge du rendu de l'attribut html.* > _**NB :** Pour ne pas alourdir trop le code de la méthode render() je vous recommande de créer une nouvelle méthode `renderAttribute()` -appelée dans la méthode `render()`- qui va être en charge du rendu de l'attribut html._
Testez ce nouveau code, le rendu devra cette fois être :
<img src="images/readme/pizzaland-02.png" >
Testez ce nouveau code, le rendu devra cette fois être :<br><a href="images/readme/pizzaland-03.png"><img src="images/readme/pizzaland-03.png" width="80%"></a>
## B.4. Héritage : La classe Img ## B.4. Héritage : La classe Img
1. **Créez maintenant une nouvelle classe `Img`** qui hérite de `Component` et dont le constructeur s'utilise comme ceci : 1. **Créez maintenant une nouvelle classe `Img`** qui hérite de `Component` et dont le constructeur s'utilise comme ceci :
```js ```js
...@@ -156,9 +195,11 @@ Pour pouvoir utiliser ces syntaxes, nous allons modifier la configuration de Bab ...@@ -156,9 +195,11 @@ Pour pouvoir utiliser ces syntaxes, nous allons modifier la configuration de Bab
``` ```
Testez le résultat de ce composant à l'aide de l'instruction : Testez le résultat de ce composant à l'aide de l'instruction :
```js ```js
document.querySelector( '.pizzasContainer' ).innerHTML = img.render(); document.querySelector( '.pageContent' ).innerHTML = img.render();
``` ```
Le rendu doit être identique à la capture précédente : <br><a href="images/readme/pizzaland-03.png"><img src="images/readme/pizzaland-03.png" width="80%"></a> Le rendu doit être identique à la capture précédente :
<img src="images/readme/pizzaland-02.png" >
## Étape suivante ## Étape suivante <!-- omit in toc -->
Si vous avez terminé cette partie sur la POO, il est l'heure de mettre en place les modules dans la partie suivante : [C. Les modules](C-modules.md). Si vous avez terminé cette partie sur la POO, il est l'heure de mettre en place les modules dans la partie suivante : [C. Les modules](C-modules.md).
\ No newline at end of file
# TP 2 : C. Les modules <!-- omit in toc --> <img src="images/readme/header-small.jpg" >
# C. Les modules <!-- omit in toc -->
## Sommaire <!-- omit in toc --> ## Sommaire <!-- omit in toc -->
- [C.1. Rappels](#c1-rappels) - [C.1. Rappels](#c1-rappels)
- [C.2. Support natif dans les navigateurs modernes](#c2-support-natif-dans-les-navigateurs-modernes) - [C.2. Support natif dans les navigateurs modernes](#c2-support-natif-dans-les-navigateurs-modernes)
- [C.3. Rendre les modules compatibles avec les vieux navigateurs](#c3-rendre-les-modules-compatibles-avec-les-vieux-navigateurs) - [C.3. Rendre les modules compatibles avec les vieux navigateurs](#c3-rendre-les-modules-compatibles-avec-les-vieux-navigateurs)
- [C.4. mode dev vs mode prod](#c4-mode-dev-vs-mode-prod) - [C.4. mode dev vs mode prod](#c4-mode-dev-vs-mode-prod)
- [Étape suivante](#Étape-suivante)
## C.1. Rappels ## C.1. Rappels
**Comme vu en cours, le système de modules ES6 permet de répartir son code dans plusieurs fichiers et de gérer les dépendances de l'application fichier par fichier** (plutôt que d'avoir à maintenir une longue liste de balises `<script>` dans le fichier html). **Comme vu en cours, le système de modules ES6 permet de répartir son code dans plusieurs fichiers et de gérer les dépendances de l'application fichier par fichier** (_plutôt que d'avoir à maintenir une longue liste de balises `<script>` dans le fichier html_).
Par exemple, si l'on a deux fichiers `main.js` et `hodor.js`, on peut partager une variable de l'un à l'autre grâce aux instructions `import` et `export` : Par exemple, si l'on a deux fichiers `main.js` et `vehicle.js`, on peut partager une variable de l'un à l'autre grâce aux instructions `import` et `export` :
```js ```js
// hodor.js // vehicle.js
const character = 'Hodor'; const vehicle = 'the RV';
export default character; export default vehicle;
``` ```
```js ```js
// main.js // main.js
import character from "./hodor.js"; import vehicle from './vehicle.js';
console.log( character ); // 'Hodor' console.log( vehicle ); // 'The RV'
``` ```
Le [support navigateur des modules ES6](https://caniuse.com/#feat=es6-module) est plutôt bon mais encore un peu juste pour des applications grand public *(absence de support sur IE et android 4.4)*. Le [support navigateur des modules ES6](https://caniuse.com/#feat=es6-module) est plutôt bon mais encore un peu juste pour des applications grand public *(absence de support sur IE et android 4.4)*.
<a href="http://caniuse.com/#feat=es6-module"> <a href="http://caniuse.com/#feat=es6-module">
<picture> <picture>
<source type="image/webp" srcset="https://caniuse.bitsofco.de/static/v1/es6-module-1581268872568.webp"> <source type="image/webp" srcset="https://caniuse.bitsofco.de/image/es6-module.webp">
<img src="https://caniuse.bitsofco.de/static/v1/es6-module-1581268872568.png" alt="Data on support for the es6-module feature across the major browsers from caniuse.com"> <source type="image/png" srcset="https://caniuse.bitsofco.de/image/es6-module.png">
<img src="https://caniuse.bitsofco.de/image/es6-module.jpg" alt="Data on support for the es6-module feature across the major browsers from caniuse.com">
</picture> </picture>
</a> </a>
Dans un premier temps nous ferons abstraction de ces questions de compatibilité et nous nous appuierons sur le fait que **les dernières versions de Chromium/Chrome et de FireFox supportent nativement les modules ES6**.<br>Nous verrons plus tard dans le TP comment rendre nos modules compatibles avec les vieux navigateurs grâce à Webpack. Dans un premier temps nous ferons abstraction de ces questions de compatibilité et nous nous appuierons sur le fait que **les dernières versions de Chromium/Chrome et de FireFox supportent nativement les modules ES6**.
Nous verrons plus tard dans le TP comment rendre nos modules compatibles avec les vieux navigateurs grâce à Webpack.
## C.2. Support natif dans les navigateurs modernes ## C.2. Support natif dans les navigateurs modernes
1. **Avant d'utiliser le système de modules et les instructions `import`/`export`, il faut d'abord indiquer au navigateur que notre fichier `main.js` est lui-même un module.** Pour cela, ajouter un attribut `type="module"` dans la balise `<script>` du fichier `index.html` : 1. **Avant d'utiliser le système de modules et les instructions `import`/`export`, il faut d'abord indiquer au navigateur que notre fichier `main.js` est lui-même un module.** Pour cela, ajoutez un attribut `type="module"` dans la balise `<script>` du fichier `index.html` :
```html ```html
<script type="module" src="build/main.js"></script> <script type="module" src="build/main.js"></script>
``` ```
Vous noterez que l'attribut `"defer"` n'est plus nécessaire car il est implicite pour les modules ! > _**NB :** Vous noterez que l'attribut `"defer"` n'est plus nécessaire car il est implicite pour les modules !_
2. **Il faut ensuite configurer Babel.** En effet, par défaut Babel va chercher à compiler toutes les instructions `import` et `export` qu'il trouvera pour les transformer en code compatible ES5 (mais qui nécessite l'emploi de librairies supplémentaires). Ici on veut utiliser le support natif du navigateur pour les modules ES6, par conséquent il faut indiquer à Babel de ne pas compiler les `import`/`export`.<br>
Modifiez le fichier `.babelrc` comme suit (notez le tableau dans un tableau !) : 2. **Il faut ensuite configurer Babel.** En effet, par défaut Babel va chercher à compiler toutes les instructions `import` et `export` qu'il trouvera pour les transformer en code compatible ES5. Ici on veut utiliser le support natif du navigateur pour les modules ES6, par conséquent il faut indiquer à Babel de ne pas compiler les `import`/`export`.<br>
Modifiez le fichier `.babelrc` comme suit (**attention: notez bien le tableau dans un tableau !**) :
```json ```json
{
"presets": [ "presets": [
["@babel/env", {"modules": false}] ["@babel/env", {"modules": false}]
], ],
"plugins": ["@babel/plugin-proposal-class-properties"]
}
``` ```
Pour prendre en compte la nouvelle configuration de Babel, stoppez (<kbd>CTRL</kbd>+<kbd>C</kbd>) puis relancez la compilation à l'aide de la commande `npm run watch` Pour prendre en compte la nouvelle configuration de Babel, **stoppez (<kbd>CTRL</kbd>+<kbd>C</kbd>) puis relancez** la compilation à l'aide de la commande `npm run watch`
1. **Passez enfin la variable `data` ainsi que les classes `Component`, `Img` dans des modules ES6 distincts** (`js/data.js`, `js/components/Component.js` et `js/components/Img.js`). <br> 1. **Passez enfin la constante `data` ainsi que les classes `Component` et `Img` dans des modules ES6 distincts** (`src/data.js`, `src/components/Component.js` et `src/components/Img.js`).
***Rappelez vous :** tout ce qui est défini dans un module (variables, fonctions, classes), n'existe qu'à l'intérieur de ce module **SAUF** s'il est exporté, puis importé dans un autre fichier.*
Le fichier `main.js` conservera uniquement : Le fichier `main.js` conservera uniquement :
- l'instanciation et l'affichage (`render()`) du composant de titre - l'instanciation et l'affichage (`render()`) du composant de titre
- l'instanciation et l'affichage (`render()`) de l'image - l'instanciation et l'affichage (`render()`) de l'image
***NB1 :** Exporter par défaut une constante sur la même ligne que sa création est interdit (voir la bible [stackoverflow](https://stackoverflow.com/a/36261387)):* <br>
```js
export default const data = [...]; // ERREUR ! > _**NB1 :** Rappelez vous : tout ce qui est défini dans un module (variables, fonctions, classes), n'existe qu'à l'intérieur de ce module **SAUF** s'il est exporté, puis importé dans un autre fichier._
```
*Il faut obligatoirement faire cela en deux étapes :* > _**NB2 :** Exporter **par défaut** une constante sur la même ligne que sa création est interdit (cf. la Bible : [stackoverflow](https://stackoverflow.com/a/36261387)):_
```js > ```js
const data = [...]; > export default const data = [...]; // ERREUR !
export default data; // OK ! > ```
``` > _Il faut obligatoirement faire cela en deux étapes :_
***NB2 :** Un export simple (pas par défaut) est en revanche autorisé :* > ```js
```js > const data = [...];
export const data = [...]; // OK ! > export default data; // OK !
``` > ```
***NB3 :** Cette restriction ne s'applique pas aux fonctions et aux classes ; on peut tout à fait faire :*
```js > _**NB3 :** Un export simple (pas par défaut) d'une const est en revanche autorisé :_
export default class Component {...} // OK ! > ```js
``` > export const data = [...]; // OK !
```js > ```
export default function checkValue(value){...} // OK aussi !
```
2. **Compilez votre code et testez la page dans le navigateur** : le résultat doit être identique à celui obtenu précédemment :<br><a href="images/readme/pizzaland-03.png"><img src="images/readme/pizzaland-03.png" width="80%"></a> > _**NB4 :** Cette restriction ne s'applique pas aux fonctions et aux classes ; on peut tout à fait faire :_
> ```js
> export default class Component {...} // OK !
> ```
> ```js
> export default function checkValue(value){...} // OK aussi !
> ```
3. **Ouvrez l'onglet Réseau/Network des devtools, vous devez normalement voir le chargement automatique des différents modules** (une ligne par fichier JS) :<br><a href="images/readme/pizzaland-06.png"><img src="images/readme/pizzaland-06.png" width="80%"></a> 2. **Compilez votre code et testez la page dans le navigateur** : le résultat doit être identique à celui obtenu précédemment :
<img src="images/readme/pizzaland-02.png" >
5. **Ouvrez l'onglet Réseau/Network des devtools, vous devez normalement voir le chargement automatique des différents modules** (_une ligne par fichier JS_)
<img src="images/readme/modules-network.png" />
## C.3. Rendre les modules compatibles avec les vieux navigateurs ## C.3. Rendre les modules compatibles avec les vieux navigateurs
**Pour rendre nos modules compatibles avec les anciens navigateurs, il faut utiliser un "bundler".** <br> **Pour rendre nos modules compatibles avec les anciens navigateurs, il faut utiliser un "bundler".**
Le but d'un "bundler" est de rassembler tous les scripts de notre application (toutes les dépendances) en un seul gros fichier JS. Le navigateur n'ayant plus alors qu'un seul fichier à charger, il n'a plus à se soucier des modules. Le but d'un "bundler" est de rassembler tous les scripts de notre application (toutes les dépendances) en un seul gros fichier JS. Le navigateur n'ayant plus alors qu'un seul fichier à charger, il n'a plus à se soucier des modules.
Comme vu en cours, le bundler le plus employé en JS est [Webpack](https://webpack.js.org/), c'est donc cet outil que l'on va installer et configurer. Comme vu en cours, le bundler le plus employé en JS est [Webpack](https://webpack.js.org/), c'est donc cet outil que l'on va installer et configurer.
1. **Dans le fichier `index.html`, retirez l'attribut `type="module"` de la balise script et remettez l'attribut `defer`. Remplacez aussi le nom du fichier `build/main.js` par `build/main.bundle.js`.** C'est en effet une pratique courante de nommer les fichiers de ce type avec le mot "bundle". 1. **Dans le fichier `index.html`, retirez l'attribut `type="module"` de la balise script et remettez l'attribut `defer`. Remplacez aussi le nom du fichier `build/main.js` par `build/main.bundle.js`** (_c'est en effet une pratique courante de nommer les fichiers de ce type avec le mot "bundle"_).
2. **Installez webpack** à l'aide de la commande suivante (*prenez garde à la lancer à la racine de votre TP, là où se trouve le fichier `package.json`*): 2. **Installez webpack** à l'aide de la commande suivante (_prenez garde à la lancer à la racine de votre TP, là où se trouve le fichier `package.json`_):
```bash ```bash
npm install --save-dev webpack webpack-cli@3.x babel-loader npm install --save-dev webpack webpack-cli babel-loader
``` ```
3. **Créez à la racine de votre TP un fichier `webpack.config.js`** (au même niveau que le `package.json` et le `.babelrc`) et placez y le code suivant : 3. **Créez à la racine de votre TP un fichier `webpack.config.js`** (au même niveau que le `package.json` et le `.babelrc`) et placez y le code suivant :
```js ```js
...@@ -99,12 +117,14 @@ Comme vu en cours, le bundler le plus employé en JS est [Webpack](https://webpa ...@@ -99,12 +117,14 @@ Comme vu en cours, le bundler le plus employé en JS est [Webpack](https://webpa
module.exports = { module.exports = {
// Fichier d'entrée : // Fichier d'entrée :
entry: './js/main.js', entry: './src/main.js',
// Fichier de sortie : // Fichier de sortie :
output: { output: {
path: path.resolve(__dirname, './build'), path: path.resolve(__dirname, './build'),
filename: 'main.bundle.js' filename: 'main.bundle.js'
}, },
// compatibilité anciens navigateurs (si besoin du support de IE11 ou android 4.4)
target: ['web', 'es5'],
// connexion webpack <-> babel : // connexion webpack <-> babel :
module: { module: {
rules: [ rules: [
...@@ -121,14 +141,16 @@ Comme vu en cours, le bundler le plus employé en JS est [Webpack](https://webpa ...@@ -121,14 +141,16 @@ Comme vu en cours, le bundler le plus employé en JS est [Webpack](https://webpa
} }
``` ```
4. **Modifiez les scripts `"build"` et `"watch"` du fichier `package.json` pour replacer babel par webpack** (*notez quand même que babel sera toujours utilisé en arrière plan par webpack*): 4. **Modifiez les scripts `"build"` et `"watch"` du fichier `package.json` pour replacer babel par webpack** (_notez quand même que **babel sera toujours utilisé mais en arrière plan** par webpack grâce au `webpack.config.js` que l'on vient d'écrire_):
```json ```json
"build": "webpack --mode=production", "build": "webpack --mode=production",
"watch": "webpack --mode=development --watch" "watch": "webpack --mode=development --watch"
``` ```
5. **Lancez la compilation** : stoppez le watch précédent (<kbd>CTRL</kbd>+<kbd>C</kbd>), effacez tout le contenu du dossier `build` et relancez la compilation à l'aide de la commande `npm run watch` (*qui lancera cette fois webpack et plus Babel*) 5. **Lancez la compilation** : stoppez le watch précédent (<kbd>CTRL</kbd>+<kbd>C</kbd>), effacez tout le contenu du dossier `build` et relancez la compilation à l'aide de la commande `npm run watch` (*qui lancera cette fois webpack et non plus Babel*)
6. **Enfin, vérifiez dans le navigateur que la page s'affiche toujours** et que dans l'onglet "Réseau"/"Network" vous n'avez maintenant bien plus qu'un seul fichier JS téléchargé par le navigateur : le `build/main.bundle.js`
6. **Enfin, vérifiez dans le navigateur que la page s'affiche toujours** et que dans l'onglet "Réseau"/"Network" vous n'avez maintenant bien plus qu'un seul fichier JS téléchargé par le navigateur : le `build/main.bundle.js`<a href="images/readme/pizzaland-07.png"><img src="images/readme/pizzaland-07.png" width="80%"></a> <img src="images/readme/modules-network-bundle.png" />
## C.4. mode dev vs mode prod ## C.4. mode dev vs mode prod
...@@ -137,7 +159,7 @@ Vous l'aurez peut-être remarqué, les deux scripts que l'on vient d'ajouter au ...@@ -137,7 +159,7 @@ Vous l'aurez peut-être remarqué, les deux scripts que l'on vient d'ajouter au
1. **Renommez** le fichier `build/main.bundle.js` en `build/main.bundle.dev.js` 1. **Renommez** le fichier `build/main.bundle.js` en `build/main.bundle.dev.js`
2. Stoppez la commande `npm run watch` (<kbd>CTRL</kbd>+<kbd>C</kbd>) et **lancez à la place la commande `npm run build`**. 2. Stoppez la commande `npm run watch` (<kbd>CTRL</kbd>+<kbd>C</kbd>) et **lancez à la place la commande `npm run build`**.
3. **Comparez** le fichier `main.bundle.js` généré avec le mode "production" et le `main.bundle.dev.js` qui avait été généré en mode "development". A votre avis, quelle est l'utilité du mode "production" ? 3. **Comparez** le fichier `main.bundle.js` généré avec le mode "production" et le `main.bundle.dev.js` qui avait été généré en mode "development". A votre avis, quelle est l'utilité du mode "production" ?
4. **Demandez au professeur qui encadre votre séance TP si vous avez vu juste avant de passer à la suite.** 4. **Demandez au formateur qui encadre votre séance TP si vous avez vu juste avant de passer à la suite.**
## Étape suivante ## Étape suivante <!-- omit in toc -->
Maintenant que les modules sont en place, on va passer à un peu de POO avancée : [D. POO avancée](D-poo-avancee.md) Maintenant que les modules sont en place, on va passer à un peu de POO avancée : [D. POO avancée](D-poo-avancee.md)
# TP 2 : D. POO avancée <!-- omit in toc --> <img src="images/readme/header-small.jpg" >
# D. POO avancée <!-- omit in toc -->
## Sommaire <!-- omit in toc --> ## Sommaire <!-- omit in toc -->
- [D.1. *Composition :* La classe PizzaThumbnail](#d1-composition--la-classe-pizzathumbnail) - [D.1. _Composition :_ La classe PizzaThumbnail](#d1-composition-la-classe-pizzathumbnail)
- [D.2. La classe HomePage](#d2-la-classe-homepage) - [D.2. _Composition :_ La classe PizzaList](#d2-composition-la-classe-pizzalist)
- [D.3. *Propriétés et méthodes statiques :* La classe PageRenderer](#d3-propriétés-et-méthodes-statiques--la-classe-pagerenderer) - [D.3. _Propriétés et méthodes statiques :_ La classe Router](#d3-propriétés-et-méthodes-statiques-la-classe-router)
- [D.4. *Setter & Getter :* La propriété `pizzas`](#d4-setter--getter--la-propriété-pizzas) - [D.3.1 Rappels de syntaxe](#d31-rappels-de-syntaxe)
- [Étape suivante](#Étape-suivante) - [D.3.2. La classe `Router`](#d32-la-classe-router)
- [D.4. _Private, Setter & Getter :_ La propriété `pizzaList.pizzas`](#d4-private-setter-getter-la-propriété-pizzalistpizzas)
- [D.4.1. Rappels propriétés privées](#d41-rappels-propriétés-privées)
- [D.4.2. Rappels getters/setters](#d42-rappels-getterssetters)
- [D.4.3. Mise en oeuvre](#d43-mise-en-oeuvre)
## D.1. _Composition :_ La classe PizzaThumbnail
_**L'objectif de cet exercice est d'utiliser une classe à l'intérieur d'une autre. On va se servir de la classe `Img` développée précédemment à l'intérieur d'un nouveau composant : `PizzaThumbnail`.**_
## D.1. *Composition :* La classe PizzaThumbnail
1. **Modifiez le code de la méthode `render()`** de la classe `Component` pour lui permettre de recevoir dans le paramètre `children` : 1. **Modifiez le code de la méthode `render()`** de la classe `Component` pour lui permettre de recevoir dans le paramètre `children` :
- soit une **chaîne de caractères** (comme c'est déjà le cas actuellement) - soit une **chaîne de caractères** (comme c'est déjà le cas actuellement)
- soit un **tableau de chaînes de caractères.** <br>Par exemple : si `tag` vaut `"div"` et que `children` vaut `[ "youpi", "ça", "marche" ]` alors `render()` retournera la chaîne `"<div>youpiçamarche</div>"`. - soit un **tableau de chaînes de caractères.** <br>Par exemple : si `tag` vaut `"div"` et que `children` vaut `[ "youpi", "ça", "marche" ]` alors `render()` retournera la chaîne `"<div>youpiçamarche</div>"`.
***NB :** Pour ne pas alourdir trop le code de la méthode `render()` et pour avoir un code plus lisible, passez le code de rendu des enfants, dans une méthode `renderChildren()`.* > _**NB :** Pour ne pas alourdir trop le code de la méthode `render()` et pour avoir un code plus lisible, passez le code de rendu des enfants, dans une méthode `renderChildren()`._
Pour tester si `children` est un tableau (classe `Array`), vous pouvez utiliser l'opérateur `instanceof` cf. https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Op%C3%A9rateurs/instanceof > _**NB2 :** Pour tester si `children` est un tableau (classe `Array`), vous pouvez utiliser l'opérateur `instanceof` cf. https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Op%C3%A9rateurs/instanceof_
Testez votre classe avec le code suivant : Testez votre classe avec le code suivant :
```js ```js
...@@ -23,40 +32,40 @@ ...@@ -23,40 +32,40 @@
``` ```
2. **Adaptez la méthode `render()`** pour permettre de passer dans `children` non seulement des chaînes de caractères comme c'est déjà le cas mais aussi **d'autres `Component`**, comme ceci : 2. **Adaptez la méthode `render()`** pour permettre de passer dans `children` non seulement des chaînes de caractères comme c'est déjà le cas mais aussi **d'autres `Component`**, comme ceci :
```js
const c = new Component( 'span', null, [
'JS',
new Component( 'strong', null, 'FTW' )
] );
```
Pour cela, la méthode `renderChildren()` devra maintenant tester le type de chaque enfant :
- si cet enfant est lui-même une instance de Component, on fait alors appel à la méthode `render()` du `Component` enfant (petit indice : ça ressemble au concept de "récursivité" )
- si l'enfant est une chaîne de caractères, alors la chaîne est ajoutée telle qu'elle, comme auparavant
Pour tester si un enfant est de la classe `Component`, vous pouvez là aussi utiliser l'opérateur `instanceof` cf. https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Op%C3%A9rateurs/instanceof
Si votre code fonctionne correctement, le code suivant :
```js ```js
const c = new Component( const c = new Component(
'article', 'article',
{name:'class', value:'media'}, {name:'class', value:'pizzaThumbnail'},
[ [
new Img('https://images.unsplash.com/photo-1532246420286-127bcd803104?fit=crop&w=500&h=300'), new Img('https://images.unsplash.com/photo-1532246420286-127bcd803104?fit=crop&w=500&h=300'),
'Regina' 'Regina'
] ]
); );
document.querySelector( '.pizzasContainer' ).innerHTML = c.render(); document.querySelector( '.pageContent' ).innerHTML = c.render();
``` ```
doit afficher la page suivante :<br><a href="images/readme/pizzaland-04.png"><img src="images/readme/pizzaland-04.png" width="80%"></a> Pour cela, la méthode `renderChildren()` devra maintenant tester le type de chaque enfant :
- si cet enfant est lui-même une instance de Component, on fait alors appel à la méthode `render()` du `Component` enfant (petit indice : ça ressemble quand même beaucoup au concept de "récursivité"...)
- si l'enfant est une chaîne de caractères, alors la chaîne est ajoutée telle qu'elle, comme auparavant
> **NB :** Pour tester si un enfant est de la classe `Component`, vous pouvez là aussi utiliser l'opérateur [`instanceof`](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Op%C3%A9rateurs/instanceof)_
Si votre code fonctionne correctement, vous devez avoir le rendu suivant :
3. **Créez une classe `PizzaThumbnail`** (dans `js/components/PizzaThumbnail.js`) qui hérite de la classe `Component` : <img src="images/readme/pizzaland-03.png" >
+ le constructeur prend en paramètre un objet nommé `pizza` dont le format correspond à celui d'un élément du tableau `data` (cad. propriétés `nom`, `base`, `prix_petite`, `prix_grande` et `image`)
+ la méthode `render()` retourne un code du type : 3. **Créez une classe `PizzaThumbnail`** (dans `src/components/PizzaThumbnail.js`) qui hérite de la classe `Component` et qui utilise **obligatoirement** la classe **`Img`** pour rendre son code HTML.
Avec le code JS suivant :
```js
const pizzaThumbnail = new PizzaThumbnail(data[0]);
document.querySelector( '.pageContent' ).innerHTML = pizzaThumbnail.render();
```
... le code HTML généré sera :
```html ```html
<article class="media"> <article class="pizzaThumbnail">
<a href="https://images.unsplash.com/photo-1532246420286-127bcd803104?fit=crop&w=500&h=300"> <a href="https://images.unsplash.com/photo-1532246420286-127bcd803104?fit=crop&w=500&h=300">
<img src="https://images.unsplash.com/photo-1532246420286-127bcd803104?fit=crop&w=500&h=300" /> <img src="https://images.unsplash.com/photo-1532246420286-127bcd803104?fit=crop&w=500&h=300" />
<section class="infos"> <section>
<h4>Regina</h4> <h4>Regina</h4>
<ul> <ul>
<li>Prix petit format : 6.50 €</li> <li>Prix petit format : 6.50 €</li>
...@@ -66,47 +75,125 @@ ...@@ -66,47 +75,125 @@
</a> </a>
</article> </article>
``` ```
+ la classe `PizzaThumbnail` devra utiliser la classe `Img` pour le rendu de l'image de la pizza. ... et le rendu obtenu dans la page sera celui-ci :
+ Testez la classe `PizzaThumbnail` avec le code suivant :
<img src="images/readme/pizzaland-04.png" >
## D.2. _Composition :_ La classe PizzaList
_**Après un exemple simple de composition, attaquons nous à un cas plus complexe : celui de la `PizzaList`.**_
Cette classe va nous permettre d'afficher plusieurs vignettes côte à côte grâce au composant `PizzaThumbnail` créé précédemment.
**Créez la classe `PizzaList` dans le fichier `src/pages/PizzaList.js`** :
- cette classe hérite de `Component`
- instanciez-la dans `src/main.js` comme ceci :
```js ```js
const pt = new PizzaThumbnail(data[0]); // `data` est le tableau défini dans `src/data.js`
document.querySelector( '.pizzasContainer' ).innerHTML = pt.render(); const pizzaList = new PizzaList(data);
document.querySelector( '.pageContent' ).innerHTML = pizzaList.render();
``` ```
<br><a href="images/readme/pizzaland-04b.png"><img src="images/readme/pizzaland-04b.png" width="80%"></a> - pour chaque cellule du tableau `data`, le composant `PizzaList` créera un composant `PizzaThumbnail` associé
- le code HTML retourné par la méthode `pizzaList.render()` sera une balise `<section class="pizzaList">` dans laquelle sera injectée la combinaison du `render()` de chaque `PizzaThumbnail`
> _**NB :** en théorie, un simple override du constructor et l'utilisation de la méthode [Array.map](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/map) doivent suffire !_
## D.2. La classe HomePage Le résultat attendu est le suivant :
1. **Créez une classe `HomePage` dans le fichier `js/pages/HomePage.js` qui hérite de `Component`** : <img src="images/readme/pizzaland-05.png" >
- le constructeur recevra en paramètre un tableau de pizzas
- pour chaque cellule du tableau, le composant `HomePage` créera un composant `PizzaThumbnail` associé
- le code HTML retourné par la méthode `render()` sera une balise `<section>` dans laquelle sera affichée la combinaison du `render()` de chaque `PizzaThumbnail`
***NB:** en théorie, un simple override du constructor et l'utilisation de la méthode [Array.map](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/map) doivent suffire !* ## D.3. _Propriétés et méthodes statiques :_ La classe Router
Le résultat attendu est le suivant :<br><a href="images/readme/pizzaland-05.png"><img src="images/readme/pizzaland-05.png" width="80%"></a> Dans cet exercice, je vous propose de développer une classe `Router` qui, à l'aide des **propriétés et méthodes statiques**, va gérer l'affichage à la fois du titre de la page, et de son contenu.
## D.3. *Propriétés et méthodes statiques :* La classe PageRenderer ### D.3.1 Rappels de syntaxe
1. Créez une classe `Page` (dans un module `js/pages/Page.js`) : Pour rappel les propriétés et méthodes statiques peuvent se déclarer à l'aide du mot clé `static`. Ces propriétés/méthodes sont définies et s'utilisent au niveau de la classe (et pas de l'instance) comme ceci :
- qui hérite de `Component`
- dont le constructeur reçoit comme paramètres: title (string) et children ```js
- `title` est enregistré dans une propriété d'instance privée class Counter {
- la classe dispose d'une méthode `renderTitle()` qui retourne le titre passé au constructeur entouré d'une balise `<h1>...</h1>` static counter = 0;
2. Modifiez la classe `HomePage` pour la faire hériter de `Page`. Son titre sera `'La carte'`. static getCounter() {
3. Créez une classe `PageRenderer` (dans un module `js/PageRenderer.js`) avec : return this.counter++;
}
}
console.log(
Counter.getCounter(), // 0
Counter.counter, // 1
Counter.getCounter(), // 1
Counter.counter, // 2
);
```
### D.3.2. La classe `Router`
1. **Créez une classe `Router`** (dans un module `src/Router.js`) avec :
- une propriété statique `titleElement` - une propriété statique `titleElement`
- une propriété statique `contentElement` - une propriété statique `contentElement`
- une méthode statique `renderPage( page )` qui affiche dans `titleElement` le résultat de l'appel à la méthode `page.renderTitle()` et dans contentElement le résultat de l'appel à la méthode `page.render()` de la page passée en paramètre. - une propriété statique `routes`
4. Assigner à `titleElement` : `document.querySelector('.pageTitle')` et à `contentElement` : `document.querySelector( '.pizzasContainer' )` 2. **Dans le `src/main.js`, renseignez les valeurs de `titleElement`, `contentElement` et `routes` comme ceci :**
5. Afficher la `HomePage` grâce à la classe `PageRenderer`
## D.4. *Setter & Getter :* La propriété `pizzas`
A l'aide des propriétés privées, des getters et des setters, faire en sorte que le code suivant permette d'afficher la liste des pizzas :
```js ```js
const homePage = new HomePage([]); Router.titleElement = document.querySelector('.pageTitle');
PageRenderer.renderPage( homePage ); // affiche une page vide Router.contentElement = document.querySelector('.pageContent');
homePage.pizzas = data; Router.routes = [
PageRenderer.renderPage( homePage ); // affiche la liste des pizzas {path: '/', page: pizzaList, title: 'La carte'}
];
``` ```
5. **Enfin développez une méthode statique `Router.navigate(path)` qui permette d'afficher la `PizzaList` et son titre comme ceci :**
```js
Router.navigate('/'); // affiche 'La carte' dans .pageTitle
// et la pizzaList dans .pageContent
```
## D.4. _Private, Setter & Getter :_ La propriété `pizzaList.pizzas`
Dans cet exercice je vous propose d'utiliser plusieurs syntaxes supplémentaires: les **propriétés et méthodes privées** ainsi que les **getters/setters**.
### D.4.1. Rappels propriétés privées
Pour déclarer et utiliser des propriétés ou méthodes privées il suffit de les préfixer du caractère `'#'`comme ceci :
```js
class Character {
firstName;
#canCook = false; // propriété privée (#)
constructor(firstName) {
this.firstName = firstName;
this.#canCook = (firstName === 'Walter');
}
}
```
Le support des propriétés et méthodes privées est en stage 3/4 de spécification. Ce n'est donc pas encore dans la spec EcmaScript officielle. Néanmoins il est possible de les utiliser grâce à au plugin Babel [@babel/plugin-proposal-class-properties](https://babeljs.io/docs/en/babel-plugin-proposal-class-properties) que l'on a déjà installé précédemment (c'est le même que pour les propriétés publiques).
> _**NB :** Si vous vous demandez pourquoi on écrit `#propriete` et pas `private propriete` comme dans d'autres langages, la réponse se trouve ici :_ https://github.com/tc39/proposal-class-fields/blob/master/PRIVATE_SYNTAX_FAQ.md#why-arent-declarations-private-x
## Étape suivante ### D.4.2. Rappels getters/setters
Si vous êtes arrivés ici, bravo, il vous reste à voir comment on peut renforcer le typage du code notre application dans la partie suivante: [E. Le typage](E-typage.md) Vous pouvez déclarer des getter et des setters de la façon suivante :
\ No newline at end of file
```js
class Character {
#firstName;
set firstName(value) {
console.log(value);
this.#firstName = value.toLowerCase();
}
}
```
Ce sont en fait des méthodes qui se "déguisent" en propriétés. Pour utiliser le setter écrit au dessus, on peut faire simplement :
```js
heisenberg.firstName = 'Walter';
```
On a l'impression d'utiliser une propriété, mais en réalité c'est une méthode qui est déclenchée, et donc le `console.log(value)` va s'exécuter.
### D.4.3. Mise en oeuvre
A l'aide des propriétés privées, des getters et des setters, faire en sorte que le code suivant permette d'afficher la liste des pizzas :
```js
const pizzaList = new PizzaList([]);
Router.routes = [{ path: '/', page: pizzaList, title: 'La carte' }];
Router.navigate('/'); // affiche une page vide
pizzaList.pizzas = data;
Router.navigate('/'); // affiche la liste des pizzas
```
# TP 2 : E. typage <!-- omit in toc -->
## Sommaire <!-- omit in toc -->
- [E.1 Installation et configuration](#e1-installation-et-configuration)
- [E.2. Premiers tests](#e2-premiers-tests)
- [E.3. intégration avec Babel](#e3-intégration-avec-babel)
- [E.4. Intégration dans VSCodium](#e4-intégration-dans-vscodium)
- [E.5. Typer notre code](#e5-typer-notre-code)
Comme vu en cours, il existe plusieurs solutions pour ajouter du typage statique dans notre code JS.
Celle que nous allons employer aujourd'hui se base sur [Flow](https://flow.org/).
## E.1 Installation et configuration
1. **Installez Flow (attention, cette commande est bien à lancer dans votre dossier de travail, là où se trouvent les fichiers index.html, package.json, .babelrc, etc.) :**
```bash
npm install --save-dev flow-bin
```
2. **Initialisez flow à l'aide de la commande suivante :**
```bash
./node_modules/.bin/flow init
```
5. **Ajoutez un script pour lancer flow dans le fichier `package.json`:**
```json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --mode=production",
"watch": "webpack --mode=development --watch",
"flow": "flow"
},
```
lancez ensuite dans un terminal intégré à VSCodium :
```bash
npm run flow
```
Vous devriez avoir comme résultat final :
```bash
No errors!
```
En effet, pour le moment nos fichiers ne contiennent aucune information de typage !
## E.2. Premiers tests
1. **Dans le fichier `js/main.js` ajoutez le code suivant (au début du fichier):**
```js
// @flow
const i:number = '12';
```
2. **Relancez la commande `npm run flow`** (*pensez à stopper le `watch`précédent s'il tournait toujours <kbd>CTRL</kbd>+<kbd>C</kbd>)*. Cette fois plusieurs erreurs doivent apparaître dans votre terminal :
<br><a href="images/flow-error.jpg"><img src="images/flow-error.jpg" width="80%"></a>
On a en réalité 3 erreurs :
- la première concerne notre ligne `const i:number = '12';` ce qui est normal puisqu'on a volontairement tenté d'assigner une chaîne de caractères dans une variable typée "number". On peut supprimer cette ligne.
- Les deux autres erreurs nous amènent à comprendre en quoi le typage des variables peut nous aider à avoir un code plus robuste... En effet l'erreur est la suivante :
```bash
Cannot assign title.render() to document.querySelector(...).innerHTML because property innerHTML is missing in null [1].
```
Ce que nous apprend cette ligne, c'est que la méthode `document.querySelector()` peut dans certains cas nous retourner une valeur vide (`null`). C'est en effet le cas si par exemple la page html ne contient pas de balise avec la classe CSS 'pageTitle', ou si le script s'exécute avant que la page ne soit complètement chargée.
Si on laisse notre code tel quel, alors on s'expose à des bugs car dans l'hypothèse où querySelector retournerait `null`, la ligne
```js
document.querySelector('.pageTitle').innerHTML = title.render();
```
planterait aussitôt (impossible d'appeler la propriété "innerHTML" sur une valeur nulle !).
3. **Corrigez les deux erreurs remontées par Flow en décomposant les instructions `querySelector` et `innerHTML` en 2 étapes** :
```js
const titleElement:?HTMLElement = document.querySelector('.pageTitle');
if ( titleElement ) {
titleElement.innerHTML = title.render();
}
```
Vous noterez qu'on en a profité pour typer la nouvelle variable titleElement dans un type compatible avec la valeur de retour de querySelector (on approfondira ce sujet lors du prochain cours sur l'API DOM).
Faites de même pour la homePage et relancez flow, vous n'avez en principe plus d'erreur !
## E.3. intégration avec Babel
Si vous tentez de recompiler votre JS une fois les informations de typage ajoutées, vous verrez que webpack et babel n'arriveront plus à compiler votre code.
En effet, la syntaxe flow ne fait pas partie de la spec officielle d'ECMAScript.
On peut cependant assez facilement rendre tout ça compatible :
1. **Installez le preset babel flow** (qui va permettre à babel de comprendre les instructions de typage de flow) :
```bash
npm install --save-dev @babel/preset-flow
```
2. **Dans le fichier `.babelrc` ajoutez le preset "flow"** :
```json
{
"presets": [
["@babel/env", {"modules": false}],
"@babel/flow"
],
"plugins": ["@babel/plugin-proposal-class-properties"]
}
```
3. **Relancez la compilation à l'aide de la commande `npm run build` ou `npm run watch`** (*pensez à stopper le `watch`précédent s'il tournait toujours <kbd>CTRL</kbd>+<kbd>C</kbd>)*, vérifiez que tout compile correctement et que l'affichage dans le navigateur est inchangé.
## E.4. Intégration dans VSCodium
Pour permettre à VSCodium de comprendre les informations de typage flow, installez l'extension : https://marketplace.visualstudio.com/items?itemName=flowtype.flow-for-vscode
1. Activez l'extension une fois installée en relançant VSCodium
2. Modifiez le fichier `.vscode/settings.json` comme ceci :
```json
{
"javascript.validate.enable": false,
"flow.useNPMPackagedFlow": true,
"[javascript]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
```
Désormais si vos fichiers comportent l'annotation `// @flow` alors le plugin va afficher les éventuelles erreurs de typage !
## Ça y est ! <!-- omit in toc -->
***vous avez un environnement de travail prêt à utiliser les dernières technologies JS : des classes avec une syntaxe moderne, des modules et du typage, tout en conservant une compatibilité avec les vieux navigateurs. La vie est belle !***
***Ceci étant dit le travail n'est pas terminé :***
## E.5. Typer notre code
**Maintenant que flow est installé, ajoutez les informations de typage aux différents fichiers de notre application (main, Component, Img, PizzaThumbnail, HomePage, etc.):**
- typer toutes les variables (let, const)
- typer les propriétés d'instances
- typer les paramètres de méthodes et de fonctions
- typer les valeurs de retour des méthodes et fonctions
***NB:** Ne pas oublier d'ajouter le commentaire `// @flow` en entête de chaque fichier modifié*
# Cette fois c'est terminé, bravo ! <!-- omit in toc -->
\ No newline at end of file
# TP 2 - POO - modules - typage <!-- omit in toc --> <img src="images/readme/header.jpg">
## Objectifs <!-- omit in toc --> ## Objectifs
- Savoir faire de la POO en ES6 - Savoir faire de la POO en ES6
- Mettre en oeuvre le système de modules - Mettre en oeuvre le système de modules
- Ajouter le support du typage statique à notre application
- Et faire évoluer notre application ***"Pizzaland"*** 🍕 - Et faire évoluer notre application ***"Pizzaland"*** 🍕
## Sommaire ## Sommaire
...@@ -13,4 +12,3 @@ Pour plus de clarté, les instructions du TP se trouvent dans des fichiers disti ...@@ -13,4 +12,3 @@ Pour plus de clarté, les instructions du TP se trouvent dans des fichiers disti
2. [B. La POO](B-poo.md) 2. [B. La POO](B-poo.md)
3. [C. Modules](C-modules.md) 3. [C. Modules](C-modules.md)
4. [D. POO avancée](D-poo-avancee.md) 4. [D. POO avancée](D-poo-avancee.md)
5. [E. Typage](E-typage.md)
images/readme/flow-error.jpg

61.5 KiB

images/readme/header-small.jpg

27.5 KiB

images/readme/header.jpg

46 KiB

images/readme/modules-network-bundle.png

53.4 KiB

images/readme/modules-network.png

66.8 KiB

images/readme/npm-run-build.gif

590 KiB

images/readme/npm-run-test.gif

884 KiB

images/readme/npm-run-watch.gif

730 KiB

images/readme/pizzaland-00.jpg

153 KiB

images/readme/pizzaland-00.png

1020 KiB

images/readme/pizzaland-01.jpg

32.2 KiB

images/readme/pizzaland-01.png

413 KiB

images/readme/pizzaland-02.jpg

35.9 KiB

images/readme/pizzaland-02.png

701 KiB

0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment