UNPKG

8.62 kBMarkdownView Raw
1# Why is Marko Fast?
2
3<a href="https://medium.com/@psteeleidem/why-is-marko-fast-a20796cb8ae3">
4 <img src="https://user-images.githubusercontent.com/1958812/28104838-d0182f48-6691-11e7-808d-d1ae2d0fed6d.png" alt="Marko logo" width="100%" />
5</a><br />
6
7You can find the original ["Why is Marko Fast?" article here](https://medium.com/@psteeleidem/why-is-marko-fast-a20796cb8ae3)!
8
9At eBay we are using [Marko](http://markojs.com/) to render over a billion
10requests every day and this has required us to finely tune Marko, our open
11source UI library. We have heavily optimized Marko for fast rendering, [advanced
12performance
13techniques](http://www.ebaytechblog.com/2014/12/08/async-fragments-rediscovering-progressive-html-rendering-with-marko/)
14and to achieve a minimal page weight (~10kb gzipped). Performance is only one
15concern because we have also had to scale Marko to support development across
16hundreds of teams in a way that allows developers to efficiently create
17maintainable and robust web apps.
18
19We have created [our own
20benchmarks](https://github.com/marko-js/isomorphic-ui-benchmarks) and we have
21[added Marko to other
22benchmarks](https://github.com/raxjs/server-side-rendering-comparison/pull/11),
23but benchmarks cannot always be trusted. While we make every effort to be fair
24with our benchmarks, what matters most is performance in real world applications
25as opposed to focusing on micro benchmarks. This is one reason that the V8 team
26has switched to [a new methodology to measure and understand real-world
27JavaScript
28performance](https://v8project.blogspot.com/2016/12/how-v8-measures-real-world-performance.html).
29
30Similarly, we’ve taken a look at how our developers are _actually_ writing their
31Marko components and have found patterns that could be further optimized.
32Instead of focusing on benchmarks in this article, I want to focus on the
33details of optimizations that we have applied to Marko.
34
35### Multiple Compilation Outputs
36
37Marko is an isomorphic UI library that runs on both the server and in the
38browser. As [Michael Rawlings](https://medium.com/@mlrawlings) mentioned in
39“[Server-side Rendering
40Shootout](https://hackernoon.com/server-side-rendering-shootout-with-marko-preact-rax-react-and-vue-25e1ae17800f)”,
41when rendering on the server, Marko renders directly to a string representation
42of the document (HTML) that can be sent as the HTTP response.
43
44When rendering in the browser, an HTML string would have to be parsed in order
45to update the DOM. For this reason, Marko compiles a view to a program that
46renders directly to a virtual document (VDOM) tree that can be used to
47efficiently update the real DOM when targeting the browser.
48
49Given the following template:
50
51```marko
52<div>Hello ${input.name}!</div>
53```
54
55#### Compiled for the server
56
57The compiled output is optimized for streaming HTML output on the server:
58
59```js
60var marko_template = require("marko/html").t(__filename),
61 marko_helpers = require("marko/runtime/html/helpers"),
62 marko_escapeXml = marko_helpers.x;
63
64function render(input, out) {
65 out.w("<div>Hello " + marko_escapeXml(input.name) + "!</div>");
66}
67```
68
69#### Compiled for the browser
70
71```js
72var marko_template = require("marko/vdom").t(__filename);
73
74function render(input, out) {
75 out
76 .e("DIV", null, 3)
77 .t("Hello ")
78 .t(input.name)
79 .t("!");
80}
81```
82
83The compiled output is optimized for virtual DOM rendering in the browser:
84
85### Modular Runtime
86
87The Marko runtime is not distributed as a single JavaScript file. Instead, the
88Marko compiler generates a JavaScript module that will only import the parts of
89the runtime that are actually needed. This allows us to add new features to
90Marko without bloating existing applications. For example, given the following
91template:
92
93```marko
94$ var color = 'red';
95<div style={backgroundColor: color}></div>
96```
97
98In the above example, extra runtime code is needed to render the `style` attribute
99based on the JavaScript object that is provided. The compiled code that imports
100the `styleAttr` helper is shown below:
101
102```js
103var marko_styleAttr = require("marko/runtime/vdom/helper-styleAttr");
104
105function render(input, out) {
106 var color = "red";
107 out.e(
108 "DIV",
109 {
110 style: marko_styleAttr({
111 backgroundColor: color
112 })
113 },
114 0,
115 4
116 );
117}
118```
119
120### High performance server-side rendering
121
122Compared to solutions based on JSX that exclusively do virtual DOM rendering,
123Marko has a huge advantage for server-side rendering. When rendering to a
124virtual DOM tree on the server it’s a two-step process to render HTML:
125
126- First pass to produce an entire virtual DOM tree in memory
127- Second pass to serialize the virtual DOM tree to an HTML string that can then be
128 sent over the wire (this requires traversing the entire tree structure)
129
130In contrast, Marko renders directly to an HTML stream in a single pass. There is
131no intermediate tree data structure.
132
133### Compile-time optimization of static sub-trees
134
135Given the following template:
136
137```marko
138<div>This is a <strong>static</strong> node</div>
139```
140
141Marko will recognize that the template fragment produces the same output every
142time and it will thus create the virtual DOM node once as shown in the following
143compiled output:
144
145```js
146var marko_node0 = marko_createElement("DIV", null, 3, ...)
147 .t("This is a ")
148 .e("STRONG", null, 1)
149 .t("static")
150 .t(" node");
151
152function render(input, out) {
153 out.n(marko_node0);
154}
155```
156
157Rendering a static sub-tree has virtually zero cost. In addition, Marko will
158skip diffing/patching static sub-trees.
159
160Similarly, on the server, Marko will merge static parts of the template into a
161single string:
162
163```js
164function render(input, out) {
165 out.w("<div>This is a <strong>static</strong> node</div>");
166}
167```
168
169### Compile-time optimization of static attributes
170
171Marko will also optimize static attributes on dynamic elements.
172
173Given the following template:
174
175```marko
176<div.hello>Hello ${input.name}!</div>
177```
178
179Marko will produce the following compiled output:
180
181```js
182var marko_attrs0 = {
183 class: "hello"
184};
185
186function render(input, out) {
187 out
188 .e("DIV", marko_attrs0, 3)
189 .t("Hello ")
190 .t(input.name)
191 .t("!");
192}
193```
194
195Notice that the attributes object is only created once and it is used for every
196render. In addition, no diffing/patching will happen for static attributes.
197
198### Smart compiler
199
200With Marko we favor doing as much at compile-time as possible. This has made our
201compiler more complex, but it gives us significant gains at runtime. We have
202~90% code coverage and over 2,000 tests to ensure that the compiler is working
203correctly. In addition, in many cases the Marko compiler provides hints to the
204runtime for a given template so that the runtime can optimize for specific
205patterns. For example, Marko recognizes if an HTML element only has `class`/`id`/`style` defined
206and the runtime optimizes for these virtual DOM nodes when doing
207diffing/patching (the Marko compiler generates code that flags simple virtual
208DOM nodes for targeted diffing/patching logic).
209
210### Event delegation
211
212If you are building a UI component you will likely need to write code to handle
213various DOM events (`click`, `submit`, etc.). It is common for developers to write code that
214adds DOM event listeners using `dom.addEventListener(...)` or using a library such as jQuery. You can still
215do that when building UI components using Marko, but there is overhead in
216attaching listeners when lots of components are being initialized. Instead,
217Marko recommends using declarative event binding as shown below:
218
219```marko
220<button type="button" on-click("handleClick")>
221 Click Me
222</button>
223```
224
225When using declarative event binding, no DOM event listeners are actually
226attached for events that bubble. Instead, Marko attaches a single listener on
227the root DOM element of the page for each DOM event that bubbles (done at
228startup). When Marko receives an event at the root it handles delegating the
229event to the appropriate components that are interested in that event. This is
230done by looking at the `event.target` property to see where the event originated and then
231walking up the tree to find components that need to be notified. As a result,
232there is slightly more work that is done when a DOM event is captured at the
233root, but this approach uses much less memory and reduces the amount of work
234that is done during initialization. The extra overhead of delegating events to
235components will not be noticeable so it is a very beneficial optimization.
236
237_Cover image credit: [Superhero by Gan Khoon Lay from the Noun Project](https://thenounproject.com/search/?q=superhero&i=690775)_