feat(core)!: introduce runtime extension registry and flat module conventions
BREAKING CHANGE: sup config now derives entries from package.json#exports and a server/client glob instead of manual lists; module structure follows flat + barrel convention with .server.js/.client.js runtime suffixes
This commit is contained in:
+60
-9
@@ -36,22 +36,73 @@ Pour les conventions de commit : [COMMITS.md](./dev/COMMITS.md).
|
||||
|
||||
---
|
||||
|
||||
## 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é**. Tout fichier `*.server.js` ou `*.client.js` est automatiquement ajouté à la config tsup non-bundlée pour préserver la frontière RSC.
|
||||
|
||||
---
|
||||
|
||||
## Build et configuration tsup
|
||||
|
||||
### Règle des externals
|
||||
### Source de vérité
|
||||
|
||||
Tout import de la forme `@zen/core/*` dans un fichier bundlé par tsup (typiquement `src/modules/*/api.js`, `src/modules/*/actions.js`, `src/modules/*/crud.js`) **doit figurer dans la liste `external`** du premier bloc de config dans `tsup.config.js`.
|
||||
`tsup.config.js` dérive ses entrées de deux sources :
|
||||
|
||||
Pourquoi : tsup tente de résoudre ces imports au moment du build. Or les fichiers `dist/` n'existent pas encore — le build échoue avec `Could not resolve "@zen/core/..."`.
|
||||
1. `package.json#exports` — pour tous les points d'entrée publics.
|
||||
2. Un glob récursif `src/**/*.{server,client}.js` — pour le wiring interne (pages, widgets) qui doit rester en module séparé sans être public.
|
||||
|
||||
**Règle :** quand on crée ou refactorise un module `src/core/*/index.js` exposé via `package.json` `exports`, on ajoute immédiatement l'entrée correspondante dans `external` de `tsup.config.js`.
|
||||
**Ajouter un module public = éditer seulement `package.json#exports`.** La liste `external` et la liste `entry` sont régénérées au prochain build. Il n'y a plus trois listes à synchroniser.
|
||||
|
||||
Les self-imports `@zen/core/*` sont générés automatiquement à partir des clés de `exports`.
|
||||
|
||||
---
|
||||
|
||||
## É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
|
||||
// tsup.config.js — external (premier bloc)
|
||||
'@zen/core/api', // ← à ajouter si src/core/api/index.js est un entry tsup
|
||||
'@zen/core/database',
|
||||
'@zen/core/storage',
|
||||
// etc.
|
||||
// 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';
|
||||
|
||||
// Widget dashboard — fetcher serveur + composant client partagent un même id.
|
||||
registerWidgetFetcher('orders', async () => ({ total: await countOrders() }));
|
||||
registerWidget({ id: 'orders', Component: OrdersWidget, order: 20 });
|
||||
|
||||
// Sidebar
|
||||
registerNavSection({ id: 'commerce', title: 'Commerce', icon: 'ShoppingBag03Icon', order: 30 });
|
||||
registerNavItem({ id: 'orders', label: 'Commandes', icon: 'ShoppingBag03Icon', href: '/admin/orders', sectionId: 'commerce' });
|
||||
|
||||
// Page — le slug correspond au segment sous /admin/.
|
||||
registerPage({ slug: 'orders', Component: OrdersPage, title: 'Commandes' });
|
||||
```
|
||||
|
||||
```js
|
||||
// app/layout.js — un seul import suffit ; les side effects enregistrent tout.
|
||||
import './zen.extensions';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user