Skip to content

Commit

Permalink
Add very basic benchmark setup
Browse files Browse the repository at this point in the history
This is just the server for now, without any runner
  • Loading branch information
marvinhagemeister committed Sep 13, 2022
1 parent 4823f83 commit bc88066
Show file tree
Hide file tree
Showing 11 changed files with 258 additions and 7 deletions.
14 changes: 14 additions & 0 deletions benches/cases/create-computed/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="dark light" />
<title>{%TITLE%}</title>
</head>
<body>
<h1>{%NAME%}</h1>
<script type="module" src="./index.js"></script>
</body>
</html>
14 changes: 14 additions & 0 deletions benches/cases/create-computed/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { signal, computed } from "@preact/signals-core";
import * as bench from "../measure";

const count = signal(0);
const double = computed(() => count.value * 2);

bench.start();

for (let i = 0; i < 20000000; i++) {
count.value++;
double.value;
}

bench.stop();
35 changes: 35 additions & 0 deletions benches/cases/measure.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
let startTime = 0;

export function start() {
startTime = performance.now();
}

export function stop() {
const end = performance.now();
const duration = end - startTime;

const url = new URL(window.location.href);
const test = url.pathname;
const variant = url.searchParams.get("variant") || "default";

let memory = 0;
if ("gc" in window && "memory" in window) {
window.gc();
memory = performance.memory.usedJSHeapSize / 1e6;
}

// eslint-disable-next-line no-console
console.log(
`Time: %c${duration}ms ${memory > 0 ? `${memory}MB` : ""}%c- done`,
"color:green",
"color:inherit"
);

return fetch("/result", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ test, duration, variant, memory }),
});
}
30 changes: 30 additions & 0 deletions benches/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="dark light" />
<title>Benchmarks</title>
</head>
<body>
<div class="page">
<h1>Benchmarks</h1>
<ul>
{%LIST%}
</ul>
</div>
<style>
body {
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}

.page {
margin: 0 auto;
max-width: 40rem;
padding: 2rem;
}
</style>
</body>
</html>
19 changes: 19 additions & 0 deletions benches/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "demo",
"private": true,
"scripts": {
"start": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"preact": "10.9.0",
"@preact/signals-core": "workspace:../packages/core",
"@preact/signals": "workspace:../packages/preact"
},
"devDependencies": {
"@types/connect": "^3.4.35",
"tiny-glob": "^0.2.9",
"vite": "^3.0.7"
}
}
Binary file added benches/public/favicon.ico
Binary file not shown.
8 changes: 8 additions & 0 deletions benches/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact",
"module": "esnext"
}
}
104 changes: 104 additions & 0 deletions benches/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { defineConfig, Plugin } from "vite";
import { resolve, posix } from "path";
import fs from "fs";
import { NextHandleFunction } from "connect";

// Automatically set up aliases for monorepo packages.
// Uses built packages in prod, "source" field in dev.
function packages(prod: boolean) {
const alias: Record<string, string> = {};
const root = resolve(__dirname, "../packages");
for (let name of fs.readdirSync(root)) {
if (name[0] === ".") continue;
const p = resolve(root, name, "package.json");
const pkg = JSON.parse(fs.readFileSync(p, "utf-8"));
if (pkg.private) continue;
const entry = prod ? "." : pkg.source;
alias[pkg.name] = resolve(root, name, entry);
}
return alias;
}

export default defineConfig(env => ({
plugins: [indexPlugin(), multiSpa(["index.html", "cases/**/*.html"])],
build: {
polyfillModulePreload: false,
cssCodeSplit: false,
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx", ".d.ts"],
alias: env.mode === "production" ? {} : packages(false),
},
}));

function indexPlugin(): Plugin {
return {
name: "index-plugin",
async transformIndexHtml(html, data) {
if (data.path === "/index.html") {
const cases = await getTestCases("cases/**/*.html");
return html.replace(
"{%LIST%}",
cases.htmlEntries.length > 0
? cases.htmlUrls
.map(url => `<li><a href="${encodeURI(url)}">${url}</a></li>`)
.join("\n")
: ""
);
}

const name = posix.basename(posix.dirname(data.path));
return html.replace("{%TITLE%}", name).replace("{%NAME%}", name);
},
};
}

// Vite plugin to serve and build multiple SPA roots (index.html dirs)
import glob from "tiny-glob";

async function getTestCases(entries: string | string[]) {
let e = await Promise.all([entries].flat().map(x => glob(x)));
const htmlEntries = Array.from(new Set(e.flat()));
// sort by length, longest to shortest:
const htmlUrls = htmlEntries
.map(x => "/" + x)
.sort((a, b) => b.length - a.length);
return { htmlEntries, htmlUrls };
}

function multiSpa(entries: string | string[]): Plugin {
let htmlEntries: string[];
let htmlUrls: string[];

const middleware: NextHandleFunction = (req, res, next) => {
const url = req.url!;
// ignore /@x and file extension URLs:
if (/(^\/@|\.[a-z]+(?:\?.*)?$)/i.test(url)) return next();
// match the longest index.html parent path:
for (let html of htmlUrls) {
if (!html.endsWith("/index.html")) continue;
if (!url.startsWith(html.slice(0, -10))) continue;
req.url = html;
break;
}
next();
};

return {
name: "multi-spa",
async config() {
const cases = await getTestCases(entries);
htmlEntries = cases.htmlEntries;
htmlUrls = cases.htmlUrls;
},
buildStart(options) {
options.input = htmlEntries;
},
configurePreviewServer(server) {
server.middlewares.use(middleware);
},
configureServer(server) {
server.middlewares.use(middleware);
},
};
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"docs:start": "cd docs && pnpm start",
"docs:build": "cd docs && pnpm build",
"docs:preview": "cd docs && pnpm preview",
"bench:start": "cd benches && pnpm start",
"ci:build": "pnpm build && pnpm docs:build",
"ci:test": "pnpm lint && pnpm test"
},
Expand Down
38 changes: 31 additions & 7 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@ packages:
- "packages/*"
# all packages in subdirs of components/
- "docs/**"
# all packages in subdirs of components/
- "benches/**"
# exclude packages that are inside test directories
- "!**/test/**"

0 comments on commit bc88066

Please sign in to comment.