master
Denis MIGDAL 9 months ago
parent 672c46fe07
commit 579761fcb6

@ -40,27 +40,25 @@ include_toc: true
- [ ] Move optimisation 2 [F]
- [ ]  OPTI REST API (cf youtube)
- [ ] OPTI REST API (cf youtube)
5. <mark>[F]</mark> Perfs et SEO - [partie opti de com' Client-Serveur]
## S4 Programmation Web
1. <mark>[D-TS]</mark> JS (+ TS type - basique)
1. Manque structures de données [Obj/Array / Str / Map/Set]
2. Classes, fonctions avancées, autres.
2. Classes, fonctions avancées, imports (30min) + APIs (30min)
3. [C-Deno] Deno + Deno/TS config + npm + Deno API + serveur
1. BDD API
2. File API
3. etc.
3. [X] WebComponents en vrai / Animations / transitions => Setinter+animationFR/ canvas / WebWorker/service workers + socket servers [Add fiche] + [Worklet - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/Worklet) / [Animation Worklet - HTTP203 Advent - YouTube](https://www.youtube.com/watch?v=ZPkMMShYxKU&t=19s)
4. [<mark>D-TS</mark>] TS avancé (dont TS config ?)
4. [X] WebComponents en vrai / Animations / transitions / canvas / WebWorker
5. unit tests / tests de recettes (en pratique) / validation des données runtime
5. [<mark>D-TS</mark>] TS avancé / WebPack / CI/CD / unit tests / tests de recettes (en pratique) / validation des données runtime
Web3A : WebPack / CI/CD / SEO ???
## S5 .....

@ -0,0 +1,636 @@
---
gitea: none
include_toc: true
---
# Typescript
[TOC]
## JavaScript et TypeScript
Au semestre précédent, nous avons utilisé Brython afin d'exécuter du Python dans le navigateur. En réalité Brython transforme le code Python en JavaScript qui est ensuite interprété par le navigateur. En effet, le navigateur ne peut exécuter que deux langages :
- **WASM** : une sorte de langage bas niveau vers lequel d'autres languages peuvent être compilés (e.g. C, C++, Rust, Java, etc).
- **JavaScript**
JavaScript signale uniquement les erreurs d'exécutions. Ainsi certaines erreurs de programmations peuvent être silencieuses, ou ne pas être détectées lors de vos tests. Nous utiliserons TypeScript, un sur-ensemble de JavaScript permettant de détecter des erreurs en amont, facilitant grandement les étapes de tests et de développements.
Typescript permet d'indiquer le type des variables permettant à la fois de documenter l'usage des fonctions, mais aussi de vérifier et garantir qu'une fonction est bien appelée avec les bons paramètres. Ainsi en à peine quelques millisecondes, Typescript est capable de vous avertir d'erreurs de programmations que vous pourriez ne pas détecter, puis passer une journée à chercher. Il facilite aussi grandement les étapes de refactoring, e.g. lorsque la signature d'une fonction change, et requiert alors de changer son appel dans l'intégralité de votre code.
Au semestre précédent, nous avons utilisé l'API JS/DOM via Brython. Nous utiliserons la même API ce semestre, i.e. vous réutiliserez les mêmes fonctions et paramètres, seul le langage utlisé change.
### Exécution de Typescript côté client
Le navigateur ne peut pas exécuter directement du code TypeScript. Il faut ainsi, en amont, le convertir en JavaScript à l'aide de la commande shell `tsc` :
```bash
tsc --target esnext --strict --watch $FILE
```
Les options sont soit données en paramètre de la commande, ou indiquées dans un fichier `tsconfig.json`. L'option `--watch` permet de générer les fichiers JS à chaque modifications du fichier TS correspondant. Son avantage majeur est d'intégrer un cache en RAM permettant d'éviter de re-générer les fichiers JS de fichiers TS non-modifiés. Il permet ainsi un gain de temps significatif lors du développement, passant de plusieurs minutes de traduction pour de gros projets, à quelques millisecondes.
Une fois le fichier JS généré, on peut désormais l'inclure à la page Web :
```html
<!DOCTYPE html>
<html>
<head>
<!-- ... -->
<script src="$JS_FILE" type="module" defer></script>
</head>
<body>
</body>
</html>
```
💡 Dans le cadre d'un projet, il est recommandé d'utiliser un `tsconfig.json` :
```shell
tsc --init --target esnext --strict # initialiser un projet (crée le tsconfig).
tsc --build --watch # convertir les fichiers TS du projet en JS.
```
#### Installation de `tsc`
```shell
npm install typescript --save-dev
echo "PATH=\$PATH:$PWD/node_modules/typescript/bin/" >> ~/.bashrc
```
### Exécution de TypeScript côté serveur
JavaScript et TypeScript peuvent non seulement s'exécuter côté client, sur le navigateur, mais aussi côté serveur. Ce à l'aide d'interpréteurs comme `Node` ou `Deno`. Utiliser le même langage côté client et côté serveur possède plusieurs avantages :
- **factoriser** des parties de codes utilisés à la fois côté client et côté serveur.
- **déplacer** aisément des calculs du côté serveur vers le côté client et inversement.
- évite d'avoir à apprendre et **maîtriser deux langages** différents.
Dans le cadre de ce module, nous utiliserons `Deno` qui permet d'exécuter directement des fichiers TS sans avoir à générer des fichiers JS via `tsc`. Il a aussi l'avantage de supporter les APIs fournis par le navigateur.
Nous étudierons plus en détails `Deno` dans le cours suivant.
L'exécution d'un script TypeScript avec Deno se fait via la commande suivante :
```bash
deno run --check --watch $FILE
```
💡 L'option `--watch` permet de relancer votre application lorsque vous en modifiez un des fichiers.
💡 Vous pouvez aussi ajouter un shebang au début du fichier Typescript afin de pouvoir l'exécuter comme une commande shell :
```typescript
#!/usr/bin/env -S deno run --check
// Usage: ./index.ts [arg1,...]
Deno.args // arguments de la commande shell.
const answer = prompt($QUESTION); // affiche une invite à l'utilisateur.
```
📖 Il est de bon usage d'intégrer les lignes de commandes pour construire et exécuter un projet soit dans un fichier `README.md` à la racine du projet, soit en commentaire au début du fichier principal (e.g. `index.ts` / `main.ts` ).
💡 Comme avec `tsc`, il est recommandé d'utiliser un fichier de configuration pour les gros projets `deno.json`:
```json
{
"tasks": {
"taskname": "command",
"run": "deno run --check --watch index.ts"
}
}
```
```shell
deno task run # Exécuter "tâche "run".
```
#### Installation de Deno
```shell
DENO_INSTALL="/home/IUT/$USER/scratch/.deno"
curl -s https://deno.land/x/install/install.sh | sh
echo "export DENO_INSTALL='$DENO_INSTALL'" >> ~/.bashrc
echo 'PATH="$PATH:$DENO_INSTALL/bin"'
```
📖 Pour plus d'indications sur l'installation de Deno, vous pouvez vous reporter à :
https://docs.deno.com/runtime/manual/getting_started/installation
## Types et structures de données natives
### Déclaration d'une variable
Contrairement à Python, JS possède 3 manières de déclarer une variable :
```javascript
let a = ...; // "block-scoped".
var a = ...; // "function-scoped".
const A = ...; // une constante, i.e. ne peut être modifiée.
```
*Note:* contrairement à Python, JS n'a pas besoin du mot clef `global` pour accéder à une variable globale. L'object `globalThis` contient l'ensemble des variables globales.
### Types de bases
JavaScript distingue 6 types de base :
| Déclaration | JS Type | Python Type |
| -------------------- | ----------- | ------------ |
| `let a = 42;` | `number` | `float` |
| `let a = true;` | `boolean` | `bool` |
| `let a = "str";` | `string` | `str` |
| `let a = undefined;` | `undefined` | ≈ `NoneType` |
| `let a = {a: 42}` | `object` | ≈ `dict` |
| `let a = foo;` | `function` | `function` |
Il existe plusieurs valeurs spéciales :
| Valeur | JS Type | En Python | Remarques |
| -------------------------- | ----------- | ----------------- | ------------------------------- |
| `NaN` | `number` | `float('nan')` | Not a Number (e.g. 0/0) |
| `Number.POSITIVE_INFINITY` | `number` | `float('inf')` | |
| `Number.NEGATIVE_INFINITY` | `number` | `float('-inf')` | |
| `0b01010111` | `number` | `0b01010111` | Nombre en binaire. |
| `0o777` | `number` | `0o777` | Nombre en octal |
| `0xFF` | `number` | `0xFF` | Nombre en hexadécimal |
| `1.2e2` | `number` | `1.2e2` | Nombre en notation scientifique |
| `true` | `boolean` | `True` | |
| `false` | `boolean` | `False` | |
| `` `val: ${value}` `` | `string` | `f"val: {value}"` | littéraux de gabarits |
| `null` | `object` | ≈ `None` | valeur "pas de valeurs" |
| `undefined` | `undefined` | ≈ `None` | valeur inexistante |
Pour retrouver le type d'une variable, vous pouvez utiliser `typeof $VAR`, qui est l'équivalent de `type($VAR)` en Python.
### Opérateurs
JavaScript utilise les mêmes opérateurs que Python à quelques exceptions près.
#### Opérateurs bit à bits :
| Opérateur | Nom |
| --------- | ----------- |
| `a\|b` | OU binaire |
| `a&b` | ET binaire |
| `a^b` | XOR binaire |
| `~a` | NOT binaire |
#### Opérateurs logique
| JavaScript | Python | Commentaires |
| ---------- | --------- | --------------------- |
| `a\|\|b` | `a or b` | OU logique |
| `a&&b` | `a and b` | ET logique |
| `!a` | `not a` | NON logique |
| `a??b` | | Coallescence des nuls |
💡 *Astuce :* l'opérateur de coallescence des nuls peut être utilisé pour indiquer des valeurs par défauts :
```javascript
let a ??= b; // si a est null ou undefined, a = b.
let a ||= b; // si a est évalué à false, a = b
```
#### Opérateurs de comparaison
| JavaScript | Python | Commentaires |
| ---------- | ------------ | ---------------------------- |
| `a == b` | `a == b` | égalité de valeur (à éviter) |
| `a != b` | `a != b` | |
| `a === b` | `a is b` | égalité stricte |
| `a !== b` | `a is not b` | |
Opérateurs communs : `>`, `>=`, `<`, `<=`.
#### Opérateurs arithmétiques
Opérateurs communs : `+` (binaire et unaire), `-` (binaire et unaire), `/`, `%`, `*`, `**`
| JavaScript | Python | Commentaires |
| ---------- | ------ | ---------------------------------- |
| `++a` | `a+=1` | incrément |
| `a++` | | retourne la valeur puis incrémente |
| `--a` | `a-=1` | décrément |
| `a--` | | retourne la valeur puis décrémente |
#### Opérateurs d'affectations
Comme en python, il s'agit des opérateurs arithmétiques suffixé d'un `=`.
Par exemple `a += b` est équivalent à `a = a+b`.
#### Conversions
```javascript
let a = +$VALUE; // number
let a = !!$VALUE; // boolean
let a = (5).toString(); // string "5"
let a = (5).toString(2);// string "101"
```
📖 `0`, `""`, `null`, `undefined`, sont évalués à `false`.
### Structures de données <mark>[TODO]</mark>
Contrairement à Python, JavaScript ne possède qu'un seul type de tableaux, les `Array` (l'équivalent des `list` en Python). Les dictionnaires quant à eux sont implémentés soit par des `Object` (si les clefs sont des `string`) ou par des `Map`.
| JavaScript | Python | Remarques |
| ---------- | ------------------ | ----------------- |
| `Array` | `list` ou `tupple` | |
| `Object` | `dict` | Si clefs `string` |
| `Map` | `dict` | |
| `Set` | `set` | |
#### Opérateurs sur les objets et tableaux
| JavaScript | Python | Remarques |
| ------------------------ | ---------------- | ---------------------------------------------- |
| `0 in a` | | Existence de la clé "0" dans l'objet/tableau a |
| `a[0]` | `a[0]` | Valeur associé à la clé "0" |
| `delete a[0]` | `del a[0]` | Supprime un élément. |
| `a?.[0]` | | Chaînage optionnel (clé) |
| `a?.foo` | | Chaînage optionnel (membre) |
| `a?.()` | | Chaînage optionnel (appel fonction) |
| `[...a, ...b]` | `(*a, *b)` | Spread operator (tableaux) |
| `{...a, ...b}` | `{**a, **b}` | Spread operator (objects) |
| `[a, b, ...c] = [1,2,3]` | `a,b,*c=(1,2,3)` | Décomposition (tableaux) |
| `{a, b, ...c} = {...}` | | Décomposition (objects) |
| `{a: c}  = {a: 4}` | | Décomposition et renommage (`c=4`). |
#### Opérations
##### Array
##### Objects
+ Object.create(null)
##### Map
##### Set
##### String
## Indications de type avec Typescript
### Indication explicite de type
```typescript
let a = 42; // TS déduit que a est un number.
let a: number = 42; // on indique explicitement le type.
```
### Union de types
Il arrive qu'une variable puisse prendre des valeurs de plusieurs types différents :
```typescript
let a = 42; // TS déduit que a est un number.
a = "42"; // erreur, a ne peut être qu'un number.
let a: number | string = 42;
a = "42"; // légal
```
### Valeur optionnelle
Il arrive qu'une variable puisse ne pas avoir de valeurs à l'initialisation :
```javascript
let a: number; // erreur.
let a: number|undefined; // légal
```
### Types TypeScript pour les structures de données
| JavaScript | Type TS | Remarques |
| ---------------- | ------------------------------------ | ---------------------------------------------- |
| `Array` | `Array<T>` ou `T[]` | Liste |
| `Array` | `[string, number]` | Tupple |
| `Array` | `ReadonlyArray<T>` ou `readonly T[]` | Liste en lecture seule. |
| `{v: 4}` | `Record<string, number>` | Liste associative (accepte de nouvelles clés). |
| `{v: 4, u: 5}` | `Record<"v"\|"u", number>` | Liste associative (clés précises). |
| `o = {v: 4}` | `Record<keyof typeof o, number>` | Liste associative (clés précises). |
| `{v: 4, u: "s"}` | `{v: number, u: string}` | Liste associative (clés et valeurs précises). |
| `Set` | `Set<V>` | |
| `Map` | `Map<K,V>` | |
### Alias de types
En TypeScript, il est possible de définir des alias pour désigner un type. Cela est bien utile lorsque la description du type est longue à écrire, pour expliciter la fonction du type, permettre de changer le contenu d'un type sans avoir à modifier l'intégralité du code, ou respecter l'encapsulation.
```typescript
type X = number | string;
let a: X = 42; // équivaut à let a: number | string = 42;
type Point2D = [number, number]; // explicite
type Result = { value: string }; // la structure peut changer.
type ID = ???; // je peux utiliser le type ID sans en connaître le type exact.
```
### TS typeof
⚠ Il existe 2 `typeof`, un JS qui déduit le type de la **valeur** à l'exécution, et un TS qui, dans les indications de types, indique le type TS de la **variable**.
```typescript
type A = typeof a; // TS, number | string
console.log(typeof a); // JS, string
```
## Structures de contrôle
### Conditions
```javascript
// [JS] Javascript
if( $COND2 ) {
} else if($COND2) {
} else {
}
```
```python
# [🐍] Python
if $COND1:
pass
elif $COND2
pass
else
pass
```
### Opérateur ternaire
```javascript
// [JS] Javascript
let a = $COND ? $IF_TRUE : $IF_FALSE
let a = $COND
? $IF_TRUE
: $IF_FALSE
```
```python
# [🐍] Python
a = $IF_TRUE if $COND else $IF_FALSE
```
### Switch
```javascript
// [JS] Javascript
switch(value) {
case 'value':
//...
break;
default:
//...
}
```
```python
# [🐍] Python
match value:
case 'value':
pass
case _:
pass
```
### Boucles
#### While
```javascript
// [JS] Javascript
while($COND) {
}
```
```python
# [🐍] Python
while $COND:
pass
```
#### Do while
```javascript
// [JS] Javascript
do {
// s'exécute au moins une fois.
} while( $COND );
```
#### For in (parcours les clefs)
```javascript
// [JS] Javascript
for( let value in $OBJ ) {
}
```
#### For of (parcours les valeurs)
```javascript
// [JS] Javascript
for( let value of $OBJ ) {
}
```
### Exceptions
```javascript
// [JS] Javascript
try {
throw new Error($MESSAGE)
} catch(error) {
} finally {
}
```
```python
# [🐍] Python
try:
raise Exception($MESSAGE)
except Exception as error:
pass
finally:
pass
```
## Fonctions
### Déclaration
Contrairement à Python, il n'est pas possible, en JavaScript, d'appeler une méthode avec le nom des paramètres (e.g. `foo(a=2);`), l'appel ne se fait qu'avec la position des paramètres (e.g. `foo(1,2,3)`).
```javascript
// [JS] JavaScript
function foo(arg1, arg2 = "2") {
//...
return 2.3;
}
foo(1)
```
```python
# [🐍] Python
def foo(arg1, arg2 = "2", /): # les paramètres positionnel uniquement
# ...
return 2.3
foo(1)
```
```typescript
//[TS] TypeScript
function foo(arg1: number, arg2?: string = "2"): number {
// ...
return 2.3;
}
foo(1)
```
### Paramètre du reste (rest parameter)
```javascript
// [JS] JavaScript
function foo(arg1, ...arg2) {
// arg2 = ["2", "3"]
return 2.3;
}
foo(1, "2", "3")
```
```python
# [🐍] Python
def foo(arg1, /, *arg2):
# arg2 = ("2", "3")
return 2.3
foo(1, "2", "3")
```
```typescript
//[TS] TypeScript
function foo(arg1: number, ...arg2: readonly string[]): number {
// arg2 = ["2", "3"]
    return 2.3;
}
foo(1, "2", "3")
```
### Opérateur de décomposition (spread operator) comme argument
```javascript
// [JS] JavaScript
function foo(arg1, arg2, arg3) {
//...
return 2.3;
}
const args = ["2", "3"];
foo(1, ...args)
```
```python
# [🐍] Python
def foo(arg1, arg2, arg3, /):
# ...
return 2.3
args = ("2", "3")
foo(1, *args )
```
```typescript
//[TS] TypeScript
function foo(arg1: number, arg2: string, arg3: string): number {
return 2.3;
}
const args = ["2", "3"] as const; // ou as ["string", "string"]
foo(1, ...args)
```
### Generiques
En Typescript, un générique est désigné par `<T>`.
Par exemple prenons une fonction `min(a, b)` qui retourne la valeur minimale entre `a` et `b`. Ils peuvent être de n'importe quel type, cependant, `a`, `b` et la valeur de retour doivent être du même type. Ainsi on écrira :
```typescript
function min<T>(a: T, b: T): T { ... }
min<number>(1,2); // ne peut retourner qu'un number.
min(1,2); // aux paramètres, TS déduit que T = number.
```
Si on veut restreindre les types possibles pour `T`, on utilisera `extends` :
```typescript
function min<T extends string|number|boolean>(a: T, b: T): T { ... }
```
### Asynchronisme
L'asynchronine permet d'éviter de bloquer le thread principal lors d'opérations bloquantes et coûteuses. Par exemple, de ne pas bloquer la GUI et effectuer d'autres opérations en attendant le retour d'un `fetch()` qui peut prendre quelques ms à plusieurs secondes.
Comme Python, JavaScript gère l'asynchronisme, à une différence près. En JavaScript, la fonction asynchrone commence à s'exécuter dès son appel et retourne une promesse. En Python, l'appel à la fonction asynchrone retourne directement une coroutine, et la fonction ne commence à s'exécuter que lorsque la coroutine est soit attendue, soit donnée à une "boucle" asyncio.
| TypeScript | Python | Remarques |
| ------------------------- | ------------------ | ---------------------------------------------- |
| Promesse | Coroutine | Mécanisme |
| `async function foo() {}` | `async def foo():` | Déclarer une fonction asynchrone |
| `async foo(){...}` | `async def foo():` | Déclarer une méthode asynchrone |
| `await foo()` | `await foo()` | Attendre le résultat d'une fonction asynchrone |
En réalité `async function foo(): number {...}` est un sucre syntaxique pour écrire `function foo(): Promise<number> { return new Promise(() => {...}) }`.
Une promesse s'utilise comme suit :
```typescript
const p = new Promise<number>( (resolve, reject) => {
// code à exécuter de manière asynchrone.
resolve($RETURN_VALUE); // await p retournera $RETURN_VALUE
reject($MESSAGE); // await p lancera une exception.
});
//...
await p;
// ou
let {promise, resolve, reject} = Promise.withResolvers<number>();
addEventListener($EVENT, () => resolve(), {once: true});
// la promesse sera résolue plus tard.
await promise;
// attendre plusieurs promesses (retourne un tableau des résultats)
await Promise.all([p1, p2,..]); // exception si une des promesses échoue.
await Promise.allSettled([p1, p2, ...]); // tableau de {status, value, reason}
// attendre la première promesse
await Promise.race([p1, p2, ...); // s'arrête au premier resolve ou reject.
await Promise.any([p1, p2, ...]); // s'arrête au premier resolve.
```
## Futur
- Manipuler URL => move 2 JS-DOM API ?
- console/assert => move 2 JS-DOM API ?

@ -0,0 +1,211 @@
---
gitea: none
include_toc: true
---
# Classes et API
[TOC]
## Classes
### Usage
Bien que la syntaxe change quelque peu, les classes JS/TS ont un fonctionnement très similaires à celles de Python.
| TypeScript | Python | Description |
| ------------------------- | ------------------------------- | ------------------------------------------------------- |
| `class C{...}` | `class C:` | Déclarer une classe |
| `let c = new C()` | `c = C()` | Instancier une classe |
| `constructor(...){...}` | `def __init__(self):` | Constructeur/Méthode |
| `_x: string` | `_x = None` | Déclarer un attribut |
| `_x?: string` | `_x = None` | Déclarer un attribut non initialisé |
| `_x = "str"` | `_x  = "str"` | Initialiser un attribut |
| `this._x` | `self._x` | Accéder à un attribut à l'intérieur d'une méthode. |
| `get x() {...}` | `@property x(self):` | Getter |
| `set x(value) {...}` | `@x.setter def x(self, value):` | Setter |
| `this.x = 2` | `self.x = 2` | Accéder à un getter/setter à l'intérieur d'une méthode. |
| `static foo = 42` | `foo = 42` | Attribut statique |
| `static foo(){...}` | `@staticmethod def foo():` | Méthode statique |
| `C.foo` | `C.foo` | Accéder à un membre statique |
| `class C extends B {...}` | `def C(B):` | Héritage |
| `this` | `self` | Auto-référence |
| `super` | `super()` | Classe-mère |
📖 Les getters et setters peuvent être vus comme des attributs donc la valeur est retournée par le getter, et la modification gérée par le setter.
Il peuvent avoir plusieurs utilités :
- créer un attribut donc la valeur n'est pas stockée mais calculée ;
- créer un attribut accessible qu'en lecture ou qu'en écriture ;
- contrôler les valeurs possibles dans le setter.
```typescript
class C {
#list = [];
get size() {
return this.#list.length;
}
set size(size: number) {
if(size < 0) throw new Error('Une taille doit être positive!');
this.#list.length = size;
}
}
const c = new C();
c.size = 2; // setter
console.log(c.size); // getter
```
### Ajouts de Typescript
| TypeScript | Javascript | Python | Commentaires |
| ---------------- | ----------------- | ----------------------- | ------------------------ |
| `protected name` | `_name` | `_name` | Attribut protégé. |
| `private name` | `#name` | `__name` | Attribut privé. |
| `readonly name` | `get name(){...}` | `@property def name():` | Attribut non-modifiable. |
Typescript permet aussi de créer des **classes abstraites** ainsi que des **interfaces**. Toutes les deux possèdent des **membres abstraits**, i.e. qui devront être implémentées par la classé héritant/implémentant la classe abstraite/interface. L'interface ne possède que des membres abstraits, tandis que la classe abstraite peut posséder des membres non-abstraits.
| Classe Abstraite | Interface | Remarques |
| ---------------------- | ------------------------- | ----------------------- |
| `abstract class X{}` | `interface X{}` | Déclaration |
| `class C extends X {}` | `class C implements X {}` | Héritage/implémentation |
| `abstract foo();` | `foo();` | Membre abstrait |
### Notions avancées [<mark>TODO</mark>]
#### Well-known Symbols
#### Mixins
#### Prototype
+ bind
## Import/export
Comme en Python, on peut exporter des symboles (fonctions, classes, variables, etc) et les importer dans un autre fichier afin de les utiliser. En Javascript et contrairement à Python, l'import utilise le chemin du fichier (e.g. `./toto/titi.ts`) et non un nom de module (e.g. `toto.titi`). Aussi les symboles à exporter doivent être explicités en leur préfixant le mot clef `export`.
| Typescript | Python | Remarques |
| ------------------------------- | -------------------------------- | --------------------------------------------------------- |
| `export class X{}` | `def X:` | Déclarer une classe à exporter |
| `export function foo(){}` | `def foo():` | Déclarer une fonction à exporter |
| `export const X = ...` | `X = ...` | Déclarer une variable à exporter |
| `export type T = ...` | | Déclarer un type à exporter. |
| `import {X, foo} from '...'` | `from '...' import X, foo` | Importer des symboles |
| `import * as M from '...'` | `import '...' as M` | Importer tous les symboles |
| `export default class X{}` | | Export par défaut |
| `import X, {...} from '...'` | | Importer l'export par défaut. |
| `const M = await import('...')` | `importlib.import_module('...')` | Import dynamique. |
| `export {...} from '...'` | | Exporter des symboles d'un autre fichier |
| `export {X as Y} from '...'` | | Exporter sous un autre nom un symbole d'un autre fichier. |
| `import type {...} from '...'` | | Importer en tant que type Typescript. |
### Import maps
Les cartes d'imports permettent de définir des alias pour les imports :
```html
<!-- Côté client : fichier HTML -->
<!DOCTYPE html>
<html>
<head>
<script type="importmap">
{
"imports": {
"$NAME": "$PATH"
}
}
</script>
<script type="module">
import * from '$NAME/...'; // équivalant à from "$PATH/..."
</script>
</head>
</html>
```
```json
// Côté serveur : deno.json
{
"imports": {
"/": "./",
"./": "./",
"$NAME/": "$PATH/"
},
"tasks": {
//...
}
}
```
⚠ Les imports map sont actuellement encore boguées.
## Fonctions (avancé)
### Fonctions génératrices
<mark>TODO: utilité</mark>
```javascript
// [JS] JavaScript
function * foo(nb) {
for(let i = 0; i < nb; ++i)
yield i;
}
for( let val of foo(5) )
console.log(val);
```
```python
# [🐍] Python
def foo(nb, /) {
for i in range(nb):
yield i;
}
for val in foo(5)
console.log(val);
```
```typescript
//[TS] TypeScript
function * foo(nb: number): Generator<number> {
for(let i = 0; i < nb; ++i)
yield i;
}
for(let val of foo(5) )
console.log(val);
```
### Fonctions fléchées
<mark>TODO</mark>: pas d'équivalent en python + utilité (callbacks)
```javascript
const foo = arg => arg[0]; // est équivalent à :
const foo = function(arg){ return arg[0]; }
// si plusieurs paramètres, utiliser des parenthèses ().
// si valeur retournée plus complexe, utiliser des accolades {}.
const foo = (arg1, args) => { ... ; return x; } // est équivalent à :
const foo = function(arg1, arg2){ ... ; return x; }
```
## API Deno
File (cf 54&55 old slides CM1)
BDD (cf 56&57 old slides CM1)
Server (cf lien) / npm
## API JS
Date / Regexp / Math API / Crypto

@ -0,0 +1,446 @@
---
gitea: none
include_toc: true
---
# Applications et tests
[TOC]
## Applications Web
Comme vous le savez déjà, une application Web est composée de :
- une partie **cliente** (*frontend*), gérant l'interface graphique (GUI) et les interactions avec l'utilisateur.
- une partie **serveur** (*backend*), gérant à la fois les ressources statiques (fichiers HTML/CSS/JS) et les ressources dynamiques (API REST).
le frontend est exécuté sur le navigateur, i.e. la même application, les mêmes langages, pour les différentes plateformes desktop/mobile.
Il est cependant fréquent de déporter des parties du serveur vers le client :
- des **calculs** afin de réduire la charge du serveur et éviter certaines attaques DoS.
- des **fichiers statiques** via des [politiques de cache](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control), afin de réduire la consommation et les besoins en bande passante.
- des **données** via `localStorage` ou `indexDB`, notamment pour des raisons de vie privée.
Pour certaines applications, il arrive aussi qu'on souhaite gérer des pertes de connexions temporaires. Par exemple en ayant un "faux" serveur côté client, e.g. via un [ServiceWorker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API), interceptant les requêtes REST, et maintenant un état interne local, pour ensuite se synchroniser avec le serveur distant une fois la connexion rétablie.
Mais on peut aller plus loin, en installant le serveur côté client, c'est à dire que l'ordinateur client exécutera lui-même :
- *le frontend:* i.e. un navigateur permettant de visualiser les pages Web.
- un serveur Web pour distribuer les fichiers statiques au frontend.
- le *backend*: un serveur HTTP répondant aux requêtes du client (e.g. API REST ou websockets).
Cela possède plusieurs avantages :
- peu de différences entre les versions web, desktop, et mobile d'une application.
- grande liberté dans le design des interfaces graphiques grâce à HTML5/CSS3/JS.
- accéder aux ressources locales inaccessibles à partir du navigateur, e.g. le système de fichiers du client.
- temps de chargements des ressources réduites car locales, complètement hors ligne.
- on n'expose pas de données sensibles car conservées localement.
Dans ce cadre, on préfère généralement lancer une fenêtre n'affichant que le site Web, sans intégrer l'interface graphique du navigateur (e.g. barre d'URL, menus, etc.). On peut alors utiliser des frameworks comme [Electon](https://www.electronjs.org/) ou [Tauri](https://tauri.app/), mais le plus simple reste de lancer le navigateur avec les options suivantes :
```shell
chromium --app=$URL
firefox --kiosk $URL # force aussi le plein écran
```
## Validation de données à l'exécution
Afin de rendre l'application plus stable, d'éviter des bugs, et des comportements indéterminés, il est important de valider les données avant tout traitements. Or, si TypeScript permet de s'assurer du type des variables lors de la phase de compilation, il ne permet pas de s'assurer de leur validité en cours d'exécution. En effet, de nombreuses données peuvent ne pas être connues lors de la phase de compilation, issus notamment :
- de formulaires renseignés par l'utilisateur.
- de fichiers, qui peuvent être modifiés par un tiers, ou dont le format peut évoluer entre deux versions.
- de ressources distantes, e.g. via une API REST.
Lors des échanges client-serveur, il est aussi d'usage de valider les données 2 fois :
- *côté client* : pour ne pas inutilement envoyer des données invalides, et ainsi éviter de charger le serveur pour rien.
- *côté serveur* : car il ne faut **jamais faire confiance au client** : un tiers pouvant aisément modifier son comportement (e.g. via des WebExtensions, injections de scripts, forger des requêtes avec `curl`, etc.).
### Validation de formulaires
Avec HTML5/CSS3, il est possible de s'assurer de la validité des données renseignées dans un champ grâce aux attributs suivants :
- `required` : ce champ ne peut être vide.
- `type` : le type de données attendues (e.g. nombre, mails, etc.), [cf doc](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types).
- `pattern` : une expression régulière (regex).
- `min`, `max`, `minlenght`, `maxlenght`.
Il est possible de vérifier la validité des données lors de 3 événements :
- `input` : la donnée est modifiée, usuellement, une indication visuelle (CSS) indique si la donnée est valide ou non.
- `change` : l'utilisateur quitte le champ (presse entré, clic ailleurs), usuellement, un message d'avertissement s'affiche si la donnée est invalide.
💡 On peut aussi activer et désactiver le bouton de soumission du formulaire en fonction de la présence ou non d'un champ dont la donnée est invalide.
- `submit` : le formulaire est soumis, usuellement, on valide la *structure des données* du formulaire, si invalide, on annule l'événement : `ev.preventDefault()`.
Vous pouvez aussi personnaliser l'affichage via :
- `:valid` pour ajouter des règles CSS aux champs ayant une donnée valide.
- `:invalid` pour ajouter des règles CSS aux champs ayant une donnée invalide.
- `.setCustomValidity(msg: string)` pour changer l'avertissement qui s'affiche.
Vous pouvez ainsi définir vos propres règles de validation :
```ts
input.addEventListener('input', () => {
// see also https://developer.mozilla.org/en-US/docs/Web/API/ValidityState
if( this.validity.valid === false ) { // invalide.
return;
}
if( ... ) // condition personnalisée.
return this.setCustomValidity('Check1 failed');
// ...
this.setCustomValidity(''); // pas d'erreurs.
})
```
### Validation de structures de données
Plusieurs bibliothèques permettent de valider les structures de données, dont [SuperStruct](https://docs.superstructjs.org/) :
```ts
import * as ss from 'superstruct';
// Décrire la structure de données.
const UserStruct = ss.object({
id : ss.number(),
name: ss.string()
});
// Récupérer un type TS à partir de la description.
type User = ss.Infer<UserStruct>;
// Utilisation (validation d'un formulaire):
const formData = new FormData(form); // récupère les valeurs d'un formulaire.
const obj = Object.fromEntries(formData.entries()); // convertir en objet.
if( ss.is(obj, UserStruct) ) {} // retourne false si 'obj' ne correspond pas à UserStruct.
ss.assert(obj, UserStruct); // lance une exception si 'obj' ne correspond pas à UserStruct.
```
SuperStruct propose les types suivants :
- `literal(val)`, `boolean()`, `number()`, `integer()`, `date()`;
- `array(e)`, `tupple([])`, `set(v)`, `map(k,v)`, `record(k, v)` ;
- `object({})`, `type({})` `instance(Klass)`, `unknown()`, `never()`, `any()` ;
- `func()`, `regexp()`,` enums([])`.
💡 SuperStruct permet aussi de définir ses propres types :
```ts
const mytype = ss.define<Type>('$Name', (value) => ...)
```
SuperStruct permet aussi d'ajouter des précisions sur les types :
- `nullable(a)`, `optionnal(a)` ;
- `empty(val)`, `nonempty(val)`, `min(val, $min)`, `max(val, $max)`, `size(val, $size)` ;
- `intersection([])`, `union([])` ;
- `pattern(val, $regexp)` .
💡 SuperStruct permet aussi de rafiner des types existants :
```ts
const mycstrnt = ss.refine(Struct, '$Name', (value) => {
if( ... )
return "msg error";
return true;
});
```
💡 SuperStruct permet aussi de :
- [manipuler des descriptions](https://docs.superstructjs.org/api-reference/utilities) (partial, pick, omit) ;
- [pré-traiter les données](https://docs.superstructjs.org/api-reference/coercions) (valeurs par défaut, enlever les espaces, etc.) ;
- [définir des dépréciations](https://docs.superstructjs.org/api-reference/utilities#deprecated).
### Les expressions régulières (regex)
Les *expressions régulières* (regex) permettent de décrire un format en vue d'effectuer des opérations sur des chaînes de charactères.
```ts
// déclarer une regex.
const regexp = /^(yes|no)$/sm;
// ou
const regexp = new RegExp("^(yes|no)$", "sm");
// retourne un booléen indiquant si le string correspond au regex.
regexp.test('yes'); // true
regexp.test('non'); // false
```
| Expression | Description |
| ---------- | -------------------------------- |
| `^` | Début de la chaîne de caractère. |
| `$` | Fin de la chaîne de caractère. |
| `(ab\|cd)` | `ab` ou `cd` |
Il est possible d'utiliser des expressions régulières sur les opérations suivantes :
| Méthode | Description |
| ------------------ | ------------------------------------------------------------------------------------------------------------ |
| `.replace(r, v)` | Remplace la sous-chaîne correspondant au regex `r` par la valeur `v` (possibilité d'utiliser un callback). |
| `.replaceAll(r,v)` | Remplace les sous-chaînes correspondant au regex `r` par la valeur `v` (possibilité d'utiliser un callback). |
| `.search(r)` | Renvoie l'index de la première sous-chaîne correspondant à la regex `r`. |
| `.split(r)` | Découpe la chaîne de caractère en utilisant la regex `r` comme séparateur. |
💡 Vous pouvez visualiser et tester vos expression régulières, e.g. via [Regex Vis](https://regex-vis.com/).
L'onglet "Test" dans la colonne de droite, permet de tester votre Regexp sur plusieurs valeurs.
#### Classes de caractères
| Expression | Équivalance | Opposé | Description |
| ---------- | ------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `.` | | | N'importe quel caractère. |
| `[abc]` | | `[^abc]` | Le caractère `a`, `b`, ou `c`. |
| `[a-d]` | `[abcd]` | `[^a-d]` | Caractère entre `a` et `d` (inclus). |
| `[-a-d]` | `[-abcd]` | `[^-a-d]` | Idem, mais avec `-` en plus. |
| `\d` | `[0-9]` | `\D` | Chiffre |
| `\w` | `[a-zA-Z0-9]` | `\W` | Caractère alpha-numérique. |
| `\s` | | `\S` | Espace |
| `\p{x}` | | `\P{x}` | Classes de charactères unicode.<br/>[cf documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Unicode_character_class_escape) |
#### Quantifieurs
Il permettent d'indiquer le nombre de répétitions d'un élément :
| Expression | Équivalance | Description |
| ---------- | ----------- | ---------------------------- |
| `{n,m}` | | entre `n` et `m` répétitions |
| `{n}` | `{n,n}` | `n` répétitions |
| `{n,}` | `{n,∞}` | au moins `n` répétitions |
| `?` | `{0,1}` | élément optionnel |
| `+` | `{1,∞}` | au moins une répétition |
| `*` | `{0,∞}` | élément répété ou optionnel. |
### Capture
Il est aussi possible de d'utiliser les regex pour extraire des valeurs d'une chaîne de caractère grâce à `(?<name>pattern)`.
```ts
const search = /<(?<name>.*)>/sm;
const result = search.exec("Une <balise> html.");
if( result !== null) { // le pattern a été trouvé
result.groups.name // = "balise".
}
```
⚠ En Python, la notation est `(?P<name>pattern)`
```python
import re
regex = re.compile("<(?P<name>.*)>", re.S|re.M)
result = re.match(regex, "Une <balise> html.")
if result is not None:
result.group('name') # "balise"
# result.groupdict() pour obtenir l'ensemble des groupe sous forme de dict.
```
## Les tests
Valider le type et la structure des données n'est pas suffisant pour garantir le bon fonctionnement d'une application. On écrit alors des tests notamment pour :
- garantir que l'application respecte le comportement défini dans *le cahier des charges* ;
- *développement dirigé par les tests* (TDD), i.e. les tests servent de *cahier des charges*.
- prévenir des *régressions*, i.e., suite à une modification, un bug (re-)introduit sur une fonctionnalité existante ;
- *servir de documentation*, donne des exemples d'usages des fonctions.
- *déboguer*.
### La couverture des tests
📖 On appelle la *couverture des tests* (test coverage), la partie de code pour laquelle il existe des tests.
⚠ Il est impossible de tester tous les cas possibles et d'avoir une couverture parfaites. Il y a donc un compromis à faire sur l'utilité des tests et le temps pour les écrire.
Par exemple, sur un code non-collaboratif, stable, simple, sans nécessité de garanties, les tests se justifieront moins sur un code collaboratif, instable, complexe et avec obligations contractuelles.
⚠ Il ne faut pas aussi que le test soit redondant avec l'implémentation, i.e. utilise le code de la fonction pour vérifier son résultat :
```ts
function add(a,b) { return a+b; }
Deno.test('Addition', async() => {
const answer = add(1,2);
if( answer !== (1+2) ) // Vérifie que 1+2 === 1+2...
throw new Error(`\nExpected: ${1+2}\nGot: ${answer}`);
});
```
On peut distinguer 4 contextes dans l'écriture de code :
- *le comportement attendu est connu, mais pas l'algorithme* : on connait les données en entrées, les données en sortie, mais on ne sait pas encore la manière dont on va l'implémenter. Écrire des tests permet alors de décrire le comportement attendu, ainsi que de vérifier que l'implémentation respecte ces attendus.
*e.g. compter le nombre de mots dans une phrase.*
- *re-implémente un algorithme* (e.g. dans un autre langage, version plus rapide, etc.) : on peut alors tester les résultats de notre nouvelle implémentation contre les implémentations existantes, les mêmes entrées devant produire les mêmes résultats.
*e.g. signature RSA.*
- *l'algorithme est connu, mais pas les résultats* : il est nécessaire d'exécuter l'algorithme pour connaître le résultat. Dans ce cadre les tests ont de grandes chances d'être redondants avec l'implémentation. Toutefois des tests de non-régressions peuvent être implémentés en pré-calculant les résultats attendus.
*e.g. la somme des éléments d'une liste.*
- *l'algorithme est une traduction du comportement attendu* : l'implémentation a de grande chance d'être triviale, et les tests d'être redondants.
*e.g. la liste doit être vide à l'initialisation.*
### Les tests pour déboguer
Il est en effet très intéressant d'utiliser des tests pour déboguer. Lorsqu'un bug est constaté, il faut pouvoir le reproduire, généralement via un exemple minimal reproduisant l'erreur. Il ne reste alors plus qu'à ajouter une vérification pour obtenir un test :
```ts
// Exemple minimal :
let answer = authenticate("h4cker", "**********")
// expected : false
// got : true
// Le test (Deno) :
// Git issue #XXXX
Deno.test('H4cker authentication #XXXX', async() => {
let answer = authenticate("h4cker", "**********");
if( answer !== false)
throw new Error(`\nExpected: ${false}\nGot: ${answer}`);
})
```
Le bug sera alors considéré résolu lorsqu'il passera le test. Aussi, contrairement à des `console.log()` qui sont temporaires, les tests restent, et pourront être réexécutés à chaque *pull request*, afin de prévenir les régressions.
> « Les logs s'envolent, les tests restent »
### Tester le serveur (Deno)
Deno offre une API permettant aisément d'écrire et exécuter des suites de tests (*tests suite*) :
```typescript
Deno.test($TEST_NAME, async() => {
//...
if( $COND )
throw new Error($MSG);
});
```
```ts
deno test $FILE # exécuter le fichier de test $FILE
deno test $DIR # exécuter les tests contenu dans le dossier $DIR.
deno test # exécuter tous les tests du projet.
```
💡 La commande `deno run` supporte aussi les options `--watch` et `--check`.
💡 Vous pouvez écrire vos propres fonctions d'assertions, ou utiliser [une bibliothèque dédiée](https://docs.deno.com/runtime/manual/basics/testing/assertions) :
```ts
function assertEquals(got: any, expected: any, msg: string = '') {
if( got !== expected ) throw new Error
(`${msg}
\x1b[1;31m- ${got}\x1b[0m
\x1b[1;32m+ ${expected}\x1b[0m`);
}
// ou
import * from "https://deno.land/std@0.215.0/assert/mod.ts";
Deno.test('H4cker authentication #XXXX', async () => {
let answer = authenticate("h4cker", "**********");
assertEquals(answer, false, 'cf issue #XXXX');
});
```
💡 Si vous avez beaucoup de tests, vous pouvez définir des sous-tests :
```ts
Deno.test('Test', async (t) => {
await t.step('Sous-test', async(t) => {
await t.step('Sous-sous-test', async() => {});
});
});
```
💡 Vous pouvez aussi réinitialiser les valeurs initiales avant chaque sous-tests :
```ts
Deno.test('Test', async (t) => {
let user!: User;
async function step(name: string, callback: ()=>Promise<void>) {
user = new User();
await t.step(name, callack);
}
await step('Sous-test', async() => {
//...
});
});
```
### Tester le client
Vous pouvez très simplement tester des bouts de code du client en les important puis exécutant sur Deno. Pour simuler le DOM, vous pouvez utiliser la bibliothèque `DOMParser` qui vous permettra ensuite d'interagir avec :
```ts
import { DOMParser } from "https://deno.land/x/deno_dom/deno-dom-wasm.ts";
const document = new DOMParser().parseFromString( await Deno.readFile(...) )
const element = document.querySelector('#elem');
element.click();
```
Une autre solution est de lancer le site Web dans un navigateur en lui injectant du code JavaScript afin de le manipuler à partir d'un serveur. C'est par exemple ce que fait [Puppeteer](https://deno.land/x/puppeteer@16.2.0) :
```ts
import puppeteer from "https://deno.land/x/puppeteer@16.2.0/mod.ts";
// démarrage.
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto("https://example.com");
// exécuter du code sur le navigateur :
const result = await page.evaluate(() => { ... });
// 💡 vous pouvez aussi transmettre des valeurs :
const result = await page.evaluate((id, name) => { ... }, 1, 'foo');
// prendre une capture d'écran
await page.screenshot({ path: "example.png" });
// fin des tests.
await browser.close();
```

@ -0,0 +1,159 @@
Vous allez concevoir puis implémenter un serveur HTTP fournissant une API REST permettant de gérer votre serveur à distance.
## Prise en main de VSHS (20min)
Pour cela vous allez utiliser une bibliothèque développée par mes soins afin de vous simplifier le travail.
1. Créez un nouveau projet git : `git init`.
2. Clonez la bibliothèque VSHS dans le dossier `/libs/VSHS` :
`git submodule add git@github.com:denis-migdal/VSHS-TS-.git ./libs/VSHS` .
3. Démarrez le serveur d'exemple, puis effectuez une requête (lisez la documentation).
4. Créez un fichier `/server/deno.json`, ainsi qu'un dossier `/server/routes/`.
5. Créez un fichier `/server/index.ts` permettant de lancer votre propre serveur.
6. Créez, puis testez, votre première route :
- Requête : `GET /hello-world?name=$name`
- Réponse : `Hello $name`
⚠ Pensez à relancer votre serveur lorsque vous ajoutez une nouvelle route.
7. Créez un fichier `server/README.md` à compléter au fur et à mesure, et contenant, entres autres, la commande pour lancer et tester le serveur.
## Conception (30min)
Dans un premier temps, vous réfléchirez aux différentes fonctionnalités que votre serveur devra fournir. Pour cela, vous rédigerez un document Markdown dans `server/doc/conception.md`. Vous devrez ainsi définir :
- les routes
- les structures des données
- les requêtes REST en précisant :
- le format de la requête : méthode HTTP, route, paramètres (dans url, corps, route).
- le format de la réponse.
- les données exploitées et leur source : fichier, BDD, RAM, commande shell.
Par exemple :
**Routes :**
- `/users/` : liste des utilisateurs.
- `/users/{id}` : utilisateur `$id`.
**Structures des données :**
- **Utilisateur :**
```ts
{
name: string,
email: string
}
```
**API :**
- `/hello-world`
- `GET /hello-world?name=$name`
- *Description :* route permettant de tester le bon fonctionnement du serveur.
- *Réponse :* `Bonjour $name.`
- *Données :* pas de données.
💡 Vous pouvez utiliser les outils suivants :
- Diagrammes : [LucidCharts](https://lucidcharts.com/) ou Dia.
- Schémas plus avancés : Inkscape (mais potentiellement chronophage)
- Markdown : MarkText
## Implémentez votre serveur (1h)
Vous implémenterez votre serveur en vous basant sur le document de conception que vous aurez produit.
Dans un premier temps, vous implémenterez un `dry mode` (par opposition au `live mode`), permettant de simuler l'exécution d'une requête sans réellement appliquer les changements demandés (exécution à sec).
Le `dry mode` est très utilisé dans le cadre de tests, e.g., pour tester l'envoi d'une requête, et le traitement de sa réponse par une application cliente. L'avantage est ainsi d'effectuer des suites de tests sans réellement modifier l'état du serveur. Le `dry mode` est aussi utilisé par des commandes shells, e.g. `rsync --dry-run` ou `dpkg --dry-run`, afin de s'assurer que les paramètres de la commande produisent bien l'effet escompté avant de réellement l'exécuter.
Pour implémenter le `dry mode`, vous créerez des interfaces qui seront implémentées par des classes, e.g. :
```ts
enum Mode {
DRY,
LIVE
}
interface IUserManager {
create(name: string): IUser;
}
class LiveUserManager implements IUserManager {
override create(name: string) { /* live implementation */ }
}
class DryUserManager implements IUserManager {
override create(name: string) { /* dry implementation */ }
}
const dryUserManager = new DryUserManager ();
const liveUserManager = new LiveUserManager();
export function getUserManager(mode: Mode = Mode.LIVE) {
return mode === Mode.LIVE ? liveUserManager : dryUserManager;
}
```
```ts
export default function() {
const userManager = getUserManager(Mode.DRY);
// Pas de différences d'usage entre le mode dry et le mode live.
let user = userManager.create("toto");
user.getName();
//...
}
```
### [Avancé] Live mode
Si vous avez le temps, vous pourrez implémenter le mode `live` sur une VM.
Vous serez certainement amené à créer des scripts shells qui seront appelés par votre serveur HTTP. Vous les placerez alors dans le dossier `/server/scripts/`.
Les outils suivants pourront vous être utiles :
- `tail -F` pour écouter des fichiers de logs.
- inotifywait pour d'autres fichiers (<mark>TODO</mark>: commande exacte à retrouver + API Deno)
💡 Pour aisément passer d'un mode à l'autre, vous pouvez soit :
- préfixer vos routes par `live` ou `dry`, en fonction du mode souhaité.
- ajouter un paramètre `mode: "dry"|"live"` dans l'URL ou le corps de la requête.
## Attentes
- [ ] Utilisation de git.
- [ ] Présence et qualité des fichiers de documentations (`server/doc`, `server/README.md`).
- [ ] Qualité de la conception.
- [ ] 1 à 2 routes avec 4 méthodes HTTP pour chaque (récupérer, ajouter, modifier, supprimer), implémentées avec VSHS en TypeScript.
- [ ] Qualité du code.
- [ ] **[Avancé]** implémenter un Server-Sent Events pour donner, en temps réel, les différentes modifications survenant sur le serveur (e.g. un utilisateur a été créé).
- [ ] **[Avancé]** Implémenter le mode live.

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@ -0,0 +1,105 @@
Afin de fournir une interface graphique permettant d'administrer votre serveur, vous allez concevoir, puis implémenter, un site Web qui requêtera l'API REST que vous avez créé à la séance précédente afin d'interagir avec le serveur.
## Prise en main de LISS (20min)
Pour cela vous allez utiliser une bibliothèque développée par mes soins afin de vous simplifier le travail.
1. Clonez la bibliothèque LISS dans le dossier `/libs/LISS` :
`git submodule add git@github.com:denis-migdal/LISS.git ./libs/LISS` .
2. Installez `tsc`, puis créez un fichier `/client/tsconfig.json`.
3. Créez un dossier `/client/components/` qui contiendra vos différents composants Web.
4. Créez votre première page `/client/index.html` contenant votre premier composant :
- Balise : `<hello-world name='$name'></hello-world>`
- Affichage : `Bonjour $name`
5. Créez un fichier `/client/README.md` à compléter au fur et à mesure.
## Conception (50min)
Dans un premier temps, vous réfléchirez aux différents composants que votre serveur devra fournir. Pour cela, vous rédigerez un document Markdown dans `/client/doc/conception.md`. Vous devrez ainsi définir les différents composants de votre page Web, avec :
- leur interface graphique :
- leur contenu graphique.
- les intéractions utilisateurs, e.g. action lors d'un clic sur un élément.
- l'API offerte par vos composants :
- ses méthodes publiques.
- les événements qu'il lance.
- les requêtes REST qu'ils doivent effectuer.
Par exemple :
**Composants :**
- `<user-list> / UserList` : liste des utilisateurs (tableau).
- `<tr is='user-line'> / UserLine` : ligne de tableau représentant un utilisateur.
- cellule "nom"
- cellule "supprimer" (clic => `this.delete()`)
💡 Il est recommandé de représenter cela sous la forme d'un schéma, permettant de se faire une idée de la structure générale de la page finale. Il est potentiellement plus aisé le le faire au crayon à papier puis de scanner le schéma :![]()
![schéma.png](/home/demigda/Data/Recherche/Git/Notes-de-Cours/Web/PW/TP3%20-%20Client%20Web/schéma.png)
**APIs :**
- **UserLine :**
```ts
/*
Events:
- DELETED: (user: UserLine) => void
*/
class UserLine {
delete(): void;
}
```
**Processus :**
- **Supprimer un utilisateur (GUI) :**
Clic sur cellule "supprimer" de `<user-line>` -> `UserLine.delete()` -> `DELETE /users/{id}` -> `UserLine.dispatchEvent(DELETED)` -> `UserList.removeLine(line: UserLine)`.
💡 Vous pouvez représenter certains processus sous la forme d'un diagramme de flux :
![PW TP3.png](/home/demigda/Data/Recherche/Git/Notes-de-Cours/Web/PW/TP3%20-%20Client%20Web/PW%20TP3.png)
💡 Pour éviter d'y passer trop de temps, vous pouvez vous contenter d'un tableau :
| Processus | Action | Méthode | Requête REST | Événement |
| -------------------------------- | ------------------------------------------ | ------------------- | --------------------- | --------- |
| Suppression<br/>d'un utilisateur | Clic sur<br/>«delete» de<br/>`<user-line>` | `UserLine.delete()` | `DELETE /users/${id}` | `DELETED` |
💡 Vous pouvez utiliser les outils suivants :
- Diagrammes : [LucidCharts](https://lucidcharts.com/) ou Dia.
- Schémas plus avancés : Inkscape (mais potentiellement chronophage)
- Markdown : MarkText
## Implémentez votre site Web (50min)
Vous implémenterez votre site Web en vous basant sur le document de conception que vous aurez produit.
## Attentes
- [ ] Utilisation de git.
- [ ] Présence et qualité des fichiers de documentations (`client/doc`, `client/README.md`).
- [ ] Qualité de la conception.
- [ ] Au moins 1 à 2 composants Web implémentés avec LISS en TypeScript.
- [ ] Qualité du code.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

@ -1,540 +0,0 @@
---
gitea: none
include_toc: true
---
[TOC]
# JavaScript et TypeScript
Au semestre précédent, nous avons utilisé Brython afin d'exécuter du Python dans le navigateur. En réalité Brython transforme le code Python en JavaScript qui est ensuite interprété par le navigateur. En effet, le navigateur ne peut exécuter que deux langages :
- **WASM** : une sorte de langage bas niveau vers lequel d'autres languages peuvent être compilés (e.g. C, C++, Rust, Java, etc).
- **JavaScript**
JavaScript signale uniquement les erreurs d'exécutions. Ainsi certaines erreurs de programmations peuvent être silencieuses, ou ne pas être détectées lors de vos tests. Nous utiliserons TypeScript, un sur-ensemble de JavaScript permettant de détecter des erreurs en amont, facilitant grandement les étapes de tests et de développements.
Typescript permet d'indiquer le type des variables permettant à la fois de documenter l'usage des fonctions, mais aussi de vérifier et garantir qu'une fonction est bien appelée avec les bons paramètres. Ainsi en à peine quelques millisecondes, Typescript est capable de vous avertir d'erreurs de programmations que vous pourriez ne pas détecter, puis passer une journée à chercher. Il facilite aussi grandement les étapes de refactoring, e.g. lorsque la signature d'une fonction change, et requiert alors de changer son appel dans l'intégralité de votre code.
Au semestre précédent, nous avons utilisé l'API JS/DOM via Brython. Nous utiliserons la même API ce semestre, i.e. vous réutiliserez les mêmes fonctions et paramètres, seul le langage utlisé change.
## Utilisation de JavaScript
```html
<!DOCTYPE html>
<html>
<head>
<!-- ... -->
<script src="$JS_FILE" type="module" defer></script>
</head>
<body>
</body>
</html>
```
## Utilisation de TypeScript
TypeScript est tout d'abord converti en JavaScript à l'aide de la commande shell `tsc`. Les options sont soient données en paramètre de la commande, ou indiquées dans le fichier `tsconfig.json`.
L'option `--watch` permet de générer les fichiers JS à chaque modifications du fichier TS correspondant. Son avantage majeur est d'intégrer un cache en RAM permettant d'éviter de re-générer les fichiers JS de fichiers TS non-modifiés. Il permet ainsi un gain de temps significatif lors du développement, passant de plusieurs minutes de traduction pour de gros projets, à quelques millisecondes.
## Exécution de TypeScript côté serveur
JavaScript et TypeScript peuvent non seulement s'exécuter sur le navigateur, côté client, mais aussi côté serveur à l'aide d'interpréteurs comme `Node` ou `Deno`. Utiliser le même langage côté client et côté serveur possède plusieurs avantages :
- **factoriser** des parties de codes utilisés à la fois côté client et côté serveur.
- **déplacer** aisément des calculs du côté serveur vers le côté client et inversement.
- évite d'avoir à apprendre et **maîtriser deux langages** différents.
Dans le cadre de ce module, nous utiliserons `Deno` qui permet d'exécuter directement des fichiers TS sans avoir à générer des fichiers JS via `tsc`.
Nous étudierons plus en détails `Deno` dans le cours suivant.
# Types et structures de données natives
## Déclaration d'une variable
Contrairement à Python, JS possède 3 manières de déclarer une variable :
```javascript
let a = ...; // "block-scoped".
var a = ...; // "function-scoped".
const A = ...; // une constante, i.e. ne peut être modifiée.
```
*Note:* contrairement à Python, JS n'a pas besoin du mot clef `global` pour accéder à une variable globale. L'object `globalThis` contient l'ensemble des variables globales.
## Types de bases
JavaScript distingue 6 types de base :
| Déclaration | JS Type | Python Type |
| -------------------- | ----------- | ------------ |
| `let a = 42;` | `number` | `float` |
| `let a = true;` | `boolean` | `bool` |
| `let a = "str";` | `string` | `str` |
| `let a = undefined;` | `undefined` | ≈ `NoneType` |
| `let a = {a: 42}` | `object` | ≈ `dict` |
| `let a = foo;` | `function` | `function` |
Il existe plusieurs valeurs spéciales :
| Valeur | JS Type | En Python | Remarques |
| -------------------------- | ----------- | ----------------- | ------------------------------- |
| `NaN` | `number` | `float('nan')` | Not a Number (e.g. 0/0) |
| `Number.POSITIVE_INFINITY` | `number` | `float('inf')` | |
| `Number.NEGATIVE_INFINITY` | `number` | `float('-inf')` | |
| `0b01010111` | `number` | `0b01010111` | Nombre en binaire. |
| `0o777` | `number` | `0o777` | Nombre en octal |
| `0xFF` | `number` | `0xFF` | Nombre en hexadécimal |
| `1.2e2` | `number` | `1.2e2` | Nombre en notation scientifique |
| `true` | `boolean` | `True` | |
| `false` | `boolean` | `False` | |
| `` `val: ${value}` `` | `string` | `f"val: {value}"` | littéraux de gabarits |
| `null` | `object` | ≈ `None` | valeur "pas de valeurs" |
| `undefined` | `undefined` | ≈ `None` | valeur inexistante |
Pour retrouver le type d'une variable, vous pouvez utiliser `typeof $VAR`, qui est l'équivalent de `type($VAR)` en Python.
# Structures de données <mark>[TODO]</mark>
base object/array (vs tupple list python)
## Opérateurs
JavaScript utilise les mêmes opérateurs que Python à quelques exceptions près.
### Opérateurs bit à bits :
| Opérateur | Nom |
| --------- | ----------- |
| `a\|b` | OU binaire |
| `a&b` | ET binaire |
| `a^b` | XOR binaire |
| `~a` | NOT binaire |
### Opérateurs logique
| JavaScript | Python | Commentaires |
| ---------- | --------- | --------------------- |
| `a\|\|b` | `a or b` | OU logique |
| `a&&b` | `a and b` | ET logique |
| `!a` | `not a` | NON logique |
| `a??b` | | Coallescence des nuls |
💡 *Astuce :* l'opérateur de coallescence des nuls peut être utilisé pour indiquer des valeurs par défauts :
```javascript
let a ??= b; // si a est null ou undefined, a = b.
let a ||= b; // si a est évalué à false, a = b
```
### Opérateurs de comparaison
| JavaScript | Python | Commentaires |
| ---------- | ------------ | ---------------------------- |
| `a == b` | `a == b` | égalité de valeur (à éviter) |
| `a != b` | `a != b` | |
| `a === b` | `a is b` | égalité stricte |
| `a !== b` | `a is not b` | |
Opérateurs communs : `>`, `>=`, `<`, `<=`.
### Opérateurs arithmétiques
Opérateurs communs : `+` (binaire et unaire), `-` (binaire et unaire), `/`, `%`, `*`, `**`
| JavaScript | Python | Commentaires |
| ---------- | ------ | ---------------------------------- |
| `++a` | `a+=1` | incrément |
| `a++` | | retourne la valeur puis incrémente |
| `--a` | `a-=1` | décrément |
| `a--` | | retourne la valeur puis décrémente |
### Opérateurs d'affectations
Comme en python, il s'agit des opérateurs arithmétiques suffixé d'un `=`.
Par exemple `a += b` est équivalent à `a = a+b`.
### Autres opérateurs <mark>[TODO]</mark>
- destructuration / décomposition / chaînage opt (.?)
- []
- in
- delete
### Conversions
```javascript
let a = +$VALUE; // number
let a = !!$VALUE; // boolean
let a = (5).toString(); // string "5"
let a = (5).toString(2);// string "101"
```
📖 `0`, `""`, `null`, `undefined`, sont évalués à `false`.
## Indications de type avec Typescript
### Indication explicite de type
```typescript
let a = 42; // TS déduit que a est un number.
let a: number = 42; // on indique explicitement le type.
```
### Union de types
Il arrive qu'une variable puisse prendre des valeurs de plusieurs types différents :
```typescript
let a = 42; // TS déduit que a est un number.
a = "42"; // erreur, a ne peut être qu'un number.
let a: number | string = 42;
a = "42"; // légal
```
### Valeur optionnelle
Il arrive qu'une variable puisse ne pas avoir de valeurs à l'initialisation :
```javascript
let a: number; // erreur.
let a: number|undefined; // légal
```
### Alias de types
<mark>TODO: intérêt</mark>
```typescript
type X = number | string;
let a: X = 42; // équivaut à let a: number | string = 42;
```
### TS typeof
⚠ Il existe 2 `typeof`, un JS qui déduit le type de la **valeur** à l'exécution, et un TS qui, dans les indications de types, indique le type TS de la **variable**.
```typescript
type A = typeof a; // TS, number | string
console.log(typeof a); // JS, string
```
### Types pour les structures de données
- generics TS `Array<X>` / types array / `Record<x, y>` / typescript []
# Structures de contrôle
## Conditions
```javascript
// [JS] Javascript
if( $COND2 ) {
} else if($COND2) {
} else {
}
```
```python
# [🐍] Python
if $COND1:
pass
elif $COND2
pass
else
pass
```
## Opérateur ternaire
```javascript
// [JS] Javascript
let a = $COND ? $IF_TRUE : $IF_FALSE
let a = $COND
? $IF_TRUE
: $IF_FALSE
```
```python
# [🐍] Python
a = $IF_TRUE if $COND else $IF_FALSE
```
## Switch
```javascript
// [JS] Javascript
switch(value) {
case 'value':
//...
break;
default:
//...
}
```
```python
# [🐍] Python
match value:
case 'value':
pass
case _:
pass
```
## Boucles
### While
```javascript
// [JS] Javascript
while($COND) {
}
```
```python
# [🐍] Python
while $COND:
pass
```
### Do while
```javascript
// [JS] Javascript
do {
// s'exécute au moins une fois.
} while( $COND );
```
### For in (parcours les clefs)
```javascript
// [JS] Javascript
for( let value in $OBJ ) {
}
```
### For of (parcours les valeurs)
```javascript
// [JS] Javascript
for( let value of $OBJ ) {
}
```
## Exceptions
```javascript
// [JS] Javascript
try {
throw new Error($MESSAGE)
} catch(error) {
} finally {
}
```
```python
# [🐍] Python
try:
raise Exception($MESSAGE)
except Exception as error:
pass
finally:
pass
```
# Fonctions
## Déclaration
```javascript
// [JS] JavaScript
function foo(arg1, arg2 = "2") {
//...
return 2.3;
}
foo(1)
```
```python
# [🐍] Python
def foo(arg1, arg2 = "2", /): # les paramètres positionnel uniquement
# ...
return 2.3
foo(1)
```
```typescript
//[TS] TypeScript
function foo(arg1: number, arg2?: string = "2"): number {
// ...
return 2.3;
}
foo(1)
```
## Paramètre du reste (rest parameter)
```javascript
// [JS] JavaScript
function foo(arg1, ...arg2) {
// arg2 = ["2", "3"]
return 2.3;
}
foo(1, "2", "3")
```
```python
# [🐍] Python
def foo(arg1, /, *arg2):
# arg2 = ("2", "3")
return 2.3
foo(1, "2", "3")
```
```typescript
//[TS] TypeScript
function foo(arg1: number, ...arg2: readonly string[]): number {
// arg2 = ["2", "3"]
    return 2.3;
}
foo(1, "2", "3")
```
## Opérateur de décomposition (spread operator) comme argument
```javascript
// [JS] JavaScript
function foo(arg1, arg2, arg3) {
//...
return 2.3;
}
const args = ["2", "3"];
foo(1, ...args)
```
```python
# [🐍] Python
def foo(arg1, arg2, arg3, /):
# ...
return 2.3
args = ("2", "3")
foo(1, *args )
```
```typescript
//[TS] TypeScript
function foo(arg1: number, arg2: string, arg3: string): number {
return 2.3;
}
const args = ["2", "3"] as const; // ou as ["string", "string"]
foo(1, ...args)
```
## Fonctions génératrices
<mark>TODO: utilité</mark>
```javascript
// [JS] JavaScript
function * foo(nb) {
for(let i = 0; i < nb; ++i)
yield i;
}
for( let val of foo(5) )
console.log(val);
```
```python
# [🐍] Python
def foo(nb, /) {
for i in range(nb):
yield i;
}
for val in foo(5)
console.log(val);
```
```typescript
//[TS] TypeScript
function * foo(nb: number): Generator<number> {
for(let i = 0; i < nb; ++i)
yield i;
}
for(let val of foo(5) )
console.log(val);
```
## Fonctions fléchées
<mark>TODO</mark>: pas d'équivalent en python + utilité (callbacks)
```javascript
const foo = arg => arg[0]; // est équivalent à :
const foo = function(arg){ return arg[0]; }
// si plusieurs paramètres, utiliser des parenthèses ().
// si valeur retournée plus complexe, utiliser des accolades {}.
const foo = (arg1, args) => { ... ; return x; } // est équivalent à :
const foo = function(arg1, arg2){ ... ; return x; }
```
# Asynchronisme
/!\ Diff with Python
- Promise
- motivation + pas bloquer thread principal
# CM2
## Fonctions avancées ?
## Classes
- class + getter/setter + private/protected + interface + iterator / async inter / héritage + fonctionnement prototype (méthode => fct et inversement + bind)
- - ts enum + mixins
- + well-known symbols
## Importations et exportations (CM2?)
## Quelques API utiles (CM2 ?)
- Math API
- Crypto
- Date / RegExp
- object / array / Map / Set / string APIs
## Futur
- Manipuler URL => move 2 JS-DOM API ?
- console/assert => move 2 JS-DOM API ?
Loading…
Cancel
Save