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.
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* 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()
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user