---
# System prepended metadata

title: Timmi.PWA
tags: [Timmi]

---

---
tags: Timmi
---


# Timmi.PWA

## Architecture globale

La PWA Timmi utilisera la technologie Angular Microfrontend basée sur Webpack Module Federation.

Le principe est de pouvoir faire cohabiter et interagir des modules issus de compilation différentes. Voici ce qui change par rapport aux applications habituelles.

### Angular "Classique" - exemple de Figgo.Flex

Quand on compile on obtient les fichiers suivants (simplifiés) : 

```
Initial Chunk Files    | Names                |  Raw Size
main.js                | main                 | 768.16 kB
styles.css             | styles               | 584.54 kB
polyfills.js           | polyfills            |  36.29 kB
runtime.js             | runtime              |   4.09 kB

                       | Initial Total        |   1.36 MB

Lazy Chunk Files       | Names                |  Raw Size
pages_dashboard.js     | pages-dashboard      | 216.61 kB
pages_schedule.js      | pages-schedule       | 200.30 kB
pages_integrations.js  | pages-integrations   | 103.38 kB
pages_settings.js      | pages-settings       |  19.33 kB
```


Le fonctionnement est le suivant : 
1. L'utilisateur va sur https://lucca.ilucca.net/work-locations et charge la section "Initial Chunk Files" qui contient, pour faire simple, Angular et le style global
2. L'utilisateur est redirigé vers https://lucca.ilucca.net/work-locations/schedule. Seul le fichier `pages_schedule.js` est chargé. Il contient l'ensemble du code permettant d'afficher le planning
3. Si l'utilisateur décide d'aller sur la paramétrage, seul le fichier `pages_settings.js` sera chargé puis exécuté

Ce système s'appelle le *lazy-loading*, il permet de réduire la quantité de script chargé au démarrage (1.36MB au lieu de 1.93MB) en ne chargeant que ce qui est nécessaire. Ce système permet d'avoir des applications qui peuvent grossir sans que ça n'impacte le temps de chargement de la page principal.

Si on transpose ce système sur Timmi.PWA, on pourrait avoir quelque chose comme : 

```
Initial Chunk Files    | Names
main.js                | main
styles.css             | styles
polyfills.js           | polyfills
runtime.js             | runtime

Lazy Chunk Files       | Names
pages_leaves.js        | pages-leaves
pages_times.js         | pages-times
pages_offices.js       | pages-offices
pages_account.js       | pages-account
```

Le problème est que pour arriver à ça il faudrait que tous ces fichiers soient issus de la même compilation (donc monorepo ou système de partage de code via NPM).

### Angular Micro Frontend

Dans ce système là, l'objectif est de fusionner le script généré par des compilations de plusieurs applications. Il y a deux types d'application : 

* le *"Shell"* qui est l'application qui va héberger les autres. Dans notre cas, il contiendra le pied de page de navigation et les pages non liées à un métier spécifique (absence, temps, bureau).
* les *"Remotes"* qui sont des applications qui peuvent fonctionner indépendamment mais qui exposent un manifeste ainsi que du script qui pourra être consommé par l'application *"Shell"*.

**Shell : Timmi PWA (/timmi/pwa)**

```
Initial Chunk Files    | Names
main.js                | main
styles.css             | styles
polyfills.js           | polyfills
runtime.js             | runtime

Lazy Chunk Files       | Names
pages_account.js       | pages-account
```

**Remote 1: Timmi Temps (/timmi)**

```
Files                  | Names
remoteEntry.js         | manifeste
timmi_time.js          | timmi-time
```

**Remote 2 : Timmi Office (/work-locations)**

```
Files                  | Names
remoteEntry.js         | manifeste
timmi_office.js        | timmi-office
```

**Remote 3 : Timmi Absenses (/timmi/leaves)**

```
Files                  | Names
remoteEntry.js         | manifeste
timmi_leaves.js        | timmi-leaves
```

Chacune des 4 applications est dans un *repository* différent et est compilé séparément.

Le fonctionnement est le suivant : 
1. L'utilisateur va sur https://lucca.ilucca.net/timmi/pwa et charge la section "Initial Chunk Files" qui contient le code du shell, Angular et Lucca Front
2. L'utilateur décide d'aller saisir une feuille de temps, le fichier `remoteEntry.js` de Timmi Temps est chargé, puis `timmi_time.js`
3. Si l'utilisateur décide ensuite d'aller poser un congé, le fichier `remoteEntry.js` de Timmi Absences est chargé, puis `timmi_leaves.js`

Il est possible de lister l'ensemble des dépendances qui seront partagées entre le shell et les remotes : 

* @angular/core
* @angular/common
* @angular/common/http
* @angular/router

Le problème est de n'avoir qu'une fois Angular de chargé et ce, dans la bonne version. Cette problématique est gérée par le ModuleFederationPlugin de Webpack !

### Expérimentation sur les versions

