/** * Dev : sert chaque dist-/ sous /slides// (sources = dossiers à la racine du dépôt). * La racine `/slides//` (et `/slides/`) redirige en 302 vers `/slides//1` (route Slidev). */ import fs from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' import sirv from 'sirv' import { listPresentationSlugs } from '../../tools/list-slugs.mjs' const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..') function slidesUrlPrefix(vpBase) { if (!vpBase || vpBase === '/') return '/slides' return `${String(vpBase).replace(/\/+$/, '')}/slides` } /** Requêtes que le middleware ne doit pas confondre avec la SPA Slidev (sinon HTML → erreur MIME sur les modules). */ function isViteOrSourceLikeRequest(rawUrl) { let u try { u = new URL(rawUrl, 'http://vitepress.local') } catch { return false } const q = u.searchParams if (q.has('import') || q.has('raw') || q.has('url') || q.has('worker') || q.has('sharedworker')) { return true } const p = u.pathname return /\.(md|mdx|vue|ts|mts|cts|tsx|jsx)($|\?)/i.test(p) } /** * Après échec sirv : ne pas laisser VitePress répondre en HTML pour des scripts / modules. * Pour une navigation document, servir le index.html du deck (router Slidev en history). */ function send404(res) { res.statusCode = 404 res.setHeader('Content-Type', 'text/plain; charset=utf-8') res.end('Not found') } function wantsAssetNotHtml(req) { const dest = req.headers['sec-fetch-dest'] if (dest === 'script' || dest === 'style' || dest === 'image' || dest === 'font') return true const accept = req.headers.accept || '' if (accept.includes('text/javascript') || accept.includes('application/javascript')) return true return false } /** Racine du deck Slidev (`/` ou `/index.html`) : le routeur attend `/1` pour la première planche. */ function isSlideDeckEntrySubpath(sub) { const n = sub.replace(/\/+$/, '') || '/' return n === '/' || n === '/index.html' } export function vitePluginSlideDists({ base: vpBase }) { const prefix = slidesUrlPrefix(vpBase) const escaped = prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') const re = new RegExp(`^${escaped}\\/([^/]+)(.*)$`) /** @type {Map>} */ const sirvBySlug = new Map() /** @type {Map} */ const indexHtmlBySlug = new Map() const warnedMissing = new Set() return { name: 'talks-slide-dists', enforce: 'pre', apply: 'serve', configureServer(server) { server.middlewares.use((req, res, next) => { const raw = req.originalUrl || req.url || '/' const pathPart = raw.split('?')[0] const m = pathPart.match(re) if (!m) return next() const slug = m[1] const subRaw = m[2] || '/' if (!listPresentationSlugs(repoRoot).includes(slug)) return next() const distDir = path.join(repoRoot, `dist-${slug}`) if (!fs.existsSync(path.join(distDir, 'index.html'))) { if (!warnedMissing.has(slug)) { warnedMissing.add(slug) server.config.logger.warn( `[talks-slide-dists] dist-${slug}/ manquant ou sans index.html — exécutez « make run » ou « npm run build » puis réessayez.`, ) } return next() } const sub = subRaw.startsWith('/') ? subRaw : `/${subRaw}` const query = raw.includes('?') ? `?${raw.split('?')[1]}` : '' if ( (req.method === 'GET' || req.method === 'HEAD') && isSlideDeckEntrySubpath(sub) && !isViteOrSourceLikeRequest(raw) ) { res.statusCode = 302 res.setHeader('Location', `${prefix}/${slug}/1${query}`) res.end() return } if (isViteOrSourceLikeRequest(raw)) { return send404(res) } const origUrl = req.url delete req._parsedUrl req.url = sub + query let handler = sirvBySlug.get(slug) if (!handler) { handler = sirv(distDir, { dev: true, etag: true, maxAge: 0 }) sirvBySlug.set(slug, handler) } handler(req, res, () => { req.url = origUrl delete req._parsedUrl if (isViteOrSourceLikeRequest(raw)) { return send404(res) } if (wantsAssetNotHtml(req)) { return send404(res) } if (req.method === 'GET') { let html = indexHtmlBySlug.get(slug) if (html === undefined) { const p = path.join(distDir, 'index.html') html = fs.readFileSync(p, 'utf8') indexHtmlBySlug.set(slug, html) } res.statusCode = 200 res.setHeader('Content-Type', 'text/html; charset=utf-8') res.end(html) return } next() }) }) }, } }