Skip to content

Commit 8e9be2d

Browse files
committed
Fill out arch and readme a bit
1 parent 4a3d8a6 commit 8e9be2d

File tree

4 files changed

+91
-134
lines changed

4 files changed

+91
-134
lines changed

Architecture.md

Lines changed: 59 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -7,130 +7,75 @@
77

88
There are 4 main organizational concepts used in this repo:
99

10-
| Concept | Description |
11-
| -------------- | ------------------------------------------------------------------------------------------------------------------------------- |
12-
| App | A single page app that supports operations to be benchmarked (e.g. a TODO app, `/apps/todo` folder) |
13-
| Benchmark | An HTML file that renders an app and measures a specific operation of that app (e.g. adding a new todo, `/apps/todo/todo.html`) |
14-
| Implementation | An implementation of an app (e.g. Preact with class components or Preact using hooks, `/apps/todo/preact-class`) |
15-
| Dependency | A dependency of an implementation (e.g. latest Preact version, `/dependencies/preact/latest`) |
16-
17-
### Apps, benchmarks, and implementations
18-
19-
An app is a collection of benchmarks and implementations in the `<repo root>/apps` directory. For example, the `table-app` directory refers to the application that the `js-framework-benchmark` repo uses to measure the performance of various JavaScript frameworks.
20-
21-
Inside the `table-app` directory we have implemented a couple different benchmarks that measure different operations or aspects of that app. For example, the `replace1k.html` benchmark measures the time it takes to replace 1,000 rows in the table. The `hydrate1k.html` benchmark measures the time it takes to hydrate 1,000 rows in the table.
22-
23-
For this app, there are also a couple different implementations of that app that we can measure:
10+
| Concept | Description |
11+
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
12+
| App | A single page app that supports operations to be benchmarked (e.g. a TODO app, `/apps/todo` folder) |
13+
| Benchmark | An HTML file that renders an app and measures various aspects a specific operation of that app (e.g. adding a new todo in `/apps/todo/todo.html`) |
14+
| Implementation | An implementation of an app (e.g. Preact with class components or Preact using hooks, `/apps/todo/preact-class`) |
15+
| Dependency | A dependency of an implementation (e.g. latest Preact version, `/dependencies/preact/latest`) |
16+
17+
We define these using a folder convention described below:
18+
19+
```text
20+
- apps/
21+
- todo/ <-- An app
22+
- preact-class/ <-- An implementation of todo using Preact class components
23+
- preact-hooks/ <-- ... ... using Preact class components
24+
- preact-compat/ <-- ... ... using Preact compat
25+
- ...
26+
- todo.html <-- A specific benchmark using the todo app
27+
- ...
28+
- filter-list/
29+
- dependencies/ <-- Collection of dependencies whose version can be overridden
30+
- preact/
31+
- local/ <-- A local version of Preact
32+
- latest/ <-- The latest version of Preact
33+
- v8/ <-- A specific version of Preact
34+
- @preact/signals/
35+
- local/
36+
- latest/
37+
- ...
38+
- ...
39+
```
2440

25-
- `preact-class` uses Preact class components
26-
- `preact-hooks` uses Preact hooks.
27-
- `preact-compat` uses Preact's compatibility layer
28-
- `preact-signals` implements the same app using `@preact/signals`
41+
All implementations of an app expose the same API. This requirement allows us to compare the performance of different implementations of the same app. For example, we can compare the performance of a Preact class component implementation of a TODO app with a Preact hooks implementation of the same app.
2942

30-
The pattern described here is used for all apps in the `apps` directory. The folders inside of an app's directory typically are named after the implementation they contain. HTML files within in the app's directory contain the code to load and measure an operation on implementation of that app.
43+
All version of a dependency have the same API as best as possible. This constraint allows us to compare the performance of different versions of the same dependency. For example, we can compare the performance of Preact v8.0.0 with Preact v10.0.0. Obviously, some things aren't possible, such as implementing hooks in Preact v8.0.0, but we can still compare the performance of the features & behaviors that are shared between versions.
3144

3245
### Dependencies
3346

3447
Each app is implemented using a JS framework (namely Preact or one of our ecosystem libraries). It is useful to be able to measure and compare how different versions of a framework perform. For example, we might want to compare how Preact v10.5.0 performs compared to Preact v10.6.0. Or how a change in my local repository of Preact performs compared to the latest version of Preact.
3548

3649
All of the dependencies we support comparing are in the `<repo root>/dependencies` directory. Each dependency has a directory named after the dependency (and scoped dependencies are nested so `@preact/signals` will be in the `dependencies/@preact/signals` folder). Inside that directory are directories for each version of that dependency that we support. For example, the `preact` directory contains a `local` directory that will load code from a local clone of Preact repository. It also contains a `latest` directory that contains the latest version of Preact from NPM.
3750

