Skip to content

Commit 4de8f06

Browse files
committed
Add performance benchmarks
1 parent e6de2ee commit 4de8f06

File tree

6 files changed

+67
-5
lines changed

6 files changed

+67
-5
lines changed

README.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@
1414
</div>
1515

1616
## Project Highlights
17-
* Performance. A script bundle with size 3.5 MB in development cut to ~70 KB in production for fast loading.
17+
* Performance. Achieved by webpack tree shaking, script bundle minification and compression (gzip and Brotli). Complimented by server-side caching described below.
18+
19+
| Benchmarking Tool | Result | Throttling |
20+
| :--- |:---:| :---:|
21+
| Google LightHouse | ![Run on Google Cloud](docs/benchmarks/100.png) | not throttled |
22+
| Google LightHouse | ![Run on Google Cloud](docs/benchmarks/84.png) | throttled to slow 4G,<br/>CPU slowdown |
23+
24+
The tool is embedded into Chrome so you can easily benchmark yourself. Follow this [link](docs/benchmarks/PERFORMANCE.md) for the details.
1825

1926
* Caching. The backend implements HTTP caching and allows long term storage of script bundles in browser's cache that further enhances performance yet supports smooth deployment of versioning changes in production (eliminating the risk of stale bundles getting stuck in the cache).
2027

@@ -135,7 +142,7 @@ The backend subproject:
135142
* In the development mode starts Express listening on the same port and working as a proxy for webpack-dev-server.
136143
* Implements HTTP caching arrangement which disables the caching for .html files and enables it for script bundles. A typical React application comes with .html files that are rather small whereas the bundles can be significantly larger. On the other hand, the build process keeps the names of .html files static and embeds a hash into the names of script bundles. As a result the caching arrangement ensures smooth deployment of versioning changes.
137144
### SPA Configuration
138-
The optional splitting of a React application into multiple SPAs (each rendered by its own bundle) improves the application loading time. It also brings development/testing benefits for medium to large applications. The `vendor` bundle contains `node_modules/` dependencies and is reused between SPAs so that there is no need to download it again when switching from one SPA to another.
145+
The optional splitting of a React application into multiple SPAs (each rendered by its own bundle) improves the application loading time. The `vendor` bundle contains `node_modules/` dependencies and is reused between SPAs so that there is no need to download it again when switching from one SPA to another.
139146

140147
Every SPA has a landing page displayed during initial rendering by the component included into the SPA. In webpack terminology such a component is called entry point. An SPA (and its bundle) is comprised of this component, the components it imports and their dependencies. Let's see how Crisp React defines the SPAs.
141148

@@ -376,9 +383,6 @@ In case you have a utility class used infrequently, it can also be imported dyna
376383
Q: Do dynamic imports negate the need to have multiple SPAs.<br/>
377384
A: It depends. These two are complimentary techniques. Obviously once a bundle grows larger, it starts affecting performance as its loading time increases. But the reverse is also true, having too many small bundles could result in more network round-trips and the bundle compression will become less efficient. It can also complicate attempts to scrutinise network traffic including requests for bundles.
378385

379-
Q: I use Apache/IIS/ASP.NET Core, not Express. Can I use the client project and what needs to be changed?<br/>
380-
A: Yes you can. The client project located in the `client` subdirectory is fully self-contained and can be used without any changes. The client related usage scenarios do not require any modifications.
381-
382386
Q: The client project does not have .html file(s). How can I add my own HTML?<br/>
383387
A: You can add .html snippet file to the project and change the `HtmlWebpackPlugin` configuration in `webpack.config.js` to include the content of your snippet into the generated .html files. That's how you would include polyfills etc. Look for the [headHtmlSnippet](https://github.com/jaketrent/html-webpack-template) configuration setting (and the bodyHtmlSnippet setting), it accepts a name of .html file.
384388

docs/benchmarks/100.png

2.42 KB
Loading

docs/benchmarks/84.png

2.14 KB
Loading

docs/benchmarks/PERFORMANCE.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
## Performance
2+
This section can be skipped at first reading. You might prefer go back to the [README](../../README.md).
3+
### Benchmarks
4+
To benchmark the solution perform the following steps:
5+
6+
1. If the [Getting Started](../../README.md#getting-started) section has been completed, skip this step. Otherwise clone the repository and build the solution:
7+
```
8+
git clone https://github.com/winwiz1/crisp-react.git
9+
cd crisp-react
10+
yarn install && yarn build:prod
11+
```
12+
2. Start the backend:
13+
```
14+
yarn run:prod
15+
```
16+
17+
3. Start Chrome and open new incognito window to disable extensions. Point it to `localhost:3000.` The Overview page should appear.
18+
19+
4. Press F12 to open Chrome DevTools, then activate the `Audits` tab. Choose the settings:
20+
```
21+
Device: Desktop
22+
Audits: Performance
23+
Throttling: No throttling
24+
Clear storage: ticked
25+
```
26+
5. Run the audit. Then change `Throttling` to `Simulated Slow 4G, 4x CPU Slowdown` and run the audit again. The results should be similar to shown in the table:
27+
28+
| Throttling | Benchmark Results |
29+
| :---:| :---:|
30+
| not throttled | ![Run on Google Cloud](unimpeded.png) |
31+
| throttled to slow 4G,<br/>CPU slowdown | ![Run on Google Cloud](throttled.png) |
32+
33+
As you can see, throttling results in 16% performance drop only. Switching from Desktop to Mobile produces slightly longer First Meaningful Paint and other metrics with the same performance scores.
34+
35+
6. Terminate the backend by pressing `Control+C`.
36+
37+
### Future Considerations
38+
The solution has a limited amount of code, as a boilerplate should. It begs a question to what extent the performance will be affected when the application grows. To contemplate an answer let's have a look at the script bundles:
39+
40+
| Bundle | Description | Brotli-compressed size in production build / development build size
41+
| :---:| :---| :---:|
42+
| `first.<hash>.js` | Renders the SPA called 'first' | 6 Kb / 64 Kb |
43+
| `second.<hash>.js` | Renders the SPA called 'second' | 8 Kb / 24 KB |
44+
| `vendor.<hash>.js` | Contains dependencies from `client/node_modules/`. Reused among SPAs. | 77 Kb / 3.6 MB|
45+
| `runtime.<hash>.js` | Webpack utility bundle. Contains internal webpack code. Reused among SPAs. | 2 Kb / 7 Kb |
46+
47+
>Sidenote. The bundles are located in the `client/dist` directory created during builds. We take Brotli compression (with `.br` file extension) into account because on the one hand it delivers approximately 17% better compression than gzip and on the other hand all modern browsers support it and have supported it for quite a while. Some bundles are too small to benefit from compression and are left uncompressed in which case we take the uncompressed size.
48+
49+
>To inspect bundle sizes in different builds, switch between development and production by executing `yarn build` or `yarn build:prod` from the `client/` subdirectory.
50+
51+
Let's assume that at the beginning of the development the second SPA and its bundle were removed (by commenting it out in the SPA Configuration) so the React app contains a single SPA only called 'first'. With the anticipated application growth the `first` bundle is expected to grow rapidly to reflect the expanding functionality and codebase. The `vendor` bundle is expected to grow at much slower pace because it already contains the bulk of dependencies (project dependencies including the React library, its direct dependencies, dependencies of the dependencies and so forth).
52+
53+
No substantial impact on the performance can be anticipated until the `first` bundle reaches the size and then outgrowths the `vendor` bundle. At this point it might be a good time to use code splitting and introduce another SPA. One of the goals of using multiple SPAs is to ensure each SPA is rendered by its own and smaller bundle thus reducing React application loading time. See [SPA Configuration](../../README.md#spa-configuration) section for more details.
54+
55+
The conclusion is that with multiple SPAs you can grow the functionality and codebase while maintaining the top performance.
56+
57+
---
58+
Back to the [README](../../README.md).

docs/benchmarks/throttled.png

11.4 KB
Loading

docs/benchmarks/unimpeded.png

11.7 KB
Loading

0 commit comments

Comments
 (0)