Skip to content

Commit

Permalink
Add ESM - part 2
Browse files Browse the repository at this point in the history
  • Loading branch information
octo-topi committed Sep 6, 2023
1 parent e159aa1 commit d967a16
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 25 deletions.
50 changes: 25 additions & 25 deletions _posts/2023-08-25-migrer-de-commonjs-vers-esm.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ authors: pierre_top
---

## Y'a quoi au menu ?
Cet article partage avec vous deux mois d'efforts (et de doutes, aussi) pour migrer une base de code NodeJS de 400 kLoC. Spoiler : nous y sommes arrivés !
Cet article partage avec vous deux mois d'efforts (et de doutes, aussi) pour migrer une base de code NodeJS de 400 kLoC. Spoiler : nous y sommes arrivés !

Vous y trouverez des considérations techniques et organisationnelles, de la syntaxe et de l'outillage, du monitoring : il y en a pour tous les goûts.

Expand All @@ -37,9 +37,9 @@ Et si vous êtes gourmands, lisez tout du début à la fin...

### Une histoire de modules

Dans la plupart des langages de programmation (C, Java, JavaScript...), le fichier de code (source) est un élément important. Il est implémenté dans le système de fichiers de l'OS, par un fichier texte. Il y a quelque chose de fondamentalement réconfortant à cela. Utiliser un langage qui stocke le code source dans des fichiers aux noms cryptiques et au contenu binaire, c'est une perte d'autonomie.
Dans la plupart des langages de programmation (C, Java, JavaScript...), le fichier de code (source) est un élément important. Il est implémenté dans le système de fichiers de l'OS, par un fichier texte. Il y a quelque chose de fondamentalement réconfortant à cela. Utiliser un langage qui stocke le code source dans des fichiers aux noms cryptiques et au contenu binaire, c'est une perte d'autonomie.

Pourquoi ? Parce que ranger des fichiers dans des dossiers est une métaphore puissante : elle permet de tracer des frontières dans sa tête, et d'oublier les particularités pour un moment. C'est la même chose qui se passe au niveau en-dessous lorsqu'on crée des fonctions.
Pourquoi ? Parce que ranger des fichiers dans des dossiers est une métaphore puissante : elle permet de tracer des frontières dans sa tête, et d'oublier les particularités pour un moment. C'est la même chose qui se passe au niveau en-dessous lorsqu'on crée des fonctions.

> Aside from the computer itself, the routine is the single greatest invention in computer science. (...) Create a routine to hide information so that you won't need to think about it. (...) Without the abstractive power of routines, complex programs would be impossible to manage intellectually.
Expand All @@ -53,19 +53,19 @@ La notion de module est un assez trop large en Javascript, on désigne parfois l

#### Tout débute dans un navigateur

Comme Javascript était initialement (1995) prévu comme un langage limité, dans le navigateur, il n'y avait pas de système de module natif : tout le code était contenu dans la balise <script></script>. Au fur et à mesure que les besoins côté navigateur grandissaient ("comportement dynamique"), ils ont été résolus en Javascript. Comme une partie de ces besoins étaient communs à toutes les équipes (ex : appeler une API REST), les librairies ont été développées. Elles pouvaient être importées depuis un fichier via l'attribut `src` : `<script src="my-library.js">`. Le code de l'application (composant) pouvait aussi être modularisé de cette manière.
Comme Javascript était initialement (1995) prévu comme un langage limité, dans le navigateur, il n'y avait pas de système de module natif : tout le code était contenu dans la balise <script></script>. Au fur et à mesure que les besoins côté navigateur grandissaient ("comportement dynamique"), ils ont été résolus en Javascript. Comme une partie de ces besoins étaient communs à toutes les équipes (ex : appeler une API REST), les librairies ont été développées. Elles pouvaient être importées depuis un fichier via l'attribut `src` : `<script src="my-library.js">`. Le code de l'application (composant) pouvait aussi être modularisé de cette manière.

#### Un petit tour côté serveur

Le passage de Javascript côté serveur (2009) changea l'approche.

Dans NodeJS, les libraires (packages) :
- déclarent leur interface (API) avec le mot-clef `module.exports` (notion de module)
- déclarent leur interface (API) avec le mot-clef `module.exports` (notion de module)
- sont enregistrées par l'application dans le fichier `package.json`
- sont installées par npm dans le fichier `node_modules` (encore le module)
- sont importées avec le mot-clef `require`.

Les composants de l'application utilisent les mêmes mot-clefs `require/module.exports`. Il n'y a pas de différence visible entre l'import d'une libraire et d'un composant, à part le fait qu'une libraire est mentionnée par son nom, et le composant par son chemin.
Les composants de l'application utilisent les mêmes mot-clefs `require/module.exports`. Il n'y a pas de différence visible entre l'import d'une libraire et d'un composant, à part le fait qu'une libraire est mentionnée par son nom, et le composant par son chemin.

```javascript
// library
Expand Down Expand Up @@ -99,7 +99,7 @@ La base de code Pix, créée en 2011, utilise le format CJS. Si le format qu'ell
Si vous n'êtes pas dans cette situation, et cherchez ce que ESM pourrait vous apporter, je conseille [cet article](https://webreflection.medium.com/cjs-vs-esm-5f8b90a4511a), écrit par un membre de [l'équipe](https://github.com/nodejs/modules) qui a implémenté ESM dans NodeJS
> CJS vs ESM is not a war, rather a topic to talk even more about, once constraints and respective features are clear, as opposite of just taking a side out of habits, or effort needed, to move on (to ESM).

Je liste tout de même les avantages d'expérience de développement après quelques mois d'utilisation :
Je liste tout de même les avantages d'expérience de développement après quelques mois d'utilisation :
- [mode strict](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Strict_mode) actif ;
- autocomplétion des imports par les IDE ;
- vérification [statique](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/named.md) des imports, absente en CJS;
Expand Down Expand Up @@ -129,7 +129,7 @@ module.exports = {

Et voilà la version équivalente en ESM :
- les imports sont visibles en haut du fichier ;
- l'interface (= les exports) est claire.
- l'interface (= les exports) est claire.
```javascript
import { Ham } from './Ham.js';
Expand Down Expand Up @@ -165,29 +165,29 @@ export {
Pour que vous puissiez suivre la démarche de migration, il est important d'avoir les bases de la syntaxe ESM. Je pars du principe que vous connaissez la syntaxe CJS.

Tous les composants (nombre, objets, fonctions, classes) peuvent être exposés dans les modules ESM. Ils portent un nom d'export. Celui-ci peut être remplacé par un autre lors de l'import dans un autre module. Il est aussi possible de supprimer leur nom à l'export, pour le remplacer par le nom `default` : il ne s'agit pas à proprement parler d'export anonyme, simplement d'un export nommé `default`, aussi appelé export par défaut.
Tous les composants (nombre, objets, fonctions, classes) peuvent être exposés dans les modules ESM. Ils portent un nom d'export. Celui-ci peut être remplacé par un autre lors de l'import dans un autre module. Il est aussi possible de supprimer leur nom à l'export, pour le remplacer par le nom `default` : il ne s'agit pas à proprement parler d'export anonyme, simplement d'un export nommé `default`, aussi appelé export par défaut.

Les imports et exports doivent être à la racine du fichier (`top-level`).
Ils sont interdits dans une structure de contrôle ou une fonction.

Les imports doivent figurer en début de fichier.
Les imports doivent figurer en début de fichier.
Leur syntaxe est la suivante, du plus simple au plus complexe :
- `import from './module.js'` : si l'on ne veut pas récupérer d'import, uniquement exécuter du code
- `import module from './module.js'` : pour récupérer l'export par défaut et le nommer `module`
- `import { foo, bar } from './module.js'` : pour récupérer certains exports nommés
- `import { foo , bar as baz } from './module.js'` : même chose, en les renommant
- `import * as module from './module.js'` : pour récupérer tous les exports nommés dans un objet
- `import from './module.js'` : si l'on ne veut pas récupérer d'import, uniquement exécuter du code
- `import module from './module.js'` : pour récupérer l'export par défaut et le nommer `module`
- `import { foo, bar } from './module.js'` : pour récupérer certains exports nommés
- `import { foo , bar as baz } from './module.js'` : même chose, en les renommant
- `import * as module from './module.js'` : pour récupérer tous les exports nommés dans un objet
La syntaxe des exports est la suivante, du plus simple au plus complexe :
- `export foo;` : exporter un objet, peut être invoqué plusieurs fois
- `export default foo` : export par défaut (l'objet exporté perd son nom `foo` pour prendre celui de `default`)
- `export { foo, bar }` : export de plusieurs objets
- `export { foo , bar as baz }` : même chose, avec renommage
- `export { foo, bar } from './module.js` : vous avez bien lu, on importe les exports nommés et on les exporte (ré-export)
- `export * from './module.js` : même chose, pour sélectionner tous les exports nommés
- `export foo;` : exporter un objet, peut être invoqué plusieurs fois
- `export default foo` : export par défaut (l'objet exporté perd son nom `foo` pour prendre celui de `default`)
- `export { foo, bar }` : export de plusieurs objets
- `export { foo , bar as baz }` : même chose, avec renommage
- `export { foo, bar } from './module.js` : vous avez bien lu, on importe les exports nommés et on les exporte (ré-export)
- `export * from './module.js` : même chose, pour sélectionner tous les exports nommés


Si vous êtes curieux de connaître l'implémentation de ESM, ce [cartoon](https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/) est une excellente introduction sur les modules en général et ESM en particulier.
Si vous êtes curieux de connaître l'implémentation de ESM, ce [cartoon](https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/) est une excellente introduction sur les modules en général et ESM en particulier.
### Modifier le code
Expand Down Expand Up @@ -258,13 +258,13 @@ https://github.com/1024pix/pix/pull/5731
## En approchant de la production
### Le suivi et la motivation
L'équipe de production a affiché l'avancée de la migration sur [Are we ESM yet ?](https://1024pix.github.io/areweesmyet/).
L'équipe de production a affiché l'avancée de la migration sur [Are we ESM yet ?](https://1024pix.github.io/areweesmyet/).
C'est une [Github page](https://github.com/1024pix/areweesmyet) avec un appel à l'API de la CI qui affiche l'avancement des tests, simple, mais efficace. Elle rend le travail et les difficultés visibles à tous.

![dashboard code](/assets/images/posts/migrer-de-commonjs-vers-esm/are-we-esm-yet.png)

### Comment tester hors CI ?
### Comment tester hors CI ?

https://1024pix.atlassian.net/wiki/spaces/TC1/pages/3754164240/D+ploiement+de+l+API+en+ESM

Expand All @@ -277,7 +277,7 @@ https://github.com/1024pix/pix/blob/dev/mon-pix/servers.conf.erb#L30
Modification du workflow Slack
![dashboard canary](/assets/images/posts/migrer-de-commonjs-vers-esm/canary.png)
2 semaines de déploiement progressif: front, workers..
2 semaines de déploiement progressif: front, workers..
## Happy end
Expand Down
122 changes: 122 additions & 0 deletions _posts/2023-09-06-migrer-de-commonjs-vers-esm-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
---
layout: post
title: "Migrer de CommonJS vers ECMAScript Modules - #2"
date: 2023-09-06 14:04:00 +0200
categories: javascript
excerpt: "Récit d'une aventure : migrer 400kLoc NodeJs en deux mois - Le faire"
cover:
image: "cjs-to-esm.png"
source_url: https://raw.githubusercontent.com/wessberg/cjstoesm/master/documentation/asset/logo.png
authors: pierre_top
---

## Y'a quoi au menu ?

Cet article est la suite de [la théorie sur la migration ESM]({% post_url 2023-08-25-migrer-de-commonjs-vers-esm %}).

Il explique comment nous nous y sommes pris pour migrer l'API du monorepo (Pix)[https://github.com/1024pix/pix/tree/dev/api] qui fait 400kLoc, en deux mois.

Si vous n'avez que cinq minutes, lisez [la stratégie](#la-stratégie).

Si vous avez un peu plus de temps, choisissez dans le menu.

Voilà les plats principaux :
- monter et faire vivre [l'équipe dédiée](#léquipe)
- [la stratégie](#la-stratégie) de migration
- faire de la [mise en production](#en-approchant-de-la-production) un non-évènement

Et si vous êtes gourmands, lisez tout du début à la fin...

## L'équipe

## La stratégie

Des compromis

### Un feedback rapide

Migrer lib et tester manuellement pour voir si ça marche
Puis migrer les tests

Le choix de corriger les syntaxes CJS incompatibles sur place pour
- bénéficier des tests automatisés
- merger rapidement

https://github.com/1024pix/pix/pull/5715
https://github.com/1024pix/pix/pull/5747

### Choisir ses combats

On ne pouvait pas gérer toute la syntaxe, on a écrit les codemods obligatoires
Le reste est modifié manuellement. Ce n'est pas un échec.


Cela aboutit, après plusieurs tentatives, à ce ![workflow de développement](/assets/images/posts/migrer-de-commonjs-vers-esm-migration/codemod-workflow.png)

"Codemodception"


### Le choix des exports nommés

Sondage dans les équipes

Exception : les fichiers qui n'exportent qu'un seul objet (use-case)
https://github.com/1024pix/pix/pull/5787/files#diff-734269ab23f38145d1f6742bd4282022dc6d47a8f57c3e2b372e45da7f1d88f4



Ou alors on transforme les exports anonymes CSJ en exports nommés CJS (et les imports aussi).

Ca permet de lancer les tests en CJS donc d’aller plus vite.

Et du coup il reste plus que deux codemods CJS=>ESM qui ne gèrent que les imports/exports nommés (simples!).

générer un import nommé ESM (on part du principe que le nom de la variable est prévisible, c’est le nom du fichier snake-case transformé en camelCase/PascalCase)

https://github.com/1024pix/pix/pull/5731

### Les tests mockés et l'injection de dépendance


## En approchant de la production

### Le suivi et la motivation
L'équipe de production a affiché l'avancée de la migration sur [Are we ESM yet ?](https://1024pix.github.io/areweesmyet/).

C'est une [Github page](https://github.com/1024pix/areweesmyet) avec un appel à l'API de la CI qui affiche l'avancement des tests, simple, mais efficace. Elle rend le travail et les difficultés visibles à tous.

![dashboard code](/assets/images/posts/migrer-de-commonjs-vers-esm-migration/are-we-esm-yet.png)

### Comment tester hors CI ?

https://1024pix.atlassian.net/wiki/spaces/TC1/pages/3754164240/D+ploiement+de+l+API+en+ESM

On s'est posé la question un peu tard, brainstorming avec les [captains](/organisation/2020/04/14/les-capitaines-de-la-production.html)

Parler du canary
https://github.com/1024pix/pix-dev-tools/tree/main/captain/canary-releases
https://github.com/1024pix/pix/blob/dev/mon-pix/servers.conf.erb#L30

Modification du workflow Slack
![dashboard canary](/assets/images/posts/migrer-de-commonjs-vers-esm-migration/canary.png)

2 semaines de déploiement progressif: front, workers..


## Happy end

Le 1er juin 2023, 3 mois après avoir donné le coup d'envoi, toute la production tourne en ESM
https://1024pix.github.io/areweesmyet/

![dashboard code](/assets/images/posts/migrer-de-commonjs-vers-esm-migration/are-we-esm-yet.png)

## Plus

### Le code
La [pull request principale](https://github.com/1024pix/pix/pull/5787) et ses 132 commits
Zoom sur les codemods

### Workflow canary
Voilà la stratégie de déploiement
![workflow canary](/assets/images/posts/migrer-de-commonjs-vers-esm-migration/workflow-canary.png)

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit d967a16

Please sign in to comment.