marko-js / marko

A declarative, HTML-based language that makes building web apps fun
https://markojs.com/
MIT License
13.25k stars 643 forks source link

How does Marko work? Why is it able to generate higher op/s than other frameworks? #691

Closed ghost closed 7 years ago

ghost commented 7 years ago

I am using vue.js at the moment and am interested in Marko. They are very similar and make it easy to transition one to the other. The one thing noticed is Marko is missing "scoped" but that's for another discussion.

ghost commented 7 years ago

No one?

austinkelleher commented 7 years ago

Hey, @juliojerrick. Check out Marko vs React for an in depth comparison of Marko and React. The article has a differences in rendering section, which lists some details on how Marko's rendering is more performant.

We also have plans to add scoped CSS like Vue. You can track this issue #666.

Hope this helps.

patrick-steele-idem commented 7 years ago

We've talked about the performance in a few places, but here's a more consolidated view of why Marko is fast:

Multiple compilation outputs

Marko compiles components differently for the server and the browser.

Given the following template:

<div>Hello ${input.name}!</div>

Compiled for the server

The compiled output is optimized for streaming HTML output on the server:

var marko_template = require("marko/html").t(__filename),
    marko_helpers = require("marko/runtime/html/helpers"),
    marko_escapeXml = marko_helpers.x;

function render(input, out) {
  out.w("<div>Hello " +
    marko_escapeXml(input.name) +
    "!</div>");
}

Compiled for the browser

The compiled output is optimized for virtual DOM rendering in the browser:

var marko_template = require("marko/vdom").t(__filename);

function render(input, out) {
  out.e("DIV", null, 3)
    .t("Hello ")
    .t(input.name)
    .t("!");
}

High performance server-side rendering

Compared to solutions like React that exclusively do virtual DOM rendering (or if you are using JSX with Vue), Marko has a huge advantage for server-side rendering. When rendering to a virtual DOM tree on the server it's a two-step process to render HTML:

In contrast, Marko renders directly to an HTML stream in a single pass. There is no intermediate tree data structure.

Compile-time optimization of static sub-trees

Given the following template:

<div>This is a <strong>static</strong> node</div>

Marko will recognize that the template fragment produces the same output every time and it creates the virtual DOM node once as shown in the following compiled output:

var marko_node0 = marko_createElement("DIV", null, 3, ...)
  .t("This is a ")
  .e("STRONG", null, 1)
    .t("static")
  .t(" node");

function render(input, out) {
  out.n(marko_node0);
}

Rendering a static sub-tree has virtually zero cost.

In addition, Marko will skip diffing/patching static sub-trees

Compile-time optimization of static attributes

Marko will also optimize static attributes on dynamic elements.

Given the following template:

<div.hello>Hello ${input.name}!</div>

Marko will produce the following compiled output:

var marko_attrs0 = {
        "class": "hello"
      };

function render(input, out) {
  out.e("DIV", marko_attrs0, 3)
    .t("Hello ")
    .t(input.name)
    .t("!");
}

Notice that the attributes object is only created once and it is used for every render. In addition, no diffing/patching will happen for static attributes.

Smart compiler

With Marko we favor doing as much at compile-time as possible. This has made our compiler more complex, but it gives us significant gains at runtime. We have ~90% code coverage and over 2,000 tests to ensure that the compiler is working correctly. In addition, in many cases the Marko compiler provides hints to the runtime for a given template so that the runtime can optimize for specific patterns. For example, Marko recognizes if an HTML is a simple HTML element with only class|id|style and the runtime optimizes for these virtual DOM nodes when doing diffing/patching (the Marko compiler generates codes that flags specific virtual DOM nodes).

The React (including React variants) runtime is typically used with JavaScript code generated by a JSX transform. JSX is just a simple transform for JSX elements to function calls. While there are a few babel transforms that apply a few compile-time optimizations, the bulk of the optimizations are happening in the runtime since there is very little happening at compile-time.

On a related note, we don't promote the usage of the Marko compiler in the browser (even though you can make it work). The Marko compiler is targeted to work almost exclusively on the server and it is pretty hefty. In contrast, Vue supports the widely adopted use case of compiling templates in the browser (in addition to compiling on the server). That means that Vue needs to be more mindful of adding new features to the compiler that might increase page weight.

Optimized data structures

Instead of rendering to a tree of virtual DOM nodes in the browser where children are represented by arrays, Marko renders to a tree where children are represented by a linked list. This reduces the memory allocation required and it is much faster in the use case of rendering virtual DOM nodes that get immediately thrown away. The virtual DOM data structures are heavily optimized as well for JavaScript runtimes based on known optimizations that V8 and others apply for given code structures and usage patterns. Here's the VDOM runtime code.

Optimized runtime

Marko has a heavily optimized runtime that has been benchmarked and fine-tuned over a long period of time. We extensively use profilers for Node.js and the browser and analyze generated opcodes to ensure that we maximize performance. We also aim to keep the code minimal to avoid introducing overhead. Lastly, we have created a variety of benchmarks that we have used to make sure we are staying fast:

Focused

Vue supports multiple ways to define a view:

Marko, on the other hand only supports and optimizes for one way of defining a view in a .marko file. Maybe Vue is doing a good job of equally optimizing all of those approaches to defining a view, but I suspect that is not the case.


I'm sure I could come up with some more reasons that Marko is fast, but I think that covers the bulk of the optimizations that Marko applies. Hope that answers your question. I'm going to close the issue, but feel free to add more comments here if you would like further clarification.

philidem commented 7 years ago

@patrick-steele-idem your response would make for a very interesting blog post.

jasonmacdonald commented 7 years ago

This should really be posted on the markojs site somewhere for others to easily find!

patrick-steele-idem commented 7 years ago

We just published an article to explain why Marko is fast! https://medium.com/@psteeleidem/why-is-marko-fast-a20796cb8ae3