227ecc9e7d
- move promise and env-var principles to top of code standards section - clarify build strategy: bundle:false applies to all src/ files, not just suffixed ones - update RSC boundary explanation to describe module isolation approach - add note about jsx loader handling .js files via esbuild - remove redundant section headers and inline comments in examples - fix grammar in singleton bundling warning
112 lines
6.2 KiB
Markdown
112 lines
6.2 KiB
Markdown
# Guide de développement
|
|
|
|
Ce document couvre les conventions de code, les règles de sécurité et la procédure de publication du package `@zen/core`.
|
|
|
|
Pour les conventions de rédaction : [LANGUE.md](./dev/LANGUE.md) et [REDACTION.md](./dev/REDACTION.md).
|
|
|
|
Pour les décisions de design et l'identité visuelle : [DESIGN.md](./DESIGN.md).
|
|
|
|
Pour l'architecture partagée (composants, icônes) : [ARCHITECTURE.md](./dev/ARCHITECTURE.md).
|
|
|
|
Pour la procédure de publication du package : [PUBLICATION.md](./dev/PUBLICATION.md).
|
|
|
|
Pour les conventions de commit : [COMMITS.md](./dev/COMMITS.md).
|
|
|
|
> **Contexte projet** : les utilisateurs finaux créent leur projet via `npx @zen/start`, qui génère automatiquement une structure Next.js avec la plateforme déjà intégré. Lors d'une assistance ou d'une revue, partez du principe que le projet cible est déjà structuré de cette façon.
|
|
|
|
---
|
|
|
|
## Standards de code
|
|
|
|
**Les promesses ne s'ignorent pas.** Chaque `Promise` est `await`ée ou `.catch()`ée. Une promesse silencieuse qui échoue est un bug invisible.
|
|
|
|
**Les variables d'environnement et la documentation se mettent à jour avec le code.** Toute variable ajoutée, renommée ou supprimée doit être reflétée dans `.env.example`. Toute décision architecturale ou convention nouvelle doit être documentée dans le fichier `docs/` concerné.
|
|
|
|
**Une fonction, une responsabilité.** Si elle fait deux choses, c'est deux fonctions. Si elle ne tient pas sur un écran, la découper.
|
|
|
|
**Le flux de contrôle se lit de haut en bas.** Pas de récursion non bornée, pas de callbacks imbriqués à plus de deux niveaux. Quelqu'un qui lit le code pour la première fois doit pouvoir suivre l'exécution sans se perdre.
|
|
|
|
**Les données entrantes sont suspectes.** On valide en entrée de fonction. On ne suppose pas que l'appelant a fait le travail.
|
|
|
|
**La portée des variables est minimale.** On déclare au plus près de l'usage. Pas de variables réutilisées pour deux rôles différents.
|
|
|
|
**ESLint passe sans avertissement.** Un warning ignoré aujourd'hui est un bug non détecté demain.
|
|
|
|
**Les commentaires reflètent toujours le comportement réel du code.** Un commentaire obsolète est pire qu'un commentaire absent — il induit en erreur. Quand on modifie une fonction, on met à jour son commentaire. Un commentaire qui contredit le code est un bug de documentation.
|
|
|
|
---
|
|
|
|
## Conventions d'arborescence
|
|
|
|
Une feature (`src/features/<nom>/` ou `src/core/<nom>/`) suit la règle **flat + un barrel** :
|
|
un `index.js` qui ré-exporte, des fichiers plats côte à côte pour l'implémentation. Pas de sous-dossier `lib/`, `middleware/`, `actions/` quand il ne contient qu'un ou deux fichiers — remonter directement au niveau du dossier feature.
|
|
|
|
Les sous-dossiers sont autorisés uniquement quand ils contiennent plusieurs fichiers du même rôle : `components/`, `pages/`, `templates/`, `widgets/`.
|
|
|
|
### Suffixes de runtime
|
|
|
|
Tout fichier épinglé à une frontière Next.js porte le suffixe dans son nom :
|
|
|
|
- `.server.js` → code serveur strict (peut importer `pg`, `fs`, etc.)
|
|
- `.client.js` → débute par `'use client'`
|
|
- pas de suffixe → module neutre, utilisable des deux côtés
|
|
- `actions.js` → débute par `'use server'` (server actions Next.js)
|
|
|
|
Ces suffixes ne sont pas cosmétiques : **le build les utilise comme source de vérité**. Le build compile l'intégralité de `src/` avec `bundle: false` — chaque fichier reste un module séparé, ce qui permet à Next.js de respecter les frontières RSC et `'use client'` sans que le bundler ne fusionne les modules.
|
|
|
|
---
|
|
|
|
## Build et configuration tsup
|
|
|
|
`tsup.config.js` compile **tous les fichiers `.js` et `.jsx` de `src/`** avec `bundle: false`. Chaque fichier devient un module standalone dans `dist/` ; les imports relatifs sont préservés tels quels. La structure `dist/` reflète exactement `src/` grâce à `outbase: 'src'`.
|
|
|
|
- **Ajouter un module public = éditer seulement `package.json#exports`.** L'entry list se régénère au prochain build via un walk de `src/`.
|
|
- **Pas de bundling des fichiers internes** : les modules de registre (`registry.js`, etc.) sont des singletons — les bundler créerait une copie inline distincte et casserait le partage d'état entre les pages et les widgets.
|
|
- Les self-imports `@zen/core/*` sont générés automatiquement à partir des clés de `exports` et restent toujours dans la liste `external`.
|
|
- Les fichiers `.js` et `.jsx` sont tous traités comme du JSX via esbuild (`loader: { '.js': 'jsx' }`, `jsx: 'automatic'`) — pas besoin d'extension `.jsx` pour écrire du JSX.
|
|
|
|
---
|
|
|
|
## Étendre l'admin
|
|
|
|
L'admin utilise un **registre runtime** pour permettre aux projets consommateurs d'ajouter des widgets, des entrées de navigation, et des pages sans modifier le core.
|
|
|
|
```js
|
|
// app/zen.extensions.js — projet consommateur
|
|
import {
|
|
registerWidget,
|
|
registerWidgetFetcher,
|
|
registerNavItem,
|
|
registerNavSection,
|
|
registerPage,
|
|
} from '@zen/core/features/admin';
|
|
import OrdersWidget from './admin/OrdersWidget';
|
|
import OrdersPage from './admin/OrdersPage';
|
|
import { countOrders } from './admin/orders.server';
|
|
|
|
registerWidgetFetcher('orders', async () => ({ total: await countOrders() }));
|
|
registerWidget({ id: 'orders', Component: OrdersWidget, order: 20 });
|
|
|
|
registerNavSection({ id: 'commerce', title: 'Commerce', icon: 'ShoppingBag03Icon', order: 30 });
|
|
registerNavItem({ id: 'orders', label: 'Commandes', icon: 'ShoppingBag03Icon', href: '/admin/orders', sectionId: 'commerce' });
|
|
|
|
registerPage({ slug: 'orders', Component: OrdersPage, title: 'Commandes' });
|
|
```
|
|
|
|
```js
|
|
// app/layout.js — un seul import suffit ; les side effects enregistrent tout.
|
|
import './zen.extensions';
|
|
```
|
|
|
|
---
|
|
|
|
## Sécurité
|
|
|
|
**Données entrantes** : toute donnée externe est considérée malveillante par défaut. On valide côté serveur uniquement.
|
|
|
|
**Base de données** : uniquement des requêtes paramétrées — jamais de SQL construit par concaténation de chaînes.
|
|
|
|
**Secrets** : aucun token, clé API ou mot de passe dans le code. Tout passe par des variables d'environnement, jamais commitées.
|
|
|
|
**Erreurs exposées** : pas de stack trace, nom de table ou requête SQL retournés au client. On log côté serveur, on renvoie un message générique côté client.
|