Soit la situation suivante : 
* ShellV1 déclare `@angular/core` en version `13.3.7`
* ShellV2 déclare `@angular/core` en version `13.3.8`
* ShellV3 déclare `@angular/core` en version `~13.3.0` (qui vaut `13.3.8` au moment de l'installation)
* RemoteV1 déclare `@angular/core` en version `13.3.7`
* RemoteV2 déclare `@angular/core` en version `13.3.8`
* RemoteV3 déclare `@angular/core` en version `~13.3.0` (qui vaut `13.3.8` au moment de l'installation)

Le but est de voir quels couple ShellVX et RemoteVY fonctionnent : 

| Shell \ Remote | V1 13.3.7 | V2 13.3.8 | V3 ~13.3.0 |
|----------------|-----------|-----------|------------|
| V1 13.3.7      |        OK |    ERROR1 |         OK |
| V2 13.3.8      |    ERROR2 |        OK |         OK |
| V3 ~13.3.0     |    ERROR3 |        OK |         OK |

```
ERROR1: Unsatisfied version 13.3.7 from shell of shared singleton module @angular/router (required =13.3.8)
ERROR2: Unsatisfied version 13.3.8 from shell of shared singleton module @angular/router (required =13.3.7)
ERROR3: Unsatisfied version 13.3.8 from shell of shared singleton module @angular/router (required =13.3.7)
```

Il y a une erreur quand le Shell n'offre pas la version requise par le Remote. Dans ce cas, seul le fichier `remoteEntry.js` est chargée, puis l'import au module plante.

Pour limiter les erreurs, il faut mettre une version "lâche" côté Remote (comme le ~13.3.0 de l'exemple). Se pose alors la question des montées de version Angular !

## Gestion des montées de version des dépendances partagées

### Cas d'un patch (passe d'Angular 13.3.7 à 13.3.8)

Si les remotes ont bien la version "lâche" `~13.3.0`, alors il suffit de mettre à jour le Shell, et de redéployer le shell uniquement.

### Cas d'une majeur (passe d'Angular 13.3.8 à 14.0.0)

Dans ce cas, impossible de faire "comme d'habitude" : une mise à jour du Shell ou du Remote entraîne une erreur car les versions d'Angular ne sont pas compatibles. Une mise en production synchronisée ne répond pas à tous les problèmes car, comme pour les applications front, les navigations des clients ayant chargé le shell en version 13 vers un Remote en version 14 planteraient.

Pour résoudre ce problème, il faut versionner les remotes :

1. Timmi PWA en Angular 13 pointe vers Timmi Office v1 qui est en Angular 13
2. Timmi Office met en ligne la V2 en Angular 14 **tout en continuant de diffuser la V1**
3. Timmi PWA fait sa montée de version en Angular 14 et fait pointer Timmi Office sur la V2
4. Au bout d'un moment, on décommissionne la V1 de Timmi Office

TODO: hébergement V1/V2
TODO: recette / preview

## Découpage du code

Le code sera découpé entre plusieurs *repository* : 

* Le shell sera dans [Timmi.PWA](https://github.com/LuccaSA/Timmi.PWA)
* Les remotes seront à côté de leur application front respective

```plantuml
frame "Timmi.Office" {
    node AppModule as OfficeAppModule {
        node styles.scss as officeStyles
    }
    
    node TimmiOfficePwaModule {
        node pwa.scss as officePwaStyles
    }
    
    OfficeAppModule --> TimmiOfficePwaModule
}

frame "Timmi.Timesheet" {
    node AppModule as TimesheetAppModule {
        node styles.scss as timesheetStyles
    }
    
    node TimmiTimesheetPwaModule {
        node pwa.scss as timesheetPwaStyles
    }
    
    TimesheetAppModule --> TimmiTimesheetPwaModule
}

frame "Timmi.Leaves" {
    node AppModule as LeavesAppModule {
        node styles.scss as leavesStyles
    }
    
    node TimmiLeavesPwaModule {
        node pwa.scss as leavesPwaStyles
    }
    
    LeavesAppModule --> TimmiLeavesPwaModule
}

frame "Timmi.PWA" {
    node AppModule as PWAAppModule {
        node styles.scss
    }
    
    PWAAppModule ----[bold]-> TimmiOfficePwaModule
    PWAAppModule ----[bold]-> TimmiLeavesPwaModule
    PWAAppModule ----[bold]-> TimmiTimesheetPwaModule
}
```

## Hébergement

## Traductions

Transloco sera utilisé de partout.

Chaque *remote* utilise un scope : 
* `office.*` pour Timmi Office
* `timesheet.*` pour Timmi Timesheet
* `leaves.*` pour Timmi Absences

Le shell s'occupe de fournir un `TranslocoLoader` qui va appeler la bonne URL en fonction du scope : 
* vers https://timmi-pwa.ilucca.net/office/v7/assets/fr.json pour Timmi Office
* vers https://timmi-pwa.ilucca.net/timesheet/v10/assets/fr.json pour Timmi Timesheet
* vers https://timmi-pwa.ilucca.net/leaves/v1/assets/fr.json pour Timmi Absences

> URLs non contractuelles

TODO Gérer le *cache-busting*

## Ressources

https://levelup.gitconnected.com/your-first-angular-microfrontend-58950768a465

https://webpack.js.org/concepts/module-federation/

https://github.com/angular-architects/module-federation-plugin/blob/main/libs/mf/README.md