Files
Talks/portal/.vitepress/vite-plugin-slide-dists.mjs
Le Prévost-Corvellec Arnault 354c8cd9ef Add Makefile, package.json, and initial configuration for VitePress and Slidev integration
- Introduced a Makefile for streamlined commands related to building, running, and managing presentations.
- Created package.json to define project metadata, dependencies, and scripts for development and testing.
- Added .gitignore entries to exclude build artifacts and temporary files from version control.
- Included playwright.config.mjs for end-to-end testing setup with Playwright.
- Added README.md for project documentation, outlining setup instructions and development guidelines.
2026-04-11 15:59:20 +02:00

149 lines
4.8 KiB
JavaScript

/**
* Dev : sert chaque dist-<slug>/ sous /slides/<slug>/ (sources = dossiers à la racine du dépôt).
* La racine `/slides/<slug>/` (et `/slides/<slug>`) redirige en 302 vers `/slides/<slug>/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<string, ReturnType<typeof sirv>>} */
const sirvBySlug = new Map()
/** @type {Map<string, string>} */
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()
})
})
},
}
}