38-
Inside these directories are the files that are needed to load that dependency. For example, the `local` directory contains scripts to setup loading from a local Preact repository. The `latest` directory contains a `package.json` file that points to the latest version of Preact on NPM. Inside these "proxy packages" you can implement code to massage over minor API differences that may exist between differences versions. For example, in the Preact versions we expose a `createRoot` API that wraps Preact to normalize over API differences between Preact versions.
51+
Inside these directories are the files that are needed to load that dependency. For example, the `local` directory contains scripts to setup loading from a local Preact repository. The `latest` directory contains a `package.json` file that points to the latest version of Preact on NPM. Inside these "proxy packages" you can implement code to normalize minor API differences that may exist between differences versions.
3952

4053
## Benchmark server
4154

42-
In order to actually run benchmarks, we need a server to serve the benchmark HTML files. We use Vite as this server, customized to support our needs.
43-
44-
The benchmark server is a web server that serves the benchmark apps and the implementations of those apps. It also serves the dependencies that the implementations use. The benchmark server is responsible for mapping the `impl` and `dep` query parameters to the correct implementation and dependency version.
45-
46-
> TODO: What should implementations import from? Different versions of a library
47-
> might have different exported APIs so they need to import a package that has
48-
> specific version of API. What if we define a common API that neither package
49-
> supports? We could do `declare module "preact"` in apps, but is it useful to
50-
> duplicate the preact TS types in the benchmark apps? We could install a version
51-
> of the libraries in apps/ to then import the types from?
52-
>
53-
> Perhaps we should just align to the latest API of the library and everything
54-
> else must conform to that API if possible.
55-
56-
## Decisions
57-
58-
- Q: Use Tach's web server or Vite
59-
- Requires a web server with the right middleware either way to support mapping "implementation" import to the correct implementation
60-
- A: Vite
61-
- Pro: supports JSX & TSX out of the box
62-
- Pro: Can customize dev experience more
63-
- e.g. load an initial html page to pick a configuration
64-
- Con: Requires running a separate web server
65-
- Unknown: Does Vite's module resolution handle nested `node_modules` with different package versions? Tach does this by creating temporary npm install directories for each version.
66-
- Tach
67-
- Pro: self-contained off-the-shelf
68-
- Con: requires authoring using browser supported syntax
69-
- Con: Have to mentally map ports to implementation/version
70-
- Q: Should benchmark apps be in the same package as CLI or in separate packages?
71-
- A: Probably a separate package since where to find apps is customizable via the CLI config
72-
- Q: How to extend the available implementations & versions from another repository?
73-
- A: Support a CLI config file extends/overrides what implementations & versions are available
74-
- Q: How do implementations specify default versions & versions specify default implementations?
75-
- A: CLI config can specify a default version for a dependency. Perhaps dependencies won't specify a default implementation anymore
76-
- Q: Where do version override packages live?
77-
- A: Any resolvable npm package (so local or NPM)?
78-
79-
## CLI ideas
80-
81-
- https://github.com/preactjs/benchmarks/discussions/1
82-
- `list` or `ls` command to list available dependency versions, and apps & implementations
83-
- Use `prompts` with `prompts.overrides` to provide an easier CLI experience
84-
- `--config` option to specify server config file for finding implementations & versions
85-
- Run Vite server in CLI process?
86-
- Should there be a "build-all" option to build a GH deployable version of all apps? Would probably be difficult as I'd need to build out versions of each implementation for each version that is deployable
87-
88-
## Running in other repos
89-
90-
- Consider exporting a function generate a "dependencies" config from the "@preact/benchmark-deps" package that takes in the paths to the relevant local repos
91-
- Then in each repo, a "prepare" step in the benches package.json script would run the function and write the config to a file in the benches directory. The local config will be ignored by Git and so can be customized with local file paths.
92-
93-
Or
94-
95-
TODO: Make decision about git submodule or not
96-
97-
- Should I just simplify this and use Git submodules? Which allows local branching and editing? And removes the need for a CLI config since we can assume more things about the repo structure.
98-
- ??? But how do I configure local repos for Preact & Signals & render-to-string?
99-
100-
## Vite server
101-
102-
- Middleware maps `impl` parameter to the correct implementation `index.js`:
103-
- ```html
104-
<script type="module">
105-
const implParam = new URLSearchParams(location.search).get("impl");
106-
const implementation = import("./" + implParam + "/index.js");
107-
</script>
108-
```
109-
- Middleware maps any requests for specified `dep` parameters to the dependency version's directory
110-
111-
## Folder structure
112-
113-
```txt
114-
- packages/
115-
- dependencies/
116-
- preact/
117-
- local/
118-
- master/
119-
- signals/
120-
- local/
121-
- master/
122-
- ...
123-
- cli/
124-
- apps/
125-
- table-app/
126-
- _shared/
127-
- preact-class/
128-
- preact-hooks/
129-
- preact-compat/
130-
- ...
131-
- 02_replace1k.html
132-
- 03_update1k10k.html
133-
- movie-app/ ...
134-
- todo/ ...
135-
- filter-list/ ...
55+
In order to actually run benchmarks, we need a server to serve the benchmark HTML files. We use Vite as this server, customized to support our needs. The primary objective is to:
56+
57+
1. Serve benchmark HTML files
58+
2. Map the `impl` query parameter to the correct implementation directory
59+
3. Map the `dep` query parameter to the correct dependency version directory
60+
61+
The URL to the HTML file specifies which implementation and versions of dependencies to use. For example, `http://localhost:5173/apps/table-app/hydrate1k.html?impl=preact-hooks&dep=preact@prev-minor` loads the `hydrate1k.html` benchmark of the `table-app` using the `preact-hooks` implementation and the `prev-minor` version of Preact.
62+
63+
The Vite server reads the query parameters, generates an import map to map the `impl` and `dep` query parameters to the correct directories, and inserts it into the benchmark HTML file. This import map maps certain key imports to implementations and versions of dependencies. The benchmark file imports from `@impl` to get the implementation of the app and implementations should directly import their dependencies (e.g. import Preact from `preact` and `@preact/signals` from `@preact/signals`). The Vite server will handle mapping these to special paths that will cause them to be loaded using the HTML import map and not Vite's default node_modules resolution.
64+
65+
Here is an example request flow:
66+
67+
```mermaid
68+
sequenceDiagram
69+
participant Browser
70+
participant Vite
71+
Browser->>Vite: GET /apps/table-app/hydrate1k.html<br>?impl=preact-hooks<br>&dep=preact@prev-minor
72+
Vite->>Vite: Add import map to HTML<br> and rewrite @impl to /@impl
73+
Vite->>Browser: /apps/table-app/hydrate1k.html
74+
Browser->>Browser: Map /@impl to /apps/table-app/preact-hooks/index.js
75+
Browser->>Vite: GET /apps/table-app/preact-hooks/index.js
76+
Vite->>Vite: Rewrite preact import to /@dep/preact
77+
Vite->>Browser: /apps/table-app/preact-hooks/index.js
78+
Browser->>Browser: Map /@dep/preact to /dependencies/preact/prev-minor/index.js
79+
Browser->>Vite: GET /dependencies/preact/prev-minor/index.js
80+
Vite->>Browser: /dependencies/preact/prev-minor/index.js
13681
```

