Skip to content

Commit c5d82f5

Browse files
committed
Merge branch 'dsvorc41-master'
2 parents 3470c4c + c6bf9c7 commit c5d82f5

File tree

15 files changed

+2565
-23
lines changed

15 files changed

+2565
-23
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ jspm_packages
2929
bower_components
3030
dist
3131

32+
# pre-built binaries for faster startup (downloaded and unzipped)
33+
pre-built-binaries
34+
35+
# test results - no need to include them in our commits
36+
webdriver-ts-results
37+
3238
.idea
3339
.vscode
3440
.DS_Store

README.md

Lines changed: 153 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@ The current snapshot that may not have the same quality (i.e.
4444
results might be for mixed browser versions, number of runs per benchmark may vary) can be seen [here](https://krausest.github.io/js-framework-benchmark/current.html)
4545
[![Results](images/results.png?raw=true "Results")](https://krausest.github.io/js-framework-benchmark/current.html)
4646

47+
## Keyed vs non-keyed frameworks
48+
49+
Some frameworks like react, vue.js or angular allow to create a 1:1-relationship between a data item and a DOM node by assigning a “key” attribute (or for angular specifying “trackBy” in *ngFor). If you use some identifier of the data as the key you get the “keyed” mode. Any update to the data will update the associated DOM node. If you reorder the list, the DOM nodes will be reordered accordingly.
50+
51+
The other mode is “non-keyed” and this is what e.g. vue.js uses by default for lists. In this mode a change to the data items can modify DOM nodes that were associated with other data before. This can be more performant, since costly DOM operations can be avoided (e.g. first removing old nodes, and the adding new nodes) and the existing DOM nodes are updated to display the new data. For react and angular using the item index as the key uses “non-keyed” mode for those frameworks.
52+
53+
Depending on your requirements the “non-keyed” mode can be a performance gain or can cause severe problems so one must choose carefully the mode and check that the framework supports that mode.
54+
55+
Read more here: [https://www.stefankrause.net/wp/?p=342](https://www.stefankrause.net/wp/?p=342)
56+
4757
# 1 NEW: Run pre-built binaries for all frameworks
4858

4959
There are currently ~60 framework entries in this repository. Installing (and maintaining) those can be challenging, but here are simplified instructions how to get started.
@@ -298,7 +308,147 @@ After that you can check all results in [http://localhost:8080/webdriver-ts/tabl
298308

299309
## 4. Contributing a new implementation
300310

301-
## 4.1 Building the app
311+
## 4.1 Example instructions for a real implementation
312+
Thanks @dsvorc41 for providing the following description:
313+
TL;DR:
314+
![demo](https://github.com/dsvorc41/js-framework-benchmark/assets/20287188/91ae2d64-7362-4be8-b88f-e52637b33fa5)
315+
316+
1. Install all of the root-level dependencies
317+
1. `cd js-framework-benchmark/`
318+
1. `npm ci` or `npm i`
319+
2. Make a new directory for your desired framework, for example Fast framework: `mkdir /frameworks/keyed/fast`
320+
3. Set up your new directory in whatever way is appropriate for that framework, for example:
321+
1. Set up prettier, eslint, dependencies (i.e. `@microsoft/fast-element`) etc
322+
2. Create `index.html` in the root of your folder where your app will be served `touch /frameworks/keyed/fast/index.html`
323+
3. Note: your html file must use the global CSS styles `<link href="/css/currentStyle.css" rel="stylesheet" />`
324+
4. Serve the page - Test that your html page is loaded properly in the browser
325+
1. For example put `<h1>Hello World - Fast Framework</h1>` somewhere
326+
2. Run the server from the root directory: `npm start`
327+
3. Visit your page in the browser (URL follows the folder structure): `http://localhost:8080/frameworks/keyed/fast/index.html`
328+
4. Note: Its important to always start the server from the root, because that way you'll get access to global CSS that all apps must share
329+
5. Note 2: **AVOID SHADOW DOM** - if your framework relies on Shadow Dom (like Fast framework does), you should turn it off. Otherwise you won't get access to global CSS.
330+
5. Add the "action triggers" - buttons that all apps must have (see `frameworks/keyed/vanillajs/index.html`)
331+
1. Note: Action triggers are simply buttons that are used to run the benchmarks (adding rows, deleting rows, swapping them, etc). Those buttons can be static HTML, or you can render them dynamically (with JS) with your framework of choice
332+
2. Make sure your HTML elements have the same classes and structure as VanillaJS, otherwise benchmarks won't be able to find your elements on the page, and you will not get the global CSS (Bootstrap)
333+
3. Add the html example below and open the page. You should see nicely formatted elements on the page, like in the GIF image above.
334+
4. Example for action triggers
335+
```html
336+
<body>
337+
<div id="main">
338+
<div class="container">
339+
<div class="jumbotron">
340+
<div class="row">
341+
<div class="col-md-6">
342+
<h1>VanillaJS-"keyed"</h1>
343+
</div>
344+
<div class="col-md-6">
345+
<div class="row">
346+
<div class="col-sm-6 smallpad">
347+
<button
348+
type="button"
349+
class="btn btn-primary btn-block"
350+
id="run"
351+
>
352+
Create 1,000 rows
353+
</button>
354+
</div>
355+
<div class="col-sm-6 smallpad">
356+
<button
357+
type="button"
358+
class="btn btn-primary btn-block"
359+
id="runlots"
360+
>
361+
Create 10,000 rows
362+
</button>
363+
</div>
364+
<div class="col-sm-6 smallpad">
365+
<button
366+
type="button"
367+
class="btn btn-primary btn-block"
368+
id="add"
369+
>
370+
Append 1,000 rows
371+
</button>
372+
</div>
373+
<div class="col-sm-6 smallpad">
374+
<button
375+
type="button"
376+
class="btn btn-primary btn-block"
377+
id="update"
378+
>
379+
Update every 10th row
380+
</button>
381+
</div>
382+
<div class="col-sm-6 smallpad">
383+
<button
384+
type="button"
385+
class="btn btn-primary btn-block"
386+
id="clear"
387+
>
388+
Clear
389+
</button>
390+
</div>
391+
<div class="col-sm-6 smallpad">
392+
<button
393+
type="button"
394+
class="btn btn-primary btn-block"
395+
id="swaprows"
396+
>
397+
Swap Rows
398+
</button>
399+
</div>
400+
</div>
401+
</div>
402+
</div>
403+
</div>
404+
<table class="table table-hover table-striped test-data">
405+
<!-- your dynamic content should render here -->
406+
</table>
407+
</div>
408+
</div>
409+
</body>
410+
```
411+
6. Generate dummy data for rendering
412+
1. See `frameworks/keyed/fast/src/utils/build-dummy-data.ts` as an example
413+
2. Note: `id` is an important attribute and it must be initialized as `1`, and continuously incremented. The only time `id` resets back to `1` is when the page reloads - otherwise it should just keep incrementing each time a new row is created. Doing anything else will cause errors when benchmarks try to find elements with specific IDs. Trust me, I learned the hard way.
414+
7. . Your app needs to support several actions that correspond to "Action triggers" listed above. Here's an example from Fast framework `frameworks\keyed\fast\src\App.ts` and `frameworks\keyed\fast\src\components\Table.ts`:
415+
1. Code example:
416+
```typescript
417+
export class BenchmarkApp extends FASTElement {
418+
createOneThousandRows() {}
419+
createTenThousandRows() {}
420+
appendOneThousandRows() {}
421+
updateEveryTenthRowLabel() {}
422+
deleteAllRows() {}
423+
swapTwoRows() {}
424+
deleteSingleRow(rowId: number) {}
425+
}
426+
427+
export class Table extends FASTElement {
428+
selectRow(rowId: number) {}
429+
}
430+
```
431+
2. Note: your app doesn't need methods with the same name - you should write idiomatic code and follow the best practices of your framework of choice. The example above is just to give you an idea of which operations must be supported, but how you choose to implement those methods can be very different from one framework to the next.
432+
8. Manually testing your app - do this before you run the benchmarks
433+
1. Open your page and click on the buttons, make sure your app adds 1000 rows, then removes them, or swaps them, or adds/removes 10,000 rows.
434+
2. To do this, you'll probably need to watch your local files and compile them into some sort of a bundle, like `frameworks\keyed\fast\dist\bundle.js` which will be loaded through a script tag in your HTML file
435+
3. For example, in Fast folder we have webpack watching our files: ` "build-dev": "rimraf dist && webpack --config webpack.config.js --watch --mode=development",`
436+
4. That means we have two terminal tabs running
437+
1. One for the server from the root folder `npm start`
438+
2. And another in our local folder where webpack is watching the files
439+
9. Run the single benchmark for your framework
440+
1. Once you manually verified that everything works as expected, run a single benchmark and make sure all of the tests are running
441+
2. If you forgot something, one of the benchmarks will probably fail - for example it won't be able to find an element on the page or similar
442+
3. Keep the server in the root folder running `npm start`, and in another terminal tab, also from the root folder run `npm run bench -- --framework keyed/fast` (or whatever is your framework `keyed/react`, `keyed/angular`, etc.).
443+
4. The benchmark runner will open and close Chrome multiple times. The whole thing will take a couple of minutes.
444+
10. Optional: run the benchmark for VanillaJS as comparison
445+
1. ` npm run bench -- --framework keyed/vanillajs`
446+
11. Build the report
447+
1. `npm run results`
448+
12. Open the report in your browser (NOTE: the server must still be running if you want to see this page)
449+
1. `http://localhost:8080/webdriver-ts-results/table.html`
450+
451+
## 4.2 Building the app
302452

303453
For contributions it is basically sufficient to create a new directory for your framework that supports `npm install` and `npm run build-prod` and can be then opened in the browser. All other steps are optional. Let's simulate that by copying vanillajs.
304454

@@ -320,7 +470,7 @@ In most cases you'll need `npm install` and `npm run build-prod` and then check
320470
321471
(Of course in reality you'd rather throw out the javascript source files and use your framework there instead of only changing the html file.)
322472
323-
## 4.2 Adding your new implementation to the results table.
473+
## 4.3 Adding your new implementation to the results table.
324474
325475
(Notice: Updating common.ts is no longer necessary, super-vanillajs is visible in the result table)
326476
@@ -358,7 +508,7 @@ The other important, but optional properties for js-framework-benchmark are show
358508
359509
You can set an optional different URL if needed or specify that your DOM uses a shadow root.
360510
361-
## 4.3 Submitting your implementation
511+
## 4.4 Submitting your implementation
362512
363513
Contributions are very welcome. Please use the following rules:
364514

frameworks/keyed/vanillajs/src/Main.js

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,6 @@ class Store {
4949
select(id) {
5050
this.selected = id;
5151
}
52-
hideAll() {
53-
this.backup = this.data;
54-
this.data = [];
55-
this.selected = null;
56-
}
57-
showAll() {
58-
this.data = this.backup;
59-
this.backup = null;
60-
this.selected = null;
61-
}
6252
runLots() {
6353
this.data = this.buildData(10000);
6454
this.selected = null;
@@ -115,16 +105,6 @@ class Main {
115105
//console.log("update");
116106
this.update();
117107
}
118-
else if (e.target.matches('#hideall')) {
119-
e.preventDefault();
120-
//console.log("hideAll");
121-
this.hideAll();
122-
}
123-
else if (e.target.matches('#showall')) {
124-
e.preventDefault();
125-
//console.log("showAll");
126-
this.showAll();
127-
}
128108
else if (e.target.matches('#runlots')) {
129109
e.preventDefault();
130110
//console.log("runLots");
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module.exports = {
2+
root: true,
3+
parser: '@typescript-eslint/parser',
4+
parserOptions: {
5+
ecmaVersion: 2019,
6+
sourceType: 'module',
7+
ecmaFeatures: {
8+
legacyDecorators: true
9+
}
10+
}
11+
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"arrowParens": "avoid",
3+
"bracketSpacing": true,
4+
"htmlWhitespaceSensitivity": "css",
5+
"insertPragma": false,
6+
"jsxBracketSameLine": false,
7+
"jsxSingleQuote": false,
8+
"printWidth": 120,
9+
"requirePragma": false,
10+
"semi": true,
11+
"singleQuote": true,
12+
"tabWidth": 2,
13+
"trailingComma": "none",
14+
"useTabs": false
15+
}

frameworks/non-keyed/fast/index.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>Fast-"keyed"</title>
6+
<link href="/css/currentStyle.css" rel="stylesheet" />
7+
</head>
8+
<body>
9+
<div id="main">
10+
<div class="container">
11+
<benchmark-app></benchmark-app>
12+
<span class="preloadicon glyphicon glyphicon-remove" aria-hidden="true"></span>
13+
</div>
14+
</div>
15+
<script src="dist/bundle.js"></script>
16+
</body>
17+
</html>

0 commit comments

Comments
 (0)