// Builds the deck page from src/deck.html plus every src/slides/*.html fragment
// (in file-name order), then inlines it and everything under src/c/ into
// app/assets.generated.ts so the package app serves with no external requests.
import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
import { join, relative, extname } from 'node:path'
const root = new URL('..', import.meta.url).pathname
const srcDir = join(root, 'src')
const types = {
'.html': 'text/html; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.js': 'text/javascript; charset=utf-8',
'.svg': 'image/svg+xml',
'.png': 'image/png',
'.ico': 'image/x-icon',
'.jpg': 'image/jpeg',
'.webp': 'image/webp',
'.woff2': 'font/woff2',
'.txt': 'text/plain; charset=utf-8',
}
const textTypes = new Set(['.html', '.css', '.js', '.svg', '.txt'])
function walk(dir) {
return readdirSync(dir).flatMap((name) => {
const p = join(dir, name)
return statSync(p).isDirectory() ? walk(p) : [p]
})
}
const slidesDir = join(srcDir, 'slides')
const slideFiles = readdirSync(slidesDir).filter((f) => f.endsWith('.html')).sort((a, b) => (a < b ? -1 : 1))
const deck = readFileSync(join(srcDir, 'deck.html'), 'utf8')
if (!deck.includes('')) throw new Error('src/deck.html needs a marker')
// Shared CSS and JS are inlined: Chrome can fetch
resources before the page's
// runtime tag exists, which would resolve them against the wrong directory.
const inlined = new Set()
const inline = (f) => (inlined.add(`c/${f}`), readFileSync(join(srcDir, 'c', f), 'utf8').trim())
const page = deck
.replace(//g, (_, f) => ``)
.replace(/`)
.replace('', () => slideFiles.map((f) => readFileSync(join(slidesDir, f), 'utf8').trim()).join('\n'))
const entries = walk(join(srcDir, 'c')).sort().filter((file) => !inlined.has(relative(srcDir, file).split('\\').join('/'))).map((file) => {
const ext = extname(file)
const type = types[ext]
if (!type) throw new Error(`No content type for ${file}`)
const path = relative(srcDir, file).split('\\').join('/')
const buf = readFileSync(file)
return textTypes.has(ext)
? { path, type, text: buf.toString('utf8') }
: { path, type, base64: buf.toString('base64') }
})
entries.unshift({ path: 'index.html', type: types['.html'], text: page })
// Landing page and the benchmark graph (data inlined from src/data-trend.json, built from data/*.csv).
entries.unshift({ path: 'home.html', type: types['.html'], text: readFileSync(join(srcDir, 'pages/home.html'), 'utf8') })
entries.unshift({ path: 'graph.html', type: types['.html'], text: readFileSync(join(srcDir, 'pages/graph.html'), 'utf8').replace('/* data */null', () => readFileSync(join(srcDir, 'data-trend.json'), 'utf8').trim()) })
const out = `// Generated by scripts/build-assets.mjs. Do not edit by hand.
export type Asset = { type: string; text?: string; base64?: string }
export const assets: Record = ${JSON.stringify(
Object.fromEntries(entries.map(({ path, ...rest }) => [path, rest])),
null,
'\t',
)}
`
writeFileSync(join(root, 'app/assets.generated.ts'), out)
const bytes = entries.reduce((n, e) => n + (e.text?.length ?? e.base64.length), 0)
console.log(`Composed ${slideFiles.length} slides. Wrote ${entries.length} assets (${(bytes / 1024).toFixed(0)} KiB) to app/assets.generated.ts`)