README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1-
# benchmarks
1+
# Preact Benchmarks
22

3-
A collection of benchmarks for Preact
3+
A collection of benchmarks for Preact and its ecosystem of libraries.
4+
5+
The `apps` directory contains a bunch of apps that can be benchmarked. Each directory under an `apps` contains HTML files that are the benchmarks for that app. Inside an `apps` folders are implementations of that app using different Preact libraries (e.g. class components, hooks, compat, signals). The `dependencies` directory contains different versions of Preact and its ecosystem libraries. The `cli` directory contains a command line interface to run benchmarks. You can specify using options to the cli for this repository which implementation and dependency version to use for a benchmark.
6+
7+
## Getting started
8+
9+
1. Clone this repository
10+
2. Run `pnpm install`
11+
3. Run `pnpm start` to start the benchmark server
12+
13+
Use the benchmark server to development/modify a benchmark implementation. The server will automatically reload the benchmark when you make changes files.
14+
15+
## Running benchmarks
16+
17+
1. Run `pnpm bench` to run a benchmark
18+
2. Follow the prompts to select which implementation and dependency versions to compare
19+
20+
Run `pnpm bench -- --help` to see all the options available to you.

package.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@
4040
]
4141
},
4242
"devDependencies": {
43-
"@preact/benchmark-apps": "workspace:^0.0.1",
44-
"@preact/benchmark-cli": "workspace:^0.0.1",
45-
"@preact/benchmark-deps": "workspace:^0.0.1",
4643
"concurrently": "^8.2.2",
4744
"husky": "^8.0.3",
4845
"lint-staged": "^15.2.0",

pnpm-lock.yaml

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

0 commit comments

Comments
 (0)