113 lines
4.3 KiB
JavaScript
113 lines
4.3 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, 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/)
|
||
|
|
})
|