oficina / slides Public

Clone
git clone https://amadan.net/oficina/slides

Plain git — no account needed to clone.

Download

Download this file

1// fixtures/build-bundles.mjs — run by a person, output committed.
2// Docs' script, adopted (oficina/docs/fixtures/build-bundles.mjs).
3// The app performs no build step (spec §3.1: "one esbuild bundle
4// committed hashed"); this produces the artifact that makes that true:
5// a single, minified, content-hashed JS bundle committed to
6// internal/slides/static/, embedded into the Go binary by render.go's
7// //go:embed and resolved at init by internal/slides/assets_embed.go.
8//
9// internal/slides/static/, not a top-level static/: Go's //go:embed
10// cannot reach outside the directory of the .go file doing the
11// embedding, so the bundle lives beside the stylesheets that tree
12// already holds.
13//
14// Paths are resolved from this file's own location, so
15// `node build-bundles.mjs` works the same from fixtures/ or anywhere.
16// The gate runs it twice and requires a clean tree: the output is a
17// pure function of the sources and the pinned toolchain.
18import { build } from 'esbuild'
19import { createHash } from 'node:crypto'
20import { readFileSync, writeFileSync, rmSync, readdirSync, mkdirSync } from 'node:fs'
21import { join, dirname } from 'node:path'
22import { fileURLToPath } from 'node:url'
23
24const here = dirname(fileURLToPath(import.meta.url))
25const staticDir = join(here, '..', 'internal', 'slides', 'static')
26
27mkdirSync(staticDir, { recursive: true })
28
29// One bundle. The names here are the names assets_embed.go's
30// bundleKinds expects; that file panics at init if one of them has no
31// committed file, so a build that silently stopped emitting one cannot
32// start.
33const bundles = [
34 { name: 'editor', entry: 'editor-entry.js' }
35]
36
37for (const { name, entry } of bundles) {
38 const outfile = join(staticDir, `${name}.js`)
39 await build({
40 entryPoints: [join(here, entry)],
41 bundle: true,
42 format: 'esm',
43 minify: true,
44 target: 'es2020',
45 outfile
46 })
47
48 // Content hash in the filename so the asset is immutable-cacheable
49 // and a stale browser cache cannot serve a bundle that disagrees
50 // with the server.
51 const bytes = readFileSync(outfile)
52 const hash = createHash('sha256').update(bytes).digest('hex').slice(0, 16)
53
54 // Exactly one file per bundle must ever be committed — assets_embed.go
55 // panics at init if it finds more or less than one — so a rebuild
56 // removes every previous hashed file for THIS name before writing
57 // the new one. Anchored to the name so a second bundle, if one ever
58 // arrives, is never deleted by the first's rebuild.
59 const stale = new RegExp(`^${name}\\.[0-9a-f]{16}\\.js$`)
60 for (const f of readdirSync(staticDir)) {
61 if (stale.test(f)) rmSync(join(staticDir, f))
62 }
63 writeFileSync(join(staticDir, `${name}.${hash}.js`), bytes)
64 rmSync(outfile)
65
66 console.log(`wrote internal/slides/static/${name}.${hash}.js (${bytes.length} bytes)`)
67}
68