docs(tsup): update build config comments and fix jsx import extensions

This commit is contained in:
2026-04-22 14:40:09 -04:00
parent 0106bc4ea0
commit d64423c1ad
8 changed files with 42 additions and 66 deletions
+34 -55
View File
@@ -4,34 +4,37 @@ import { join } from 'node:path';
const pkg = JSON.parse(readFileSync('./package.json', 'utf8'));
// Source de vérité #1 : package.json#exports. Donne la liste des points
// d'entrée publics et la liste des self-imports à marquer external.
const exportEntries = Object.values(pkg.exports)
.map(e => e.import).filter(Boolean)
.map(p => p.replace('./dist/', 'src/'));
// Les self-imports @zen/core/* restent toujours externes — ils pointent vers
// d'autres points d'entrée du même package, jamais vers des fichiers internes.
const selfImports = Object.keys(pkg.exports)
.filter(k => k !== '.' && !k.endsWith('.css'))
.map(k => '@zen/core' + k.slice(1));
// Source de vérité #2 : les fichiers *.server.js et *.client.js sous src/.
// Convention : un tel fichier est *toujours* un point d'entrée non-bundlé —
// soit il fait partie de l'API publique (listé dans exports), soit c'est un
// wiring interne (pages, widgets) qui doit rester un module séparé pour
// préserver les frontières RSC / 'use client'.
// Tous les fichiers source .js et .jsx sont compilés en module standalone
// (bundle: false). Chaque fichier est transpilé individuellement ; les imports
// relatifs sont préservés tels quels dans le dist.
//
// Pourquoi bundle: false pour tout ?
// - Les fichiers *.server.js et *.client.js doivent rester des modules séparés
// pour que Next.js respecte les frontières RSC / 'use client'.
// - Les modules de registre (registry.js, etc.) 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 widgets — deux instances, zéro partage d'état.
// - Tous les fichiers doivent exister dans dist pour que les imports relatifs
// des fichiers *.server.js/*.client.js (compilés sans bundling) puissent être
// résolus à l'exécution.
//
// La liste des points d'entrée publics reste définie dans package.json#exports.
// Modifier un export public = éditer seulement package.json#exports.
function walk(dir, out = []) {
for (const name of readdirSync(dir)) {
const full = join(dir, name);
if (statSync(full).isDirectory()) walk(full, out);
else if (/\.(server|client)\.js$/.test(name)) out.push(full);
else if (/\.(js|jsx)$/.test(name)) out.push(full);
}
return out;
}
const boundaryFiles = walk('src');
// Dédup : un chemin déclaré dans exports ET détecté par la glob ne devient
// pas deux entrées.
const allEntries = [...new Set([...exportEntries, ...boundaryFiles])];
const SHARED_EXTERNALS = [
'react', 'react-dom', 'next',
@@ -41,42 +44,18 @@ const SHARED_EXTERNALS = [
...selfImports,
];
const unbundled = allEntries.filter(e => /\.(server|client)\.js$/.test(e));
const bundled = allEntries.filter(e => !/\.(server|client)\.js$/.test(e));
const esbuildBase = (o) => {
o.loader = { '.js': 'jsx', '.jsx': 'jsx' };
o.jsx = 'automatic';
};
export default defineConfig([
{
entry: bundled,
format: ['esm'],
dts: false,
splitting: false,
sourcemap: false,
clean: true,
bundle: true,
external: SHARED_EXTERNALS,
esbuildOptions(o) {
esbuildBase(o);
o.platform = 'neutral';
o.legalComments = 'inline';
},
export default defineConfig({
entry: walk('src'),
format: ['esm'],
dts: false,
splitting: false,
sourcemap: false,
clean: true,
bundle: false,
external: SHARED_EXTERNALS,
esbuildOptions(o) {
o.loader = { '.js': 'jsx', '.jsx': 'jsx' };
o.jsx = 'automatic';
o.outbase = 'src';
},
{
entry: unbundled,
format: ['esm'],
dts: false,
splitting: false,
sourcemap: false,
clean: false,
bundle: false,
external: SHARED_EXTERNALS,
esbuildOptions(o) {
esbuildBase(o);
o.outbase = 'src';
},
},
]);
});