├── .gitignore
├── .jshintrc
├── README.md
├── benchmark-client
├── browser.json
├── client.js
├── components
│ ├── app
│ │ ├── component.js
│ │ └── index.marko
│ └── mount-container
│ │ ├── index.marko
│ │ └── style.css
├── createRoute.js
├── helpers.js
├── page.marko
└── runBenchmark.js
├── benchmark-server
├── collectStats.js
├── generateReports.js
├── reports-csv.js
├── reports-summary.js
└── run.js
├── benchmarks.js
├── benchmarks
└── search-results
│ ├── client.js
│ ├── createRoute.js
│ ├── marko
│ ├── client.js
│ ├── components
│ │ ├── app-footer
│ │ │ └── index.marko
│ │ ├── app-search-results-item
│ │ │ └── index.marko
│ │ └── app
│ │ │ └── index.marko
│ ├── page.marko
│ └── rollup.config.js
│ ├── page.marko
│ ├── preact
│ ├── client.jsx
│ ├── components
│ │ ├── App.jsx
│ │ ├── Footer.jsx
│ │ └── SearchResultsItem.jsx
│ ├── page.marko
│ ├── rollup.config.js
│ └── util
│ │ ├── htmlSafeJSONStringify.js
│ │ └── serverRender.jsx
│ ├── react
│ ├── client.jsx
│ ├── components
│ │ ├── App.jsx
│ │ ├── Footer.jsx
│ │ └── SearchResultsItem.jsx
│ ├── page.marko
│ ├── rollup.config.js
│ └── util
│ │ ├── htmlSafeJSONStringify.js
│ │ └── reactServerRender.jsx
│ └── util
│ ├── search-results-data.json
│ └── search.js
├── charts
├── cpu.png
├── memory.png
├── requestsPerSecond.png
└── responseTime.png
├── generated
└── search-results
│ ├── charts.xlsx
│ ├── marko
│ └── html-report
│ │ └── index.html
│ └── preact
│ └── html-report
│ └── index.html
├── images
├── test-image-01.jpg
├── test-image-02.jpg
├── test-image-03.jpg
├── test-image-04.jpg
├── test-image-05.jpg
└── test-image-06.jpg
├── index.marko
├── init.js
├── package.json
├── scripts
├── bundle.js
└── minify.js
├── server.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | *.marko.js
2 | /node_modules
3 | /build
4 | /.cache
5 | .*
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "Promise"
4 | ],
5 | "node" : true,
6 | "esnext" : true,
7 | "browser" : true,
8 | "boss" : false,
9 | "curly": false,
10 | "debug": false,
11 | "devel": false,
12 | "eqeqeq": true,
13 | "evil": true,
14 | "forin": false,
15 | "immed": true,
16 | "laxbreak": false,
17 | "newcap": false,
18 | "noarg": true,
19 | "noempty": false,
20 | "nonew": true,
21 | "nomen": false,
22 | "onevar": false,
23 | "plusplus": false,
24 | "regexp": false,
25 | "undef": true,
26 | "sub": false,
27 | "white": false,
28 | "eqeqeq": false,
29 | "latedef": true,
30 | "unused": "vars",
31 | "strict": false,
32 | "eqnull": true
33 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > __DEPRECATED - Please see the following benchmark that is actively maintained:__ https://github.com/marko-js/isomorphic-ui-benchmarks
2 |
3 | Marko vs React: Performance Benchmark
4 | ========================================
5 |
6 | _NOTE: This comparison was updated on January 5, 2016 to use `react@15.3.2` and `marko@4.0.0-beta.10`._
7 |
8 | The goal of this project is to provide a _real-world_ sample app that can be used to compare the performance of two different approaches of building UI component-centric, [_isomorphic_](http://nerds.airbnb.com/isomorphic-javascript-future-web-apps/) web applications:
9 |
10 | 1. [Marko](https://github.com/marko-js/marko): UI components building library. Marko uses [morphdom](https://github.com/patrick-steele-idem/morphdom) internally to diff/patch the DOM.
11 | 1. [React](http://facebook.github.io/react/): UI components built using JSX and rendered to the DOM using a virtual DOM with a diffing algorithm to minimize updates. UI components must be rendered on the client for behavior to be attached.
12 |
13 | We are interested in the following metrics for comparing the performance of Marko and React:
14 |
15 | - For rendering pages on the server under varying load:
16 | - HTTP response time
17 | - Requests per second
18 | - CPU usage
19 | - Memory usage
20 | - Performance when rendering UI components on the client
21 | - Time needed before the initial page is fully ready on the client
22 | - Page weight (amount of HTML and JS code)
23 |
24 | # Disclaimer
25 |
26 | While I, [Patrick Steele-Idem](https://github.com/patrick-steele-idem), am the author of [Marko](https://github.com/raptorjs/marko), every effort was made to be fair and unbiased when putting together this comparison. I've also asked other developers to review this benchmark. If you find a problem please open a Github issue to discuss.
27 |
28 | Both Marko and React have a lot of merit when it comes to building webapps based on UI components, and I was genuinely curious to see how they performed in practice. Both React and Marko a DOM diffing/patching algorithm to update the view. However, React makes use of a virtual DOM while Marko only utilizes the real DOM. Marko uses the [morphdom](https://github.com/patrick-steele-idem/morphdom) module to update the DOM with the minimum number of changes
29 |
30 | Also, this benchmark focuses on both server-side and client-side rendering performance. Many apps benefit from rendering the initial page on the server since it supports SEO and it often reduces the time to render the initial page. Both React and Marko support rendering on both the server and in the browser.
31 |
32 | # Test Setup Overview
33 |
34 | To compare performance, both React and Marko were used to build a page that displays search results consisting of _100_ search results items using eBay listings as sample data. Each search results item is rendered into a `
` with the following information:
35 |
36 | - Image
37 | - Title
38 | - Price
39 | - "Buy Now" button (for testing behavior)
40 |
41 | The initial page of search results is rendered on the server and the app allows additional pages of search results to be rendered completely on the client.
42 |
43 | To test server-side rendering performance, the [http-stats](https://www.npmjs.com/package/http-stats) tool is used to put the server under increasing load while collecting statistics. A freshly started server is used to test Marko and React independently.
44 |
45 | To test client-side rendering performance, a simple script is used to cycle through 100 pages of sample search results data (100 search items per page). After each page is rendered and after the resulted are flushed to the DOM, the next page of search results is rendered until the test completes. The total time used to complete the test is used to measure the performance of client-side rendering.
46 |
47 | # Running the Tests
48 |
49 | The following commands should be used to benchmark server-side rendering performance:
50 |
51 | ```
52 | git clone https://github.com/patrick-steele-idem/marko-vs-react.git
53 | cd marko-vs-react
54 | npm install
55 | npm test
56 | ```
57 |
58 | _NOTE: The server will automatically be started with `NODE_ENV=production`_
59 |
60 | The following steps should be used to benchmark client-side rendering performance:
61 |
62 | ```
63 | NODE_ENV=production node server.js
64 | ```
65 |
66 | And then launch a web browser and load the following pages:
67 |
68 | - [http://localhost:8080/react](http://localhost:8080/react)
69 | - [http://localhost:8080/marko](http://localhost:8080/marko)
70 |
71 | On each page, click on the button at the top labeled "Start Client Performance Tests". The results of the test will be shown at the top of the page.
72 |
73 | # Results
74 |
75 | The test setup is described in more detail later in this document, but below are the results of running the tests with the following configuration:
76 |
77 | - MacBook Pro (2.8 GHz Intel Core i7, 16 GB 1600 MHz DDR3)
78 | - Server-side:
79 | - Node.js: v7.3.0
80 | - Client-side:
81 | - (see versions below)
82 |
83 | ## Results Summary
84 |
85 | On the server, Marko was the winner by far. On the client, React and Marko performed nearly the same. However, on an iPhone 6 running iOS 8.4, Marko performed significantly better.
86 |
87 | On the server, Marko was able to render the page much more quickly and with much lower CPU and memory usage. These benchmarks indicate that React needs to go through a lot more optimization before it is ready to be used on the server.
88 |
89 | It is also worth pointing out that React also suffers from a severe disadvantage in that a page rendered on the server must be re-rendered on the client in order for behavior to be bound to UI components. When the re-rendering happens on the client, the exact same input data needs to be provided to the top-level UI component. For this benchmark, this means that the data used to render the initial pages of search results needed to be serialized to JSON and included in the output HTML rendered on the server. Even while temporarily disabling the serialization of JSON it was still observed that React was significantly slower doing server-side rendering.
90 |
91 | On the client, Marko and React were able to cycle through pages of search results in about the same time. The performance gap between Marko and React will likely vary by use case and will be sensitive to the DOM structure.
92 |
93 | Finally, a web page that uses React will have a significantly higher weight due to the size of the React JavaScript library, as well as the addition of React-specific `data-*` attributes (e.g., `data-reactid=".t9c80npc00.1.$1.1"`) that are added to the output HTML during rendering. The page weight also increased due to the embedding of the extra JSON required to re-render the initial page on the client (discussed earlier).
94 |
95 |
96 | ## Server-side Rendering Performance
97 |
98 | 
99 |
100 | 
101 |
102 | 
103 |
104 | 
105 |
106 | ## Client-side Rendering Performance
107 |
108 | Time taken to cycle through 100 pages of search results (100 search results items per page):
109 |
110 | ### Chrome
111 |
112 | ```
113 | marko x 259 ops/sec ±1.22% (59 runs sampled)
114 | react x 220 ops/sec ±1.09% (56 runs sampled)
115 | Fastest is marko
116 | ```
117 |
118 | _NOTE: Version 55.0.2883.95 (64-bit)_
119 |
120 | ### Firefox
121 |
122 | ```
123 | marko x 187 ops/sec ±1.80% (50 runs sampled)
124 | react x 126 ops/sec ±2.15% (52 runs sampled)
125 | Fastest is marko
126 | ```
127 |
128 | _NOTE: v50.1.0_
129 |
130 | ### Safari
131 |
132 | ```
133 | marko x 465 ops/sec ±1.50% (60 runs sampled)
134 | react x 271 ops/sec ±1.69% (53 runs sampled)
135 | Fastest is marko
136 | ```
137 |
138 | _NOTE: Version 10.0.1 (11602.2.14.0.7)_
139 |
140 | ### iOS Mobile Safari
141 |
142 | ```
143 | marko x 108 ops/sec ±2.60% (48 runs sampled)
144 | react x 68.33 ops/sec ±1.65% (47 runs sampled)
145 | Fastest is marko
146 | ```
147 |
148 | _NOTE: iPhone 6 with iOS 10.2_
149 |
150 | ## Page Weight
151 |
152 | ### JavaScript Page Weight
153 |
154 | ```
155 | [marko]
156 | gzip: 11,747 bytes
157 | min: 32,199 bytes
158 |
159 | [react]
160 | gzip: 43,005 bytes
161 | min: 134,459 bytes
162 | ```
163 |
164 | Source: [marko-js/marko/benchmark/size](https://github.com/marko-js/marko/tree/master/benchmark/size)
165 |
166 | ### HTML Page Weight
167 |
168 |
169 |
170 |
171 |
172 |
Uncompressed
173 |
Gzipped
174 |
175 |
176 |
177 |
178 |
179 |
Marko
180 |
47.2 KB
181 |
7.7 KB
182 |
✔
183 |
184 |
185 |
React
186 |
64.3 KB
187 |
12.1 KB
188 |
✖
189 |
190 |
191 |
192 |
193 | The output HTML can be compared using the links below:
194 |
195 | - [Marko output HTML](test/generated/html-marko.html)
196 | - [React output HTML](test/generated/html-react.html)
197 |
198 | # Test Setup Details
199 |
200 | The entry point for the Node.js app that starts the server is [server.js](server.js). The main script creates an Express app with the following routes:
201 |
202 | 1. `/marko` - Renders the search results page using Marko. Maps to the following controller: [src/marko/pages/search-results/index.js](src/marko/pages/search-results/index.js)
203 | 2. `/react` - Renders the search results page using React. Maps to the following controller: [src/react/pages/search-results/index.jsx](src/react/pages/search-results/index.jsx)
204 | 3. `/static` - Used to serve up static JS resources
205 | 4. `/` - Serves up the index page
206 |
207 | The code is divided into the following directories:
208 |
209 | - [/src/marko](/src/marko) - All UI components and page code that is specific to Marko
210 | - [/src/react](/src/react) - All UI components and page code that is specific to React
211 | - [/src/shared](/src/shared) - All code that is shared across Marko and React
212 |
213 | For both React and Marko, Marko templates is used to render the page skeleton and the [Lasso.js](https://github.com/lasso-js/lasso) tool is used to deliver all of the required JavaScript code to the browser.
214 |
215 | On both the server and the client, the `NODE_ENV` variable is set to `production` since React behaves differently in non-production mode.
216 |
217 | ## Marko
218 |
219 | For Marko, the [main page template](src/marko/pages/search-results/template.marko) includes a custom tag that delegates rendering of the body of the page to the [``](src/marko/components/app-search-results) UI component:
220 |
221 | ```html
222 |
223 | ```
224 |
225 | The template for the [``](src/marko/components/app-search-results) component is shown below:
226 |
227 | ```html
228 |
254 |
255 |
258 |
259 |
260 |
263 |
264 |
265 |
266 | ```
267 |
268 | The template for the [``](src/marko/components/app-search-results-item) component is shown below:
269 |
270 | ```html
271 |
278 |
279 | var itemData=data.itemData
280 |
281 |
282 |
${itemData.title}
283 |
284 | ${itemData.price}
285 |
286 |
287 | ```
288 |
289 | During rendering of the Marko templates, Marko keeps track of all the rendered widgets (including the HTML element that they are bound to). This information is passed to the client as part of the DOM so that the widgets can efficiently be initialized when the page loads. If a UI component is rendered on the client, the list of rendered widgets is kept in memory and after the resulting HTML is added to the DOM all of the rendered widgets are then initialized.
290 |
291 | ## React
292 |
293 | For React, code similar to the following is used to produce the body HTML for the page:
294 |
295 | ```javascript
296 | var React = require('react');
297 | var SearchResults = require('src/react/components/SearchResults');
298 | var searchResultsHTML = React.renderToString(
299 | );
300 | ```
301 |
302 | The `render` method for the [`SearchResults`](src/react/components/SearchResults.jsx) UI component contains the following code:
303 |
304 | ```javascript
305 | function render() {
306 | var searchResultsData = this.state.searchResultsData;
307 |
308 | return (
309 |
319 | );
320 | }
321 | ```
322 |
323 | For behavior to be attached on the client, the page must be re-rendered on the client using the same search results data. Therefore, the React testbed also includes additional code to serialize the search results data to JSON. The resulting JSON data is dropped into the page inside a `
6 |
7 |
8 |