- style.css convention: <entry dir>/style.css is imported by the virtual client entry (CSS is a client concern; server code never sees it — the v8 answer to sheetify), extracted and hashed by Vite, stylesheet-linked in the head and preloaded via Early Hints. Counter example styled. - SSR <title> from state: the server reads the first stream chunk before writing the head, by which point stores and the first render slice have run — DOMTitleChange emits land in the document title. Counter emits. - bankai build --prerender /,/about renders routes through the same toStream path to static <route>/index.html (precompressed, precached by the service worker, served with Early Hints). - bankai serve --h2: HTTP/2 with a generated local cert (openssl, cached; allowHTTP1) — browsers only act on Early Hints over h2/h3. Tested with a real h2 client observing the 103 interim response. - npm run size: the framework wire-size budget, enforced in CI. Whole framework (core + html engine + morph + hydrate) is 7.97 kB min+gzip / 7.15 kB brotli; budget 8.5 kB with the v7 '4kb' context documented (that number excluded the html engine, which lived in the browserify transform). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
167 lines
6.4 KiB
JavaScript
167 lines
6.4 KiB
JavaScript
// 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, prerendered, srv
|
|
|
|
before(async () => {
|
|
outDir = await mkdtemp(join(tmpdir(), 'bankai-test-'))
|
|
;({ meta, prerendered } = await build({ entry, outDir, title: 'counter', prerender: ['/'] }))
|
|
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('the style.css convention bundles css the server never imports', async () => {
|
|
assert.strictEqual(meta.assets.css.length, 1, 'extracted css asset')
|
|
const res = await get('/some/page')
|
|
const text = res.body.toString()
|
|
assert.match(text, /<link rel="stylesheet" href="\/assets\/.*\.css">/)
|
|
assert.match(String(res.headers.link), /as=style/, 'stylesheet in the Link header')
|
|
})
|
|
|
|
test('prerendered routes are written and served statically with hints', async () => {
|
|
assert.deepStrictEqual(prerendered, ['/'])
|
|
await access(join(outDir, 'index.html'))
|
|
const res = await get('/')
|
|
assert.strictEqual(res.status, 200)
|
|
assert.match(res.body.toString(), /count is 0/)
|
|
assert.strictEqual(res.interim[0]?.statusCode, 103, 'prerendered pages still send Early Hints')
|
|
})
|
|
|
|
test('the SSR <title> comes from state (DOMTitleChange during render)', async () => {
|
|
const res = await get('/some/page')
|
|
assert.match(res.body.toString(), /<title>count is 0<\/title>/)
|
|
})
|
|
|
|
test('live pages get 103 Early Hints carrying the route assets', async () => {
|
|
const res = await get('/some/page')
|
|
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('live pages stream SSR html with a filtered initialState tail', async () => {
|
|
const res = await get('/some/page')
|
|
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('h2: HTTP/2 server sends Early Hints and streams the page', async (t) => {
|
|
const { default: http2 } = await import('node:http2')
|
|
let h2srv
|
|
try {
|
|
h2srv = await serve({ outDir, port: 0, h2: true })
|
|
} catch (err) {
|
|
t.skip('openssl unavailable: ' + err.message)
|
|
return
|
|
}
|
|
|
|
const session = http2.connect(`https://localhost:${h2srv.port}`, { rejectUnauthorized: false })
|
|
const result = await new Promise((resolve, reject) => {
|
|
const req = session.request({ ':path': '/some/page' })
|
|
let interim = null
|
|
let status = null
|
|
let body = ''
|
|
req.on('headers', (headers) => { interim = headers })
|
|
req.on('response', (headers) => { status = headers[':status'] })
|
|
req.on('data', (d) => { body += d })
|
|
req.on('end', () => resolve({ interim, status, body }))
|
|
req.on('error', reject)
|
|
req.end()
|
|
})
|
|
session.close()
|
|
await h2srv.close()
|
|
|
|
assert.strictEqual(result.status, 200)
|
|
assert.strictEqual(result.interim?.[':status'], 103, '103 interim over h2')
|
|
assert.match(String(result.interim?.link), /rel=modulepreload/)
|
|
assert.match(result.body, /count is 0/)
|
|
})
|
|
|
|
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/)
|
|
})
|