Files
buuh/packages/bankai/test/build-serve.test.js
T

167 lines
6.4 KiB
JavaScript
Raw Normal View History

// 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/)
})