youtubeDownloader/client/build.mjs
Nicolas 725c5a5a7f feat(client): frontend (formulaire d'ajout + file d'attente)
Interface web complète : formulaire d'ajout (URL directe ou recherche de
chaîne) et panneau de file d'attente avec sondage de la progression.

- views/downloadForm/ : sélecteur de mode (vidéo/audio/miniature) partagé,
  ajout direct par URL, recherche de chaîne (GET /api/channel) avec ajout
  par résultat. Construction DOM via createElement/textContent (jamais
  d'innerHTML avec du texte venant de YouTube), pas juste par prudence :
  ça évite aussi tout risque d'injection depuis un titre de vidéo.
- views/queuePanel/ : sonde GET /api/queue toutes les 1,5s, affiche statut/
  progression/erreur par élément, annulation et lien de téléchargement
  natif (<a download>, pas de fetch+blob).
- api/ : clients fetch typés (channelApi, queueApi), chaque await dans son
  propre try/catch avec message d'erreur adapté (réseau vs HTTP vs JSON).
- QueueItem gagne un champ title optionnel (purement informatif, jamais
  utilisé pour construire la commande yt-dlp) pour un affichage plus
  parlant qu'une URL brute.
- client/tsconfig.json + build.mjs : cible ES2022 (nécessaire pour
  Error.cause, jusque-là ES2020).
- Vitest ajouté côté client (fonctions pures de src/format.ts ; le câblage
  DOM est vérifié manuellement, pas de jsdom pour l'instant).

Deux bugs réels trouvés et corrigés en testant dans un vrai navigateur
(pas seulement en lisant le code) :
- CSS : un attribut `hidden` sur un conteneur `display: flex` se faisait
  écraser par cette règle (même spécificité, la feuille de l'auteur gagne)
  - les deux panneaux d'options restaient visibles en même temps.
- server/src/routes/channel.ts : ne mappait vers 502 que les YtDlpError
  (code de sortie non nul), pas les erreurs brutes de spawn (ex. binaire
  introuvable - ENOENT), qui remontaient donc en 500 générique. Corrigé +
  test de non-régression.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 09:28:49 +02:00

57 lines
2 KiB
JavaScript

// Build client : TypeScript (+ .html/.scss par vue) -> client/dist, via
// esbuild. Script à la main plutôt qu'un outil plus lourd (Vite, webpack) :
// le client reste pour l'instant minimal, et esbuild suffit - y compris
// pour le Sass (voir le plugin ci-dessous, qui délègue à `sass` puis laisse
// esbuild regrouper le CSS résultant, importé depuis le JS, dans un
// app.css voisin).
//
// Usage : node build.mjs [--watch]
// npm run build -> build unique (celui lancé par server/Dockerfile)
// npm run watch -> reconstruit à chaque sauvegarde, pour le dev local
import { copyFileSync, mkdirSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import * as esbuild from 'esbuild';
import * as sass from 'sass';
const here = path.dirname(fileURLToPath(import.meta.url));
const outdir = path.join(here, 'dist');
mkdirSync(outdir, { recursive: true });
copyFileSync(path.join(here, 'src', 'index.html'), path.join(outdir, 'index.html'));
/** Plugin esbuild : compile les imports `.scss` en CSS via le paquet `sass`. */
const sassPlugin = {
name: 'sass',
setup(build) {
build.onLoad({ filter: /\.scss$/ }, async (args) => {
const result = await sass.compileAsync(args.path);
return { contents: result.css, loader: 'css' };
});
},
};
const buildOptions = {
entryPoints: [path.join(here, 'src', 'app.ts')],
outfile: path.join(outdir, 'app.js'),
bundle: true,
format: 'esm',
target: 'es2022',
sourcemap: true,
// Chaque vue importe son propre gabarit comme chaîne brute (`import html
// from './Vue.html'` - voir global.d.ts) et son propre style Sass ; les
// styles sont regroupés automatiquement par esbuild dans app.css.
loader: { '.html': 'text' },
plugins: [sassPlugin],
};
const watch = process.argv.includes('--watch');
if (watch) {
const ctx = await esbuild.context(buildOptions);
await ctx.watch();
console.log('[youtubedownloader-client] watching for changes...');
} else {
await esbuild.build(buildOptions);
console.log('[youtubedownloader-client] built to client/dist');
}