Skip to content

Commit bc88066

Browse files
Add very basic benchmark setup
This is just the server for now, without any runner
1 parent 4823f83 commit bc88066

File tree

11 files changed

+258
-7
lines changed

11 files changed

+258
-7
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<meta name="color-scheme" content="dark light" />
8+
<title>{%TITLE%}</title>
9+
</head>
10+
<body>
11+
<h1>{%NAME%}</h1>
12+
<script type="module" src="./index.js"></script>
13+
</body>
14+
</html>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { signal, computed } from "@preact/signals-core";
2+
import * as bench from "../measure";
3+
4+
const count = signal(0);
5+
const double = computed(() => count.value * 2);
6+
7+
bench.start();
8+
9+
for (let i = 0; i < 20000000; i++) {
10+
count.value++;
11+
double.value;
12+
}
13+
14+
bench.stop();

benches/cases/measure.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
let startTime = 0;
2+
3+
export function start() {
4+
startTime = performance.now();
5+
}
6+
7+
export function stop() {
8+
const end = performance.now();
9+
const duration = end - startTime;
10+
11+
const url = new URL(window.location.href);
12+
const test = url.pathname;
13+
const variant = url.searchParams.get("variant") || "default";
14+
15+
let memory = 0;
16+
if ("gc" in window && "memory" in window) {
17+
window.gc();
18+
memory = performance.memory.usedJSHeapSize / 1e6;
19+
}
20+
21+
// eslint-disable-next-line no-console
22+
console.log(
23+
`Time: %c${duration}ms ${memory > 0 ? `${memory}MB` : ""}%c- done`,
24+
"color:green",
25+
"color:inherit"
26+
);
27+
28+
return fetch("/result", {
29+
method: "POST",
30+
headers: {
31+
"Content-Type": "application/json",
32+
},
33+
body: JSON.stringify({ test, duration, variant, memory }),
34+
});
35+
}

benches/index.html

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<meta name="color-scheme" content="dark light" />
8+
<title>Benchmarks</title>
9+
</head>
10+
<body>
11+
<div class="page">
12+
<h1>Benchmarks</h1>
13+
<ul>
14+
{%LIST%}
15+
</ul>
16+
</div>
17+
<style>
18+
body {
19+
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
20+
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
21+
}
22+
23+
.page {
24+
margin: 0 auto;
25+
max-width: 40rem;
26+
padding: 2rem;
27+
}
28+
</style>
29+
</body>
30+
</html>

benches/package.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "demo",
3+
"private": true,
4+
"scripts": {
5+
"start": "vite",
6+
"build": "vite build",
7+
"preview": "vite preview"
8+
},
9+
"dependencies": {
10+
"preact": "10.9.0",
11+
"@preact/signals-core": "workspace:../packages/core",
12+
"@preact/signals": "workspace:../packages/preact"
13+
},
14+
"devDependencies": {
15+
"@types/connect": "^3.4.35",
16+
"tiny-glob": "^0.2.9",
17+
"vite": "^3.0.7"
18+
}
19+
}

benches/public/favicon.ico

15 KB
Binary file not shown.

benches/tsconfig.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "../tsconfig.json",
3+
"compilerOptions": {
4+
"jsx": "react-jsx",
5+
"jsxImportSource": "preact",
6+
"module": "esnext"
7+
}
8+
}

benches/vite.config.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { defineConfig, Plugin } from "vite";
2+
import { resolve, posix } from "path";
3+
import fs from "fs";
4+
import { NextHandleFunction } from "connect";
5+
6+
// Automatically set up aliases for monorepo packages.
7+
// Uses built packages in prod, "source" field in dev.
8+
function packages(prod: boolean) {
9+
const alias: Record<string, string> = {};
10+
const root = resolve(__dirname, "../packages");
11+
for (let name of fs.readdirSync(root)) {
12+
if (name[0] === ".") continue;
13+
const p = resolve(root, name, "package.json");
14+
const pkg = JSON.parse(fs.readFileSync(p, "utf-8"));
15+
if (pkg.private) continue;
16+
const entry = prod ? "." : pkg.source;
17+
alias[pkg.name] = resolve(root, name, entry);
18+
}
19+
return alias;
20+
}
21+
22+
export default defineConfig(env => ({
23+
plugins: [indexPlugin(), multiSpa(["index.html", "cases/**/*.html"])],
24+
build: {
25+
polyfillModulePreload: false,
26+
cssCodeSplit: false,
27+
},
28+
resolve: {
29+
extensions: [".ts", ".tsx", ".js", ".jsx", ".d.ts"],
30+
alias: env.mode === "production" ? {} : packages(false),
31+
},
32+
}));
33+
34+
function indexPlugin(): Plugin {
35+
return {
36+
name: "index-plugin",
37+
async transformIndexHtml(html, data) {
38+
if (data.path === "/index.html") {
39+
const cases = await getTestCases("cases/**/*.html");
40+
return html.replace(
41+
"{%LIST%}",
42+
cases.htmlEntries.length > 0
43+
? cases.htmlUrls
44+
.map(url => `<li><a href="${encodeURI(url)}">${url}</a></li>`)
45+
.join("\n")
46+
: ""
47+
);
48+
}
49+
50+
const name = posix.basename(posix.dirname(data.path));
51+
return html.replace("{%TITLE%}", name).replace("{%NAME%}", name);
52+
},
53+
};
54+
}
55+
56+
// Vite plugin to serve and build multiple SPA roots (index.html dirs)
57+
import glob from "tiny-glob";
58+
59+
async function getTestCases(entries: string | string[]) {
60+
let e = await Promise.all([entries].flat().map(x => glob(x)));
61+
const htmlEntries = Array.from(new Set(e.flat()));
62+
// sort by length, longest to shortest:
63+
const htmlUrls = htmlEntries
64+
.map(x => "/" + x)
65+
.sort((a, b) => b.length - a.length);
66+
return { htmlEntries, htmlUrls };
67+
}
68+
69+
function multiSpa(entries: string | string[]): Plugin {
70+
let htmlEntries: string[];
71+
let htmlUrls: string[];
72+
73+
const middleware: NextHandleFunction = (req, res, next) => {
74+
const url = req.url!;
75+
// ignore /@x and file extension URLs:
76+
if (/(^\/@|\.[a-z]+(?:\?.*)?$)/i.test(url)) return next();
77+
// match the longest index.html parent path:
78+
for (let html of htmlUrls) {
79+
if (!html.endsWith("/index.html")) continue;
80+
if (!url.startsWith(html.slice(0, -10))) continue;
81+
req.url = html;
82+
break;
83+
}
84+
next();
85+
};
86+
87+
return {
88+
name: "multi-spa",
89+
async config() {
90+
const cases = await getTestCases(entries);
91+
htmlEntries = cases.htmlEntries;
92+
htmlUrls = cases.htmlUrls;
93+
},
94+
buildStart(options) {
95+
options.input = htmlEntries;
96+
},
97+
configurePreviewServer(server) {
98+
server.middlewares.use(middleware);
99+
},
100+
configureServer(server) {
101+
server.middlewares.use(middleware);
102+
},
103+
};
104+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"docs:start": "cd docs && pnpm start",
2121
"docs:build": "cd docs && pnpm build",
2222
"docs:preview": "cd docs && pnpm preview",
23+
"bench:start": "cd benches && pnpm start",
2324
"ci:build": "pnpm build && pnpm docs:build",
2425
"ci:test": "pnpm lint && pnpm test"
2526
},

pnpm-lock.yaml

Lines changed: 31 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)