feat(bankai): bankai v10 — the isomorphic compiler & server on Vite 8

One command, zero config, and only the client ever bundles: v8 server
code is plain ESM that Node runs as-authored, so there is no server
build to rot (the lesson of v9).

- start: Vite middleware mode + HMR with per-request streaming SSR via
  ssrLoadModule; a virtual client entry generates the browser glue so
  the user writes exactly one isomorphic module (plan decision D5).
- build: client bundle via Vite 8/Rolldown, manifest-derived route
  assets in dist/bankai.json, service worker built with the precache
  list injected (choo-service-worker convention, manifest edition),
  brotli+gzip precompression of every text asset.
- serve: immutable caching + precompressed negotiation for hashed
  assets, 103 Early Hints (res.writeEarlyHints) with the route's assets
  before every page, streaming SSR, and a window.initialState tail with
  script-breakout-safe serialization and choo internals filtered out.
- inspect: raw/gzip/brotli size report.

Integration tests drive the real counter example through build and
serve, asserting the 103 interim response at the HTTP level.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
This commit is contained in:
Bendik Aagaard Lynghaug
2026-09-08 19:32:37 +02:00
co-authored by Claude Fable 5
parent d755af5ca0
commit 7e08b585ac
13 changed files with 716 additions and 1 deletions
+112
View File
@@ -0,0 +1,112 @@
// The full production pipeline against the real counter example:
// build → artifacts on disk → serve → 103 Early Hints, streamed SSR,
// filtered initialState, precompressed asset negotiation.
import { test, before, after } from 'node:test'
import assert from 'node:assert'
import http from 'node:http'
import { access, mkdtemp, rm } from 'node:fs/promises'
import { join, dirname } from 'node:path'
import { tmpdir } from 'node:os'
import { fileURLToPath } from 'node:url'
import build from '../lib/build.js'
import serve from '../lib/serve.js'
const repo = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..')
const entry = join(repo, 'examples', 'counter', 'app.js')
let outDir, meta, srv
before(async () => {
outDir = await mkdtemp(join(tmpdir(), 'bankai-test-'))
;({ meta } = await build({ entry, outDir, title: 'counter' }))
srv = await serve({ outDir, port: 0 })
})
after(async () => {
await srv?.close()
if (outDir) await rm(outDir, { recursive: true, force: true })
})
function get (path, headers = {}) {
return new Promise((resolve, reject) => {
const req = http.request({ host: 'localhost', port: srv.port, path, headers }, (res) => {
const chunks = []
res.on('data', (d) => chunks.push(d))
res.on('end', () => resolve({
status: res.statusCode,
headers: res.headers,
body: Buffer.concat(chunks),
interim: req.interim
}))
})
req.interim = []
req.on('information', (info) => req.interim.push(info))
req.on('error', reject)
req.end()
})
}
test('build produced manifest, metadata and precompressed assets', async () => {
await access(join(outDir, '.vite', 'manifest.json'))
await access(join(outDir, 'bankai.json'))
assert.strictEqual(meta.assets.scripts.length, 1, 'one entry chunk')
const chunk = meta.assets.scripts[0]
await access(join(outDir, chunk.slice(1)))
await access(join(outDir, chunk.slice(1) + '.br'))
await access(join(outDir, chunk.slice(1) + '.gz'))
})
test('pages get 103 Early Hints carrying the route assets', async () => {
const res = await get('/')
assert.strictEqual(res.interim.length, 1, 'one interim response')
assert.strictEqual(res.interim[0].statusCode, 103)
assert.match(String(res.interim[0].headers.link), /rel=modulepreload/)
assert.match(String(res.headers.link), /rel=modulepreload/, 'Link header repeated on the final response')
})
test('pages stream SSR html with a filtered initialState tail', async () => {
const res = await get('/')
const text = res.body.toString()
assert.strictEqual(res.status, 200)
assert.match(text, /<h1>count is 0<\/h1>/, 'server-rendered view')
assert.match(text, /<script type="module" src="\/assets\//, 'client entry wired')
assert.match(text, /window\.initialState=/, 'state serialized')
assert.ok(!/initialState=.*"events"/.test(text), 'internals filtered from state')
})
test('hashed assets are immutable and served precompressed on request', async () => {
const chunk = meta.assets.scripts[0]
const plain = await get(chunk)
assert.strictEqual(plain.status, 200)
assert.match(plain.headers['content-type'], /javascript/)
assert.match(plain.headers['cache-control'], /immutable/)
assert.strictEqual(plain.headers['content-encoding'], undefined)
const br = await get(chunk, { 'accept-encoding': 'br, gzip' })
assert.strictEqual(br.headers['content-encoding'], 'br')
assert.ok(br.body.length < plain.body.length, 'brotli variant is smaller')
const gz = await get(chunk, { 'accept-encoding': 'gzip' })
assert.strictEqual(gz.headers['content-encoding'], 'gzip')
})
test('the service worker builds with the precache list injected', async () => {
const res = await get('/sw.js')
assert.strictEqual(res.status, 200)
const sw = res.body.toString()
assert.ok(!sw.includes('__BANKAI_ASSETS__'), 'define was applied')
assert.ok(sw.includes(meta.assets.scripts[0]), 'hashed entry chunk in the precache list')
assert.match(sw, /[`"']\/[`"']\s*,/, 'root page precached (any quote style the minifier picks)')
})
test('missing assets 404; extensionless paths fall through to SSR', async () => {
const miss = await get('/assets/nope-not-real.js')
assert.strictEqual(miss.status, 404)
const page = await get('/some/page')
assert.strictEqual(page.status, 200, 'wildcard route rendered')
assert.match(page.body.toString(), /count is 0/)
})