Build et exports

Pourquoi le build ne bundle rien, comment package.json#exports pilote le package, et ce que produit chaque étape.

tsup.config.js compile tous les fichiers .js et .jsx de src/ avec bundle: false. Chaque fichier devient un module autonome dans dist/, et les imports relatifs sont préservés tels quels. La structure de dist/ reflète exactement celle de src/ grâce à outbase: 'src'.

Pourquoi bundle: false partout

Trois raisons, chacune suffisante.

Les frontières RSC. Les fichiers .server.js et .client.js doivent rester des modules séparés pour que Next.js respecte les frontières RSC et 'use client'. Un bundle les fusionnerait et effacerait la distinction.

Les registres sont des singletons. Si un barrel comme index.js bundlait registry.js avec bundle: true, il en ferait une copie inline distincte de la version importée en relatif par les pages et les widgets. Deux instances, zéro partage d'état : un widget enregistré d'un côté serait invisible de l'autre. Voir Registres et singletons.

Les imports relatifs doivent se résoudre. Tous les fichiers doivent exister dans dist/ pour que les imports relatifs des fichiers .server.js et .client.js, compilés sans bundling, se résolvent à l'exécution.

Ce que fait le build

"build": "npm run sync:version && tsup && npm run build:css && npm run build:docs && npm run build:notices"
ÉtapeEffet
tsupTranspile chaque .js et .jsx de src/ vers dist/, sans bundler. clean: true vide dist/ avant.
build:cssCopie shared/styles/core.css et le dossier des polices dans dist/.
build:docsCopie docs/ dans dist/docs/.
build:noticesRégénère THIRD-PARTY-NOTICES.md depuis l'arbre de dépendances installé. Échoue si une licence copyleft y apparaît.
sync:versionRégénère src/shared/lib/version.js depuis package.json#version, pour que le core connaisse sa propre version à l'exécution.
build:changelogRégénère CHANGELOG.md et changelog.json depuis l'historique git. Appelé par release, pas par build.

prepublishOnly lance build : une publication ne peut pas partir sur un dist/ périmé, et un build en échec annule la publication.

L'ordre compte : clean: true s'exécute au début de tsup, donc les deux copies qui suivent survivent.

JSX sans extension .jsx

Les fichiers .js et .jsx sont tous traités comme du JSX par esbuild :

o.loader = { '.js': 'jsx', '.jsx': 'jsx' };
o.jsx = 'automatic';

Il n'est donc pas nécessaire de nommer un fichier .jsx pour y écrire du JSX. C'est cohérent avec le nommage par suffixe de runtime, qui porte déjà une information plus utile.

Externals

SHARED_EXTERNALS liste ce que le build ne doit jamais tenter de résoudre : react, react-dom, next, pg, dotenv, resend, node-cron, les composants React Email, et les modules Node natifs.

Aucun SDK S3 n'y figure : core/storage parle directement le protocole S3 et signe ses requêtes en SigV4 dans src/core/storage/signing.js. C'est une dépendance lourde en moins à suivre à chaque alerte de sécurité.

Aucune bibliothèque SMTP non plus, pour la même raison : le fournisseur SMTP de core/email parle le protocole dans src/core/email/smtp.server.js et construit ses messages MIME dans src/core/email/mime.js. Ce dernier n'importe aucun module natif, ce qui le garde neutre ; seul le client protocolaire porte le suffixe .server.js.

Les self-imports @lrvo/core/* sont générés automatiquement à partir des clés de exports et restent toujours dans cette liste. Un fichier du core qui importe @lrvo/core/shared/logger pointe donc vers un autre point d'entrée du même package, jamais vers un fichier interne inline.

package.json#exports est la source de vérité

Ajouter un point d'entrée public = éditer uniquement package.json#exports.

La liste des fichiers à compiler se régénère au build par un parcours de src/ : il n'y a pas de liste d'entrées à tenir à jour en parallèle. Le champ exports ne décide donc pas de ce qui est compilé, mais de ce qui est importable de l'extérieur.

Le catalogue complet est dans Points d'entrée.

Contenu du package publié

"files": ["dist", ".env.example", "supported.json", "LICENSE", "THIRD-PARTY-NOTICES.md", "CHANGELOG.md", "changelog.json"]

Le package contient donc :

Chemin installéContenu
dist/**/*.jsTout src/, transpilé.
dist/shared/styles/core.cssFeuille Tailwind.
dist/shared/fonts/Polices IBM Plex.
dist/docs/Cette documentation.
.env.exampleModèle de démarrage copié dans un nouveau projet, pas la déclaration des variables. Celle-ci vit dans dist/core/doctor/env.js.
LICENSEContrat de licence utilisateur final.
THIRD-PARTY-NOTICES.mdInventaire des dépendances de production et de leurs licences.

src/ n'est jamais publié, et sourcemap: false empêche qu'une carte de source ne le reconstitue. Les deux fichiers légaux sont listés explicitement plutôt que laissés au comportement par défaut de npm : un paquet installé sans son contrat est un paquet sans conditions opposables. Voir Licence et propriété.

La documentation voyage donc avec chaque version installée : un projet lit la doc de la version exacte qu'il utilise, hors ligne, sous node_modules/@lrvo/core/dist/docs/. C'est le même emplacement que celui de Next.js pour sa propre documentation.

Binaires

"bin": {
  "lrvo-db": "./dist/core/database/cli.js",
  "lrvo-modules": "./dist/core/modules/cli.js",
  "lrvo-key": "./dist/core/registry/cli.js"
}

Ces trois fichiers ne sont pas des points d'entrée exports : ils sont exécutés par npx, pas importés. Voir CLI.

Modules externes

Un module @lrvo/module-* suit la même approche : bundle: false, JSX transpilé, outbase: 'src'. Le détail et un tsup.config.js minimal sont dans Créer un module.

Sur cette page