mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
Every import, the catalog pin and the docs name @hanzo/app and github.com/hanzoai/app. lib/rooms.ts is lib/app.ts and components/rooms.tsx is components/app.tsx.
58 lines
2.8 KiB
TypeScript
58 lines
2.8 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
import { readdirSync, readFileSync } from 'node:fs'
|
|
import { join, relative } from 'node:path'
|
|
import { gzipSync } from 'node:zlib'
|
|
import { OUT, ROOT, read } from './export'
|
|
|
|
/**
|
|
* What the home page makes a browser download before it can run.
|
|
*
|
|
* The first load of `/` was 635 KB of gzipped script, and most of it drew
|
|
* nothing on the page: a server component importing the `@hanzo/ui` root barrel
|
|
* registered its ~150 client components — charts, 3D, editors — as client
|
|
* entries of every route, and the root not-found put framer in the first load
|
|
* of every page. Both are invisible in a screenshot, so the bytes are asserted.
|
|
*/
|
|
|
|
/** Gzipped size of the module scripts the page's HTML names (the noModule polyfill is never fetched). */
|
|
function firstLoad(page: string): number {
|
|
const html = read(page)
|
|
const chunks = new Set([...html.matchAll(/<script src="\/_next\/(static\/chunks\/[^"]+?\.js)"(?![^>]*noModule)/g)].map((m) => decodeURIComponent(m[1])))
|
|
let total = 0
|
|
for (const chunk of chunks) total += gzipSync(readFileSync(join(OUT, '_next', chunk)), { level: 6 }).length
|
|
return total
|
|
}
|
|
|
|
test('the home page first load stays under its budget', () => {
|
|
// Measured at 415 KB when this was written; the budget is that plus room for
|
|
// a section, not for a library. 445 since @hanzo/app 0.1.39, whose lib/host
|
|
// (which the landing's plans reach through lib/destination) reads the app's
|
|
// paths with @hanzo/build's route grammar: some 3.5 KB.
|
|
expect(firstLoad('index.html') / 1024).toBeLessThan(445)
|
|
})
|
|
|
|
test('a server component reaches @hanzo/ui through its client boundary', () => {
|
|
// `components/ui` is the one module that may name the root barrel without
|
|
// being a client component: it IS the boundary. A file with no 'use client'
|
|
// can render as a server component, and then the barrel ships whole.
|
|
const offenders: string[] = []
|
|
const walk = (dir: string) => {
|
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
const path = join(dir, entry.name)
|
|
if (entry.isDirectory()) walk(path)
|
|
else if (/\.tsx?$/.test(entry.name)) {
|
|
const code = readFileSync(path, 'utf8')
|
|
// The directive may follow comments, never code.
|
|
const head = code.replace(/^(?:\s+|\/\/[^\n]*|\/\*[\s\S]*?\*\/)*/, '')
|
|
if (/^['"]use client['"]/.test(head)) continue
|
|
// A code sample is a template string, and an import written in one ships
|
|
// as text (the server pages that highlight samples, since 11265a21).
|
|
const source = code.replace(/`(?:\\[\s\S]|[^`\\])*`/g, '``')
|
|
if (/from\s+['"]@hanzo\/ui['"]/.test(source)) offenders.push(relative(ROOT, path))
|
|
}
|
|
}
|
|
}
|
|
for (const dir of ['app', 'components', 'hooks', 'lib']) walk(join(ROOT, dir))
|
|
expect(offenders).toEqual([])
|
|
})
|