Skip to content

Commit 358d6b4

Browse files
authored
New Workload: React Server Side Rendering Startup (#116)
Add a new web-ssr workload that renders a wine-collection using different react components. Currently the setup uses webpack to build a web-bundle that we can run in the browser. - We generate two bundles: - minified sources with mangled names as default for the benchmark - minifed sources with unmangeled names for nicer local profiling (we do have source maps, but we we don't check in the node modules) - A custom TerserPlugin injects a comment into every function-body - A last step stringifies the bundled code for later evaling For cache-busting new sources are generated for each iteration and the custom comment is replaced with iteration-unique string.
1 parent 3f3513d commit 358d6b4

22 files changed

+8965
-0
lines changed

JetStreamDriver.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2019,6 +2019,19 @@ let BENCHMARKS = [
20192019
],
20202020
tags: ["Default", "Proxy"],
20212021
}),
2022+
new AsyncBenchmark({
2023+
name: "web-ssr",
2024+
files: [
2025+
"./web-ssr/benchmark.js",
2026+
],
2027+
preload: {
2028+
// Debug Sources for nicer profiling.
2029+
// BUNDLE_BLOB: "./web-ssr/dist/bundle.js",
2030+
BUNDLE_BLOB: "./web-ssr/dist/bundle.min.js",
2031+
},
2032+
tags: ["Default", "web", "ssr"],
2033+
iterations: 30,
2034+
}),
20222035
// Class fields
20232036
new DefaultBenchmark({
20242037
name: "raytrace-public-class-fields",

package-lock.json

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

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,8 @@
4444
"local-web-server": "^5.4.0",
4545
"prettier": "^2.8.3",
4646
"selenium-webdriver": "^4.35.0"
47+
},
48+
"dependencies": {
49+
"@dapplets/unicode-escape-webpack-plugin": "^0.1.1"
4750
}
4851
}

web-ssr/babel.config.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"presets": [
3+
"@babel/preset-env",
4+
"@babel/preset-react"
5+
]
6+
}

web-ssr/benchmark-node.mjs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// node standalone version of the benchmark for local testing.
2+
3+
import { renderTest } from "./src/react-render-test.cjs";
4+
5+
console.log("Starting TypeScript in-memory compilation benchmark...");
6+
const startTime = performance.now();
7+
8+
renderTest();
9+
10+
const endTime = performance.now();
11+
const duration = (endTime - startTime) / 1000;
12+
13+
console.log(`TypeScript compilation finished.`);
14+
console.log(`Compilation took ${duration.toFixed(2)} seconds.`);

web-ssr/benchmark.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
globalThis.console = {
2+
log() { },
3+
warn() { },
4+
assert(condition) {
5+
if (!condition) throw new Error("Invalid assertion");
6+
}
7+
};
8+
9+
globalThis.clearTimeout = function () { };
10+
11+
12+
function quickHash(str) {
13+
let hash = 5381;
14+
let i = str.length;
15+
while (i > 0) {
16+
hash = (hash * 33) ^ (str.charCodeAt(i) | 0);
17+
i-= 919;
18+
}
19+
return hash | 0;
20+
}
21+
22+
const CACHE_BUST_COMMENT = "/*ThouShaltNotCache*/";
23+
const CACHE_BUST_COMMENT_RE = new RegExp(`\n${RegExp.escape(CACHE_BUST_COMMENT)}\n`, "g");
24+
25+
// JetStream benchmark.
26+
class Benchmark {
27+
// How many times (separate iterations) should we reuse the source code.
28+
// Use 0 to skip.
29+
CODE_REUSE_COUNT = 1;
30+
iterationCount = 0;
31+
iteration = 0;
32+
lastResult = {};
33+
sourceCode;
34+
sourceHash = 0
35+
iterationSourceCodes = [];
36+
37+
constructor(iterationCount) {
38+
this.iterationCount = iterationCount
39+
}
40+
41+
async init() {
42+
this.sourceCode = await JetStream.getString(JetStream.preload.BUNDLE_BLOB);
43+
this.expect("Cache Comment Count", this.sourceCode.match(CACHE_BUST_COMMENT_RE).length, 597);
44+
for (let i = 0; i < this.iterationCount; i++)
45+
this.iterationSourceCodes[i] = this.prepareCode(i);
46+
}
47+
48+
prepareCode(iteration) {
49+
if (!this.CODE_REUSE_COUNT)
50+
return this.sourceCode;
51+
// Alter the code per iteration to prevent caching.
52+
const cacheId = Math.floor(iteration / this.CODE_REUSE_COUNT);
53+
const previousSourceCode = this.iterationSourceCodes[cacheId];
54+
if (previousSourceCode)
55+
return previousSourceCode
56+
const sourceCode = this.sourceCode.replaceAll(CACHE_BUST_COMMENT_RE, `/*${cacheId}*/`);
57+
// Ensure efficient string representation.
58+
this.sourceHash = quickHash(sourceCode);
59+
return sourceCode;
60+
}
61+
62+
runIteration() {
63+
let sourceCode = this.iterationSourceCodes[this.iteration];
64+
if (!sourceCode)
65+
throw new Error(`Could not find source for iteration ${this.iteration}`);
66+
// Module in sourceCode it assigned to the ReactRenderTest variable.
67+
let ReactRenderTest;
68+
69+
let initStart = performance.now();
70+
const res = eval(sourceCode);
71+
const runStart = performance.now();
72+
73+
this.lastResult = ReactRenderTest.renderTest();
74+
this.lastResult.htmlHash = quickHash(this.lastResult.html);
75+
const end = performance.now();
76+
77+
const loadTime = runStart - initStart;
78+
const runTime = end - runStart;
79+
// For local debugging:
80+
// print(`Iteration ${this.iteration}:`);
81+
// print(` Load time: ${loadTime.toFixed(2)}ms`);
82+
// print(` Render time: ${runTime.toFixed(2)}ms`);
83+
this.iteration++;
84+
}
85+
86+
validate() {
87+
this.expect("HTML length", this.lastResult.html.length, 183778);
88+
this.expect("HTML hash", this.lastResult.htmlHash, 1177839858);
89+
}
90+
91+
expect(name, value, expected) {
92+
if (value != expected)
93+
throw new Error(`Expected ${name} to be ${expected}, but got ${value}`);
94+
}
95+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Babel plugin that adds CACHE_BUST_COMMENT to every function body.
2+
const CACHE_BUST_COMMENT = "ThouShaltNotCache";
3+
4+
5+
module.exports = function({ types: t }) {
6+
return {
7+
visitor: {
8+
Function(path) {
9+
const bodyPath = path.get("body");
10+
// Handle arrow functions: () => "value"
11+
// Convert them to block statements: () => { return "value"; }
12+
if (!bodyPath.isBlockStatement()) {
13+
const newBody = t.blockStatement([t.returnStatement(bodyPath.node)]);
14+
path.set("body", newBody);
15+
}
16+
17+
// Handle empty function bodies: function foo() {}
18+
// Add an empty statement so we have a first node to attach the comment to.
19+
if (path.get("body.body").length === 0) {
20+
path.get("body").pushContainer("body", t.emptyStatement());
21+
}
22+
23+
const firstNode = path.node.body.body[0];
24+
t.addComment(firstNode, "leading", CACHE_BUST_COMMENT);
25+
26+
}
27+
},
28+
};
29+
};

web-ssr/dist/bundle.js

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

web-ssr/dist/bundle.js.LICENSE.txt

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* @license React
3+
* react-dom-server-legacy.browser.production.js
4+
*
5+
* Copyright (c) Meta Platforms, Inc. and affiliates.
6+
*
7+
* This source code is licensed under the MIT license found in the
8+
* LICENSE file in the root directory of this source tree.
9+
*/
10+
11+
/**
12+
* @license React
13+
* react-dom-server.browser.production.js
14+
*
15+
* Copyright (c) Meta Platforms, Inc. and affiliates.
16+
*
17+
* This source code is licensed under the MIT license found in the
18+
* LICENSE file in the root directory of this source tree.
19+
*/
20+
21+
/**
22+
* @license React
23+
* react-dom.production.js
24+
*
25+
* Copyright (c) Meta Platforms, Inc. and affiliates.
26+
*
27+
* This source code is licensed under the MIT license found in the
28+
* LICENSE file in the root directory of this source tree.
29+
*/
30+
31+
/**
32+
* @license React
33+
* react.production.js
34+
*
35+
* Copyright (c) Meta Platforms, Inc. and affiliates.
36+
*
37+
* This source code is licensed under the MIT license found in the
38+
* LICENSE file in the root directory of this source tree.
39+
*/

web-ssr/dist/bundle.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)