packages/runtime-class/docs/marko-vs-react.md
This article was published March 2017. Both frameworks have gone through several updates since. You can find the original "Marko vs React: An In-depth Look" article here!
In this article we will take an in-depth look at the differences and similarities between Marko and React from the perspective of the maintainers of Marko.
On the surface, Marko and React have a lot in common and both are trying to solve very similar problems. Specifically, both Marko and React allow developers to build web applications based on UI components and both free developers from having to write code to manually update the DOM. While many of the features in Marko were inspired by React, Marko and React offer very different usability and performance characteristics. Marko was designed to avoid almost all boilerplate and is more closely aligned with HTML. In almost all cases, a Marko UI component will require less lines of code than its React JSX equivalent while maintaining readability and allowing the same expressiveness as JSX. In addition, Marko is highly optimized for use on the server and in the browser and has a much smaller weight:
Because the Marko JavaScript library is much smaller than React, it will require less time to load and parse and this will drastically improve page load times on slow connections or on older devices. Based on our benchmarks, Marko consistently outperforms React by a significant margin on both the server and in the browser.
The following code highlights some of the differences between Marko and React JSX using a somewhat contrived UI component as an example:
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment(delta) {
this.setState({ count: this.state.count + delta });
}
render() {
const count = this.state.count;
let countClassName = "count";
if (count > 0) {
countClassName += " positive";
} else if (count < 0) {
countClassName += " negative";
}
return (
<div className="click-count">
<div className={countClassName}>{count}</div>
<button
onClick={() => {
this.increment(-1);
}}
>
-1
</button>
<button
onClick={() => {
this.increment(1);
}}
>
+1
</button>
</div>
);
}
}
<span class="figcaption_hack">▶ Try Online</span>
class {
onCreate() {
this.state = { count: 0 };
}
increment(delta) {
this.state.count += delta;
}
}
$ var count = state.count;
<div.click-count>
<div.count class={
positive: count > 0,
negative: count < 0
}>
${count}
</div>
<button on-click('increment', -1)>
-1
</button>
<button on-click('increment', 1)>
+1
</button>
</div>
<span class="figcaption_hack">▶ Try Online</span>
Marko and React have the following in common:
domEl.addEventListener() needed)At a high level here are some differences:
<doctype> and <html>createElement() function calls while the Marko compiler has full control over how things are
compiled and optimized.PropTypes only provide
validation at render-time)class Counter extends React.Component in React).this.state.count++).this.emit('myCustomEvent', arg1, arg2)).Function references for custom
events while Marko automatically delegates emitted custom events to event
handler methods on components.this will be the component
instance.In the sections below we will take a closer look at some of the differences between Marko and React.
Both Marko and React JSX allow HTML markup and JavaScript to be combined into a single file and both support building web applications based on UI components. Marko utilizes an HTML-JS syntax while most React apps use the JSX syntax.
React JSX makes JavaScript more like HTML and Marko makes HTML more like JavaScript.
In the end, both Marko and React allow JavaScript and HTML to be intertwined.
In React JSX, all attribute values are parsed as string values unless {} is used.
<MyComponent
name="Frank"
messageCount={30}
visible={true}
person={{ firstName: 'John', lastName: 'Doe' }}
colors={['red', 'green', 'blue']} />
<div id="content" className="foo">Hello</div>
With Marko, all attribute values are parsed as JavaScript expressions. The following Marko code is equivalent to the React JSX code above:
<my-component
name="Frank"
message-count=30
visible=true
person={ firstName: 'John', lastName: 'Doe' }
colors=['red', 'green', 'blue'] />
<div id="content" class="foo">Hello</div>
React JSX starts with JavaScript and allows XML elements to be inlined as shown below:
import { formatDate } from "./util";
function formatName(person) {
return person.firstName + " " + person.lastName.charAt(0) + ".";
}
export default function HelloMessage(props) {
var person = props.person;
return (
<div>
Hello {formatName(person)}!
<span>You were born on {formatDate(person.birthday)}.</span>
</div>
);
}
Marko starts out in HTML, but it allows JavaScript to be inlined in a clean and maintainable way. Unlike other template languages, Marko aims to allow the full power of JavaScript. The following Marko code is equivalent to the React JSX code above:
import { formatDate } from './util';
static function formatName(person) {
return person.firstName + ' ' + person.lastName.charAt(0) + '.';
}
$ var person = input.person;
<div>
Hello ${formatName(person)}!
<span>
You were born on ${formatDate(person.birthday)}.
</span>
</div>
Lines prefixed with $ are directly added to the compiled JavaScript output inside
the compiled render() function (for JavaScript code that should run for every render).
Lines prefixed with static are directly added to the compiled JavaScript output
outside the render() function (for code that should only run once when the template is
loaded).
With Marko any valid HTML markup can be used inside a Marko template. This is not the case with React. The following quote is from the React documentation:
Caveat:
Since JSX is closer to JavaScript than HTML, React DOM uses
camelCaseproperty naming convention instead of HTML attribute names.
For example,
classbecomesclassNamein JSX, andtabindexbecomestabIndex.
As a result of this caveat for React, tools for converting HTML to JSX exist.
<div id="content" className="my-component">Hello</div>
<input type="text" name="firstName" value="John" />
<div id="content" class="my-component">Hello</div>
<input type="text" name="firstName" value="John">
JSX is syntactic sugar on top of JavaScript, but it requires expressions, so
simple things like an if/else/for statement don’t work on their own within a JSX element. As
a result, you must either use a ternary expression, an immediately invoked
function expression, function call expression, or the experimental do {} expression
(stage 0 at the time of writing). This is not an issue for Marko, and tags
such as if() and for can be used anywhere as shown below:
function counterMessage(count) {
return (
<div className="counter-message">
(function() {
if (count < 0) {
return <div>Count is negative</div>
} else if (count === 0) {
return <div>Count is zero</div>
} else {
return <div>Count is positive</div>
}
}())
</div>
)
}
<div.counter-message>
<if(count < 0)>
<div>Count is negative</div>
</if>
<else if(count === 0)>
<div>Count is zero</div>
</else>
<else>
<div>Count is positive</div>
</else>
</div>
Marko also allows directives to be used as attributes for a more condensed template:
<div.counter-message>
<div if(count < 0)>Count is negative</div>
<div if(count === 0)>Count is zero</div>
<div else>Count is positive</div>
</div>
function renderColors(colors) {
return (
<ul>
{colors.map((color) => (
<li
className="color"
style={{
backgroundColor: color,
}}
>
{color}
</li>
))}
</ul>
);
}
<ul>
<for|color| of=colors>
<li.color style={ backgroundColor: color }>
${color}
</li>
</for>
</ul>
<div id="content"/>
<h1 class="subheader"/>
<h1 id="pageTitle" class="foo bar"/>
<!-- Shorthand equivalent: -->
<div#content/>
<h1.subheader/>
<h1#pageTitle.foo.bar/>
Marko supports a shorthand based on CSS selectors for less code.
React does not support these helpful shorthands.
Marko supports a concise syntax that drops angled brackets and ending tags in favor of indentation. Here’s how the Marko syntax options compare:
<ul>
<for|color| of=colors>
<li>${color}</li>
</for>
</ul>
ul
for|color| of=colors
li -- ${color}
ul
for|color| of=colors
<li>${color}</li>
The HTML syntax and the concise syntax can be used together:
React does not offer a concise syntax.
Marko starts with simple HTML and allows UI component logic to easily be layered on top.
A React UI component is typically implemented as a class that extends ReactComponent:
class HelloMessage extends React.Component {
render() {
return <div>Hello {this.props.name.toUpperCase()}</div>;
}
}
React also supports a more concise functional component:
function HelloMessage(props) {
return <div>Hello {props.name.toUpperCase()}</div>;
}
However, if state or lifecycle events are needed then a functional UI component must be converted to a class component:
class HelloMessage extends React.Component {
componentDidMount() {
// ...
}
render() {
return <div>Hello {this.props.name.toUpperCase()}</div>;
}
}
Here is the same component in Marko:
<div>Hello ${input.name.toUpperCase()}</div>
Behavior can easily be added to any Marko UI component:
class {
onMount() {
// ...
}
}
<div>Hello ${input.name.toUpperCase()}</div>
Marko also allows JavaScript behavior, CSS styling and HTML markup to be embedded in the Marko template as a single file UI component:
class {
onMount() {
// ...
}
}
style.less {
.hello {
color: red;
}
}
<div.hello>
Hello ${input.name.toUpperCase()}
</div>
Marko compiles components to JavaScript modules that export their rendering APIs, as shown below:
import Greeting from "./components/greeting.marko";
Greeting.renderSync({ name: "Frank" }).appendTo(document.body);
The same UI component can render to streams, such as a writable HTTP response stream:
import Greeting from "./components/greeting.marko";
Greeting.render({ name: "John" }, res);
The users of a Marko UI component do not need to know that the component was implemented using Marko.
Contrast this with React as an example:
import ReactDOM from "react-dom";
ReactDOM.render(
<HelloMessage name="John" />,
document.getElementById("container"),
);
On top of that, React requires that a different module be imported to render the exact same UI component on the server:
import ReactDOMServer from "react-dom/server";
var html = ReactDOMServer.renderToString(<HelloMessage name="John" />);
With React, all custom tags for UI components must be explicitly imported:
import Hello from "./components/Hello";
import GoodBye from "./components/GoodBye";
export default function HelloGoodBye(props) {
return (
<div>
<Hello name={props.name} />
<GoodBye name={props.name} />
</div>
);
}
Marko supports a mechanism for automatically discovering custom tags for UI components based on the project directory structure. Marko walks up the directory tree to discover all directories and it will also automatically discover custom tags exported by installed packages. This approach negates the need for explicitly importing a custom tag to reduce the amount of code needed in a Marko template. For example given the following directory structure:
.
├── components/
│ ├── hello.marko
│ └── good-bye.marko
└── index.marko
The <hello> tag and the <good-bye> tag nested below the components/
directory will automatically be made available to the index.marko at the root:
<div>
<hello name=input.name />
<good-bye name=input.name />
</div>
This approach also allows editors and IDEs to offer autocompletion for custom tags.
Even after rendering has started, Marko allows parts of the view to be rendered
asynchronously using the <await>
tag as shown in the following Marko template:
import fsp from 'fs-promise';
$ var filePath = __dirname + '/hello.txt';
$ var readPromise = fsp.readFile(filePath, {encoding: 'utf8'});
<await(readPromise)>
<@then|helloText|>
<p>${helloText}</p>
</@then>
</await>
Marko compiles a template differently based on whether or not it will be used on the server or in the browser. For example, given the following template:
<div>Hello ${input.name}!</div>
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>");
}
var marko_template = require("marko/vdom").t(__filename);
function render(input, out) {
out.e("DIV", null, 3).t("Hello ").t(input.name).t("!");
}
The Marko compiler was built to support compile-time code generators for custom tags and it also provides support for compile-time transforms. While Babel allows code transformations of JavaScript, the Marko compiler provides support for resolving custom tags declaratively and the Marko AST provides for very powerful and simple transformations as shown in the following code for rendering Markdown to HTML at compile-time:
components/markdown/code-generator.js:
import marked from "marked";
import { removeIndentation } from "./util";
export default function generateCode(el, codegen) {
var bodyText = removeIndentation(el.bodyText);
var html = marked(bodyText);
var builder = codegen.builder;
return builder.html(builder.literal(html));
}
The <markdown> tag can then be used as shown below:
<markdown>
> This section demonstrates Markdown in Marko
# Marko is awesome!
- High performance
- Small
- Intuitive
</markdown>
In this example, after the template is compiled, the marked library is no longer needed at render-time.
Marko and React offer a variety of developer tools. The Marko developer
tools are constantly evolving, but
Marko currently provides tools for unit testing UI components, precompiling .marko
files and generating configuration-less apps (similar to
create-react-app).
Currently, there are no Marko developer tools that integrate with the browser,
but this is something we would like to see in the future. We will go into more
detail on the Marko developer tools in a future post.
Marko offers syntax highlighting across all major IDEs and editors, as well as on GitHub. Marko provides first-class support for the Atom editor with syntax highlighting, Autocomplete for both HTML and custom tags, Hyperclick to quickly jump to referenced files and methods, and Pretty printing to keep your code readable.
Here are just a few reasons you should consider using Marko over React:
Interested in learning more about Marko? If so, you can get additional information on the Marko website. Join the conversation and contribute on GitHub and follow us on Twitter.