docs(modules): update server/client boundary docs and client manifest generation
- update MODULES.md to document dual-entry pattern (main vs ./client) and explain why client entry must not import server-only code - filter client manifest to only include modules exposing a `./client` subpath export - add `moduleHasClientEntry` helper in discover.server.js to check package.json exports - update cli.js to use `moduleHasClientEntry` when rendering the client manifest - update init.js and modules/index.js to align with new client entry convention
This commit is contained in:
+42
-11
@@ -34,11 +34,12 @@ await Promise.all(modules.map(m => m.exports.register?.()));
|
||||
```js
|
||||
// app/.zen/modules.client.js — AUTO-GÉNÉRÉ (client)
|
||||
'use client';
|
||||
import * as m0_zen_module_posts from '@zen/module-posts';
|
||||
m0_zen_module_posts.register?.();
|
||||
import '@zen/module-posts/client';
|
||||
export {};
|
||||
```
|
||||
|
||||
Le manifeste client importe la **sous-entrée `./client`** de chaque module (et non son main entry). C'est essentiel : le main entry tire `createTables` / `registerApiRoutes` / la chaîne `register-server.js` qui dépend de `pg`, `fs`, `next/headers`, etc. — code serveur incompatible avec le bundle browser. Seuls les modules qui exposent `./client` dans leur `package.json#exports` sont inclus dans le manifeste client ; les modules purement back-end (API/DB) sont absents du bundle browser.
|
||||
|
||||
L'importation statique (le `import * as ...`) permet à Turbopack/Webpack d'analyser le graphe complet du module — JSX, `next/headers`, `next/navigation`, frontières `'use client'` — exactement comme pour le code de l'application elle-même.
|
||||
|
||||
### Critères de détection
|
||||
@@ -240,18 +241,48 @@ Convention : préfixer toutes les tables par `zen_<module>_` pour éviter les co
|
||||
|
||||
## Frontières serveur/client
|
||||
|
||||
Comme pour le core (voir [DEV.md](DEV.md)), les fichiers du module portent les suffixes `.server.js` / `.client.js`. Le hook `register()` côté serveur est appelé par le core ; les enregistrements client (widgets, par ex.) doivent être triggés par un import dans le bundle client — typiquement via le composant client lui-même qui appelle `registerWidget()` à l'import.
|
||||
Un module avec partie admin expose **deux entrées** dans son `package.json#exports` :
|
||||
|
||||
```js
|
||||
// Pattern recommandé pour un module avec partie client :
|
||||
|
||||
// src/register-server.js (importé par register())
|
||||
import './admin/BlogAdminPage.client.js'; // chaîne d'imports vers client
|
||||
import './widgets/BlogWidget.server.js';
|
||||
// ... registerNavItem, registerPage, registerApiRoutes, etc.
|
||||
```json
|
||||
{
|
||||
"exports": {
|
||||
".": { "import": "./dist/index.js" },
|
||||
"./client": { "import": "./dist/client.js" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Le bundle Next.js du projet consommateur traverse le graphe d'import et inclut les composants client dans le bundle client. Côté serveur, seules les fonctions et fetchers serveur sont chargés.
|
||||
| Entrée | Bundle | Contenu |
|
||||
|--------|--------|---------|
|
||||
| `.` (main) | serveur | `manifest`, `register()`, `createTables`, `dropTables`. Le `register()` tire la chaîne `register-server.js` (API routes, navigation, fetchers, storage prefixes, hooks DB). |
|
||||
| `./client` | client | `'use client'` ; uniquement `registerPage({ Component })` et `registerWidget({ Component })`. Aucun import vers `pg`, `fs`, `.server.js` ou `register-server.js`. |
|
||||
|
||||
Le manifeste serveur importe `@zen/module-X` (main) ; le manifeste client importe `@zen/module-X/client`. La séparation est obligatoire : tout ce qui est statiquement importé depuis l'entrée client finit dans le bundle browser, et `pg` / `fs` / `next/headers` y crashent.
|
||||
|
||||
```js
|
||||
// src/index.js (entrée serveur)
|
||||
export const manifest = { /* ... */ };
|
||||
export async function register() { await import('./register-server.js'); }
|
||||
export { createTables, dropTables } from './db.server.js';
|
||||
```
|
||||
|
||||
```js
|
||||
// src/register-server.js (server-only — appelée par register())
|
||||
import { registerNavItem, registerNavSection } from '@zen/core/features/admin';
|
||||
import { registerApiRoutes } from '@zen/core/api';
|
||||
import { routes } from './api.server.js';
|
||||
// PAS d'import de Component .client.js ici — ils vivent dans client.js.
|
||||
registerNavSection({ /* ... */ });
|
||||
registerApiRoutes(routes);
|
||||
```
|
||||
|
||||
```js
|
||||
// src/client.js (entrée client — chargée par le manifeste client uniquement)
|
||||
'use client';
|
||||
import { registerPage } from '@zen/core/features/admin';
|
||||
import BlogAdminPage from './admin/BlogAdminPage.client.js';
|
||||
registerPage({ slug: 'blog', Component: BlogAdminPage, title: 'Blog' });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user