a post-component library for building user-interfaces on the web.
at push-qwruonslltow 131 lines 4.1 kB view raw
1import { createBirpc } from 'birpc' 2import * as mitata from 'mitata' 3import * as devalue from './devalue.ts' 4import type { ServerFunctions } from './main.ts' 5import { assert } from './test.ts' 6 7export type TestResult = { name: string } & ({ result: 'pass'; duration: number } | { result: 'fail'; reason: unknown }) 8 9export interface ClientFunctions { 10 define<K extends keyof typeof globalThis>(name: K, value: (typeof globalThis)[K]): void 11 import(path: string): Promise<unknown> 12 run_tests(options: { filter?: RegExp }): Promise<void> 13 run_benchmarks(options: { filter?: RegExp; builds?: Array<{ dir: string; ref: string }> }): Promise<mitata.trial[]> 14 stop_coverage(): Promise<void> 15} 16 17const client: ClientFunctions = { 18 define(name, value) { 19 globalThis[name] = value 20 }, 21 import(path) { 22 return import(path) 23 }, 24 async run_tests(options) { 25 for (const test of tests) { 26 if (options.filter?.test(test.name) === false) continue 27 28 try { 29 const start = performance.now() 30 await test.fn() 31 const end = performance.now() 32 await server.report_result({ name: test.name, result: 'pass', duration: end - start }) 33 } catch (error) { 34 await server.report_result({ name: test.name, result: 'fail', reason: error }) 35 } 36 } 37 }, 38 async run_benchmarks(options) { 39 const { builds } = options 40 41 if (!builds) { 42 const { benchmarks } = await mitata.run({ filter: options.filter }) 43 return benchmarks 44 } else { 45 const versions = await Promise.all( 46 builds.map(async ({ dir, ref }) => { 47 return { 48 index: (await import(`../../${dir}/dist/index.min.js`)) as typeof import('dhtml'), 49 client: (await import(`../../${dir}/dist/client.min.js`)) as typeof import('dhtml/client'), 50 server: (await import(`../../${dir}/dist/server.min.js`)) as typeof import('dhtml/server'), 51 ref, 52 } 53 }), 54 ) 55 56 // TODO: much like tests, export a custom bench() function from test.ts, 57 // which collects in an array defined in this file, so that we don't need 58 // to hardcode the bench-comparison path. 59 // unlike test(), bench() will provide the lib argument to the function. 60 const { get_benchmarks } = await import('../../../src/client/tests/bench-comparison.ts' as string) 61 62 const called_versions = versions.map(lib => ({ 63 ref: lib.ref, 64 fns: Object.entries(get_benchmarks(lib)) 65 .filter(([name]) => (options.filter === undefined ? true : options.filter.test(name))) 66 .map(([, value]) => { 67 assert(typeof value === 'function') 68 return value 69 }), 70 })) 71 72 const n_fns = called_versions[0].fns.length 73 assert(n_fns > 0, 'filtered to 0 functions') 74 for (const { fns } of called_versions.slice(1)) { 75 assert(fns.length === n_fns, 'expected the same number of functions from all modules') 76 } 77 78 // Warmup runs for each version to reduce cache effects 79 console.log('Running warmup iterations...') 80 for (let warmup = 0; warmup < 3; warmup++) { 81 for (const { ref, fns } of called_versions) { 82 for (const fn of fns) fn() 83 } 84 } 85 console.log('Warmup complete, starting benchmarks...') 86 87 mitata.summary(() => { 88 for (const { ref, fns } of called_versions) { 89 mitata 90 .bench(ref, () => { 91 for (const fn of fns) fn() 92 }) 93 .gc('inner') 94 } 95 }) 96 97 const { benchmarks } = await mitata.run() 98 return benchmarks 99 } 100 }, 101 async stop_coverage() { 102 if (typeof process === 'undefined') return 103 const v8 = await import('node:v8') 104 v8.takeCoverage() 105 v8.stopCoverage() 106 }, 107} 108 109declare global { 110 var __onmessage: (fn: (data: any) => void) => void 111 var __postMessage: (data: any) => void 112} 113 114const server = createBirpc<ServerFunctions, ClientFunctions>( 115 client, 116 typeof process === 'undefined' 117 ? { 118 post: window.__postMessage, 119 on: fn => (window.__onmessage = fn), 120 serialize: devalue.stringify, 121 deserialize: devalue.parse, 122 } 123 : { 124 post: data => process.send!(data), 125 on: fn => process.on('message', fn), 126 serialize: devalue.stringify, 127 deserialize: devalue.parse, 128 }, 129) 130 131export const tests: Array<{ name: string; fn: () => void | Promise<void> }> = []