@edugouvfr/ngx-dsfr
Version:
NgxDsfr est un portage Angular des éléments d'interface du Système de Design de l'État Français (DSFR).
453 lines (338 loc) • 15.2 kB
Markdown
# @edugouvfr/ngx-dsfr - Guide de contribution
Ce fichier présente les conventions de formalisme et les règles de conception suivies sur ce projet.
- [@edugouvfr/ngx-dsfr - Guide de contribution](#edugouvfrngx-dsfr---guide-de-contribution)
- [Environnement](#environnement)
- [Pré-requis](#pré-requis)
- [Démarrer](#démarrer)
- [Règles de nommage](#règles-de-nommage)
- [Organisation](#organisation)
- [Code style](#code-style)
- [Format messages de commit](#format-messages-de-commit)
- [Format d'entrée du changelog](#format-dentrée-du-changelog)
- [Versionnement](#versionnement)
- [Nommage de branche](#nommage-de-branche)
- [Merge Request](#merge-request)
- [Branches d'intégration](#branches-dintégration)
- [Règles de conception](#règles-de-conception)
- [Créer un composant ](#créer-un-composant-)
- [Guide de développement](#guide-de-développement)
- [Nommage](#nommage)
- [Typescript](#typescript)
- [Processus de dépréciation](#processus-de-dépréciation)
- [Storybook](#storybook)
- [Créer les stories](#créer-les-stories)
- [Ajouter la documentation](#ajouter-la-documentation)
- [Paramétrer la story](#paramétrer-la-story)
- [Mainteneurs : créer une release ](#mainteneurs--créer-une-release-)
## Environnement
### Pré-requis
- node: "^20.19.0 || ^22.12.0 || >=24.0.0"
- angular >= 19.2
### Démarrer
- Installer les dépendances
```bash
npm install
```
- Au premier lancement générer le dossier documentant l'api publique
```bash
npm run apidoc
```
ou
```bash
npm run storybook:build
```
- Lancer Storybook (port 6006 par défaut)
```bash
npm run storybook:start
```
## Règles de nommage
- Tous les types, interfaces, classes, components, etc. exportés de la librairie doivent être préfixées par `Dsfr`
- Les sélecteurs des composants exportés sont préfixés par `dsfr-`
- Les sélecteurs des composants internes sont préfixés par `edu-`
- Les constantes sont présentées sous forme de type (ex : `DsfrSize`) et de constantes (ex: `DsfrSizeConst`)
- Les deux doivent être exportés dans l'api publique de la lib
## Organisation
- Chaque composant est placé dans son répertoire dédié nommé à partir du nom de l'élément DSFR (ex: `button`)
- Les matériaux partagés (utilitaires, composants, etc.) sont placés dans le répertoire `shared`
## Code style
- Formatage : Tout fichier de code (html, js/ts, css/scss, json, etc.) doit être formaté automatiquement lors de la
sauvegarde avec Prettier
## Format messages de commit
- Utiliser les conventions [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/)
- Scoper autant que possible avec le nom de l'élément DSFR concerné. Ex. `feat(button): add new capacity`
| Préfixe | Usage |
| ------- | ----------------------------------------------------------------------------------------------- |
| fix | Introduction d'un correctif rétro-compatible |
| feat | Introduction d'une nouvelle fonctionalité rétro-compatible |
| break | Introduction d'un changement non-rétrocompatible |
| doc | Modification de la documentation |
| chore | Tout autre changement non perceptible par l'utilisateur de la lib (code style, build, ci, etc.) |
## Format d'entrée du changelog
Utiliser les même conventions que pour les commits.
> Format : `<type>(scope): description`
- Le préfixe (type) doit être introduit par un tiret (item de liste markdown).
- Si le changement concerne un composant, le scope doit correspondre au suffixe du sélecteur du composant
- La description doit être courte (éviter les déterminants, conjonctions, etc.)
- La longueur d'une entrée de changelog ne doit pas dépasser 120 caractères
## Versionnement
- La bibliothèque utilise le formalisme [semver](https://semver.org/lang/fr/)
- Pour les versions de pré-livraisons un suffixe alpha, beta et rc doit être utilisé avec le format suivant :
- regex: `/\d+\.\d+\.\d+(-(alpha|beta|rc)\.\d+)?/g`
- ex. : `0.1.0-beta.6`, `1.0.0-rc.1`,
## Nommage de branche
- Le nom de la branche doit êre préfixé en utilisant l'un des type autorisés pour les entrées de changelog
- Le préfixe doit être suivi d'un slash puis du nom descriptif de la branche
- Le nom descriptif ne doit pas excéder 3 à 4 mots séparés d'un tiret
- Le nom complet de la branche ne doit utiliser que des caracères minuscules
## Merge Request
- Chaque nouvelle branche concernant un fix ou une feature doit être soumise en MR.
- La MR doit être créée en `Draft` tant qu'elle n'est pas prête pour la relecture.
- Lier le ticket concerné au titre de la MR et ajouter une description si nécessaire.
- La MR doit cibler la branche d'intégration et doit impérativement pouvoir être fusionnée en fast-forward.
- Le cas échéant, utiliser la commande `rebase` pour resynchroniser votre feature-branch avec la branche cible.
### Branches d'intégration
- Hotfixes : branche spécifique de `hotfix/numero-de-version`
- Features : `develop`
Toutes les livraisons sont faites depuis la branche `master`.
Celle-ci intègre les branches de `hotfix` et `develop` (en fast-forward) avant une release. Les branches de hotfixes sont
supprimées une fois la livraison de la version concernée effectuée et la branche mergée dans `develop`.
### Créer un composant <a id="creer-composant"></a>
- Créer le composant :
- Tout ce qui est exposé publiquement sera préfixé par `Dsfr`, exemple : `DsfrButtonComponent`.
- Les composants sont tous en encapsulation : ` ViewEncapsulation.None`
De manière à les rendre pleinement réceptifs au style DSFR global ainsi qu'aux éventuelles customisations
- Les composants sont en `standalone`
- De préférence pour les composants complexes ils sont en `ChangeDetectionStrategy.OnPush`
- Les sélecteurs des composants sont préfixés par `dsfr-` :
```typescript
// button.component.ts
@Component({
selector: 'dsfr-button'
templateUrl: './button.component.html',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrls: ['button.component.scss'],
imports: []
})
export class DsfrButtonComponent {}
```
- Déclarer le(s) composant(s) dans le fichier `index.ts` :
```typescript
// dsfr-button/index.ts
export { DsfrButtonComponent } from './dsfr-button.component';
```
- Publier les composants de la librairie :
```typescript
// public-api.ts
export * from './lib/dsfr-button/index';
```
### Guide de développement
#### Nommage
- Noms des méthodes/attributs en anglais et commentaires en français
- Les évènements `@Output` ne doivent pas être préfixés. Privilégier les verbes basiques `select` / `change`, préfixer
s'il y a besoin par un contexte (`linkSelect`, `tabChange`)
- Les attributs `Input()` doivent porter un nom contextuel, ex. ne pas les appeler `model`
- Les méthodes des callback internes doivent être préfixées par 'on' `(click)="onSomethingClick$event)"`
- Respecter le strictCamelCase (pas de capitales consécutives même lorsque le nom intègre un acronyme)
#### Typescript
- Ne pas spécifier le modifieur `public` sur les propriétés publiques
- Privilégier les `Union types` en complément des `enum`, cela permet de positionner l'attribut via sa valeur
directement dans le template sans être obligé d'exposer l'enum au template
```typescript
/**
* Définit les différents niveaux de sévérité supportés par le DSFR.
*/
export enum DsfrSeverityConst {
INFO = 'info',
SUCCESS = 'success',
WARNING = 'warning',
ERROR = 'error',
}
/**
* Type union correspondant aux valeurs de l'enum {@link DsfrFoDsfrSeverityConstllowNameConst}.
*/
export type DsfrSeverity = `${DsfrSeverityConst}`;
```
### Processus de dépréciation
Depuis la publication de la version 1.0.0 chaque modification non rétro-compatible doit obligatoirement faire l'objet
d'un processus de dépréciation.
Lorsqu'un élément faisant partie de l'interface publique d'un composant (input, output, slot, modèle) est modifié,
l'ancien élément doit être déprécié. Le cas le plus courant est le renommage d'un input, voici un exemple :
```typescript
/**
* Nouvelle propriété.
*/
@Input() bar:string;
/**
* Ancienne propriété.
*
* @internal
*
* @deprecated since 1.1.0, use 'bar' instead
*/
@Input() set foo(foo:string) {
this.bar = foo;
}
```
L'annotation `@deprecated` vient marquer la propriété dépréciée, elle est suivie d'un commentaire en anglais précisant
la version dans laquelle la propriété a été dépréciée et une indication permettant à l'utilisateur de savoir comment
il doit procéder à présent.
L'ancienne propriété sera supprimée lors de la prochaine version majeure de la bibliothèque.
### Storybook
#### Créer les stories
- Placer un fichier de description du composant et un fichier de stories au même niveau que le composant
```
lib/dsfr-button/button.mdx
lib/dsfr-button/button.stories.ts
```
- Initialiser la story avec un export par défaut :
```typescript
// button.stories.ts
import {DsfrButtonComponent} from '../button.component';
import {Meta} from "@storybook/angular/types-6-0";
// https://storybook.js.org/docs/angular/writing-stories/introduction#default-export
export default {
title: 'DSFR/Button',
component: DsfrButtonComponent,
} ;
export default meta;
...
```
- Créer un type Story pour les différentes stories :
```typescript
type Story = StoryObj<DsfrNoticeComponent>;
```
- Créer une story :
```typescript
export const Default: Story = {
args: {
message: 'mon message'
},
render: (args) => ({
props: args,
template: `
<dsfr-notice [message]="message">Titre</dsfr-notice>`,
}),
```
#### Ajouter les tests
Le composant doit avoir à minima un fichier de test `*.spec.ts` et/ou `*.e2e.ts`
Selon les cas les tests peuvent concerner tout ou partie de ces tests :
- des tests de composants avec Jest (`.spec.ts`)
- des tests end-to-end avec Playwright (`.e2e.ts`)
- des tests de régressions visuelle avec l'outil de screenshot de Playwright
Les tests sont un préalable avant chaque fusion de branche. Veillez donc à vérifier que ces derniers sont valides avant de pousser votre contribution.
##### Tests E2E (end-to-end)
Les tests e2e de ce projet utilisent l'outil [Playwright](https://playwright.dev/). Ils assurent à la fois le bon fonctionnement et l'intégrité visuelle de ses composants.
Pour lancer la suite de test, utilisez la commande `npm run e2e:playwright:docker`.
##### Tests de regressions visuelles (VisReg)
Les tests de regressions visuelles sont identifiés par le tag @screenshot dans la suite de tests. En cas de modification
visuel d'un composant, il vous faudra recréer les nouvelles images de référence utilisées. Pour se faire, supprimez les images contenus dans le dossier `nom-du-composant.e2e.ts-snapshots` puis relancer la suite de tests VisReg à l'aide de la commande `npm run e2e:playwright:docker:visreg`. Les tests du composants en question seront alors en erreurs, mais de nouvelles images de références seront auto-générées dans le dossier mentionné plus haut.
Notez qu'il vous sera tout de même nécessaire de vérifier visuellement la cohérence des images références ainsi générées.
En cas d'erreur sur l'un de ces tests, vous pourrez trouver le différentiel entre l'image réel et l'image attendu dans le dossier `test-results` à la racine de ce projet.
#### Ajouter la documentation
Créer le fichier .mdx en respectant le template
```md
<Meta of={Stories} title="Quote" />
# Titre
Description
Lien vers la documentation DSFR
- _Composant_ : ``
- _Tag_ : ``
## Aperçu
<Canvas of={Stories.Default} />
## API
### Types et interfaces
Références vers les différents types utilisés par le composant
- [DsfrTypeExemple](/docs/introduction-reference--docs#dsfrtypeexemple)
#### Contrôles
<Controls />
## Exemples
<Canvas of={Stories...} />
```
## Importer un bloc de code
Il est possible d'importer un bloc de code, par exemple d'une story de démo de cette manière :
```mdx
import { Canvas, Controls, Meta, Story, Source } from '@storybook/blocks';
import codeDemoReactiveForm from '!!raw-loader!./demo/demo-select-reactive.component.ts';
<Source language="ts" code={codeDemoReactiveForm} />
```
#### Paramétrer la story
- Afficher le code de la story
> Par défaut, le code sera automatiquement affiché par StoryBook
> cf. [DocsPage](https://storybook.js.org/docs/angular/writing-docs/doc-block-source#working-with-the-docspage).
> _Malheureusement, pour l'instant DocsPage affiche aussi les valeurs par défaut._
- Renommer la story si besoin :
```typescript
Primary.storyName = 'Utilisation via NgModel';
```
- Masquer un contrôle :
- Directement depuis la JSDOC
```typescript
/**
* @internal
*/
myInternalProp: string;
```
- Si besoin, depuis storybook :
```typescript
// button.stories.ts
// Cf. https://storybook.js.org/docs/angular/essentials/controls#disable-controls-for-specific-properties. Dans l'export
argTypes: {
myInternalProp: {
table: {
disable: true;
}
}
}
```
- Masquer un contrôle selon une condition
```typescript
argTypes: {
borderTertiary: { if: { arg: 'variant', eq: 'tertiary' } }
}
```
- Définir des contrôles :
```typescript
// button.stories.ts
// Cf. https://storybook.js.org/docs/angular/essentials/controls. Dans l'export par défaut :
argTypes: {
size: {
control: {type: 'inline-radio'},
options: Object.values(DsfrSize),
defaultValue: DsfrSize.DEFAULT,
}
}
```
- Pour visualiser une action :
```typescript
// button.stories.ts
argTypes: {
handleClick: {
action: true;
}
}
```
- Pour renommer / visualiser une action :
```typescript
argTypes: {
handleClick: {
action: 'onButton';
}
}
```
- Pour que l'action soit prise en compte par le composant
```typescript
parameters: {
actions: false
},
```
## Mainteneurs : créer une release <a id="release-perform"></a>
- Depuis master, créer le tag en le nommant de manière strictement identique au numéro de version
- Publier une release
- Cf. https://gitlab.forge.education.gouv.fr/dsmen/components/ngx-dsfr-components/-/wikis/Checklist-livraison
- Forcer un déploiement storybook sur Gitlab Pages :
- Se rendre sur https://gitlab.forge.education.gouv.fr/dsmen/components/ngx-dsfr-components/-/pipelines
- Cliquer sur `Run pipeline`
- Sélectionner le tag à déployer
- Renseigner la variable `DEPLOY` (peut importe sa valeur)
- Exécuter le pipeline