Hôtes

Un hôte, c'est ce qui sert le HTTP : Nuxt, Next, TanStack Start, React Router, SvelteKit, Express. Une Frond ignore lequel la fait tourner, et rien dans fronds/ n'en importe un — les six démos de ce dépôt partagent un dossier fronds/ identique à l'octet près.

Ce qu'un hôte fait, c'est monter trois portes et, s'il rend des pages, offrir les quatre primitives client. Tout le reste — le boot, le juge, la table REST, le contrat de formulaire — vit dans @fougere/app et ne change pas.

Ce que coûte chaque hôte

HôtePaquetCe que vous écrivez
Nuxt@fougere/nuxtune ligne dans nuxt.config
Next@fougere/nexttrois fichiers de route d'une ligne + withFougere()
TanStack Startaucuntrois fichiers de route + le plugin Vite
React Routeraucuntrois routes ressource + le plugin Vite
SvelteKitaucuntrois +server.ts + le plugin Vite
Expressaucunapp.use(fougere())

Trois hôtes n'ont besoin d'aucun paquet, et ce n'est pas un hasard : ils servent des Request/Response standard, donc les portes s'y branchent telles quelles. @fougere/next existe parce que Next a next/headers — un seul import — et le module Nuxt est plus gros parce qu'un événement h3 n'est pas une Request.

Ce qu'une app publie

Quels adaptateurs de protocole sont servis est un fait sur l'app, déclaré une fois à côté de db, remotes et auth :

// fougere.config.ts
export default defineFougere({
  db: 'sqlite',
  adapters: { rest: true, graphql: true },
});

graphql: true est toute la configuration GraphQL : le schéma est construit depuis les entités que l'app a déjà scannées, donc il n'y a ni builder à monter ni types à enregistrer. @fougere/adapter-graphql, @pothos/core et graphql ne sont importés que si vous le déclarez — une app qui n'en sert pas ne transporte pas un constructeur de schéma pour le découvrir.

Absent veut dire non servi. La porte peut être montée — le fichier de route existe, le middleware est installé — elle déclinera quand même, parce que ce n'est pas à l'hôte de décider. serveRest lit la déclaration lui-même, donc les six hôtes héritent d'une seule réponse au lieu de l'épeler chacun à sa façon.

L'enveloppe n'y figure pas : c'est le fil que les primitives client utilisent, pas une projection qu'une app choisit de publier.

Monter les portes

Monter une porte n'est pas publier un adaptateur : ce que vous écrivez ci-dessous met la porte en place, et adapters: décide si elle sert quelque chose.

Un hôte au standard Web — Next, TanStack Start, React Router, SvelteKit — monte les handlers directement :

// SvelteKit — src/routes/_fougere/call/+server.ts
import { fougereCall } from '@fougere/app/web';
export const POST = ({ request }: { request: Request }) => fougereCall(request);

fougereCall, fougereRest et fougereSession prennent une Request et rendent une Response. Le fichier que vous créez est le consentement : pas de fichier, pas de porte.

Express les ajoute en middlewares, comme express.json() ou cors() :

import { fougere, fougereCall, fougereRest } from '@fougere/app/express';

app.use(fougere());                 // les trois portes
app.use(fougereCall());             // ou une par une — l'enveloppe sert vos pages,
app.use('/admin', fougereRest());   // REST est publique. Ce n'est pas une seule décision.

Un chemin que Fougere ne sert pas appelle next(), donc les routes de l'app continuent de répondre, qu'elles soient déclarées avant ou après.

Nuxt les monte lui-même — son module a une API pour ça, et il ajoute les composables au passage.

Les primitives client

useQuery, useCommand, useFormFor et useCurrentUser existent par framework d'UI, pas par hôte :

  • @fougere/react — React, quel que soit ce qui le rend
  • @fougere/svelte — Svelte, sous forme de stores
  • @fougere/nuxt — Vue, via la couche données de Nuxt

Les règles qu'elles suivent sont communes : la désignation est classe + verbe, et une commande sur une entité revalide toutes les requêtes montées sur cette entité. Seul le modèle d'état change.

La seule chose à dire à tout bundler

La désignation lit le nom de classe de l'entité, et ce nom voyage — c'est la méthode JSON-RPC (post.list) et le chemin REST. Un minifieur renomme les classes, donc un build de production désignerait une entité que personne n'héberge :

Entity 'j' is not hosted here. Hosted here: post.

Vous n'avez rien à déclarer pour autant. @fougere/nuxt et @fougere/vite réservent les noms d'entités pour vous — le premier depuis le scan qu'il vient de faire, le second en lisant fronds/<frond>/entities/. Sur Next, withFougere() fait la même chose via webpack :

// next.config.mjs
import { withFougere } from '@fougere/next/config';
export default withFougere();
// vite.config.ts — TanStack Start, React Router, SvelteKit
import { fougere } from '@fougere/vite';
export default defineConfig({ plugins: [fougere(), sveltekit()] });

Express n'a besoin de rien : il exécute vos sources, rien n'est minifié.

Une app qui minifie sans l'un de ces réglages se construira sans erreur et échouera au premier appel. L'échec est bruyant — le serveur dit quelle entité il héberge vraiment — mais il arrive en production, pas au build.

Deux hôtes à la fois

Rien n'empêche une Frond d'être servie par plusieurs hôtes : faites-la tourner dans son propre process avec serve(), et que les autres l'atteignent par remotes:. C'est le gradient, et l'hôte n'entre pas dans la décision.

Construit avec Fougere — ce site tourne sur le framework qu'il documente.