Innovation in full-stack, server-side rendering JavaScript frameworks continues apace. Marko is developed under the aegis of eBay, who uses it in their e-commerce site. Marko is intended to be an easy-to-learn and high-performance framework.
Ryan Carniato, creator of SolidJS, has been involved in the development of Marko. He describes it as “built specifically to handle the high performance needs of eBay’s platform.” Considering eBay draws 307 million monthly users, Marko can almost certainly handle your use case.
Marko components
Let’s begin our exploration of Marko with its component system. Marko has one of the simplest component definition and discovery systems yet devised. You can see a simple component definition here, a color picker. Notice that in the main index.marko file, there is an HTML element called <color-picker>
, along with a property containing an array of hexidecimal colors. How does Marko find the color-picker
component?
The answer is that Marko begins at the directory where the component usage is found, then, beginning at the sibling directories, moves up looking for a /component directory containing the required component definition. If no such component is found in that app code, Marko will turn to installed dependencies and scan them as well.
Note that Marko searches upward, so that directories in separate branches are not aware of each other. This provides a kind of scoping for the components.
In our case, Marko doesn’t have to look far, because there is a /components/color-picker/index.marko file. (Marko also will load components from a file with the component name in the components directory, or a file inside the component directory with the component name as folder and file.)
If you look at the /components/color-picker/index.marko file, you’ll see the color-picker component definition shown in Listing 1.
Listing 1. color-picker.marko
import getDefaultColors from '../../util/getDefaultColors.js'
class {
onInput(input) {
var colors = input.colors || getDefaultColors();
this.state = {
selectedColor: colors[0],
colors
};
}
handleColorSelected(color) {
this.state.selectedColor = color;
}
}
<div>
<color-picker-header color=state.selectedColor/>
<color-picker-footer colors=state.colors on-color-selected("handleColorSelected")/>
</div>
Listing 1 contains the main elements of a component. It begins by importing another JS file with an import statement (a simple JavaScript function it will use if no colors are passed in). Next, it defines the JavaScript structures it will need; in this case, a class and a function. Last is the actual template markup, which is primarily pulling in two other components, the header and the footer.
Let’s take a closer look at that class definition. It defines two methods.
Property input
The first method is onInput()
. This is a lifecycle method that receives an input argument, and allows for modifying the component’s state (more on state below).
Notice the input variable. That is a reserved identifier in Marko that resolves to the properties passed in from the parent above. Remember that the main component contained a property on the element colors
that pointed to a hard-coded list of hexidecimal colors. Those are now accessed by the child component via the input.colors
property. Properties are fully reactive, meaning the system will ensure that everything dependent on the props will be updated.
Event handling
The second method on the class is handleColorSelected
, which is a custom event handler. You can see that handler in use in Listing 1 where the footer is placed:
<color-picker-footer colors=state.colors on-color-selected("handleColorSelected")/>
Translation: When the on-color-selected
event is triggered, call the handleColorSelected
method, passing whatever arguments are present.
State in Marko
State in Marko is similar to React in that it is expected to be immutable, meaning that one must assign a new state to update a single property. Marko does provide a way to force-trigger an update of state:
this.setStateDirty(property);
Like in other reactive frameworks, state in Marko models the internal state of the component. The reactive system is responsible for carrying out updates of the UI and other values that are dependent on that state.
Iterating and raising events in Marko
Now let's get a look at how the footer component does two things: iterates over the color props and triggers its on-color-selected
event.
The code for color-picker-footer/index.marko is shown in Listing 2.
Listing 2. color-picker-footer
<div.color-picker-footer>
<div.color-picker-selection-container>
<for|color| of=input.colors>
<div>
<color-picker-selection
color=color
on-color-selected("handleColorSelected", color)/>
</div>
</for>
<input key="hexInput" placeholder="Hex value" on-input("handleHexInput")/>
</div>
</div>
You can see the iteration work is done with the <for>
tag. The <for>
tag can specify its iterator variable with the name inside the |
symbols. In this case, the iterator is given the name color
. The collection to be iterated over is identified with the of
property, in this case referring to input.colors
passed in from the parent.
Each member of the input.colors
variable will be output as a div, with access to the color
variable. This is similar in syntax to other frameworks like React.
Emitting events in Marko
Most of the work of the color picking via click is handled by the color-picker-selection
component, which is output inside the for
iterator, along with the color
property and the handler for on-color-selected
.
Listing 3 shows the color-picker-selection
component.
Listing 3. color-picker-selection component
class {
handleColorSelected() {
this.emit("color-selected");
}
}
style {
.color-picker-selection {
width: 25px;
height: 25px;
border-radius: 5px 5px 5px 5px;
display: flex;
flex-direction: column;
margin: 5px 0px 0px 5px;
float: left;
}
}
<div.color-picker-selection
on-click("handleColorSelected")
on-touchstart("handleColorSelected")
style={
backgroundColor: input.color
}/>
Most of color-picker-selection
is devoted to defining the layout (i.e., the small colored squares that allow for clicking a color). Here you see CSS as part of a component’s structure, in the style block, which defines the small rounded square. Note that you can also define CSS in a separate .css file with the name style.css. You can see this latter approach in the /color-picker-selection directory.
In the template markup, notice the inline style that is used to set the background color to the hexadecimal color set on input.color
.
Also observe how the on-click
and on-touchstart
events are used to capture interaction from the user with the color square. The event is passed to handleColorSelected
, which is defined at the head of the file. It uses this.emit("color-selected")
to fire a custom color-selected
event.
Recall that color-picker-footer
watches for custom events with on-color-selected("handleColorSelected", color)
. Notice the handler is calling handleColorSelected
and passing the color
variable. But where is this function defined?
Component definition flexibility
The answer is it is defined in the separate component.js file in the same directory, similar to the separate style.css file you saw earlier. The ability to put the discrete parts of the component into one file or into separate files (or a combination of both) allows for nice flexibility in how you define components as they grow from simple to complex.
Await tag in Marko
Marko also includes an <await>
tag for handling asynchronous rendering. The <await>
tag allows you to pass in a promise, and the framework will deal with waiting for its result and only display it when it becomes available. (This is analogous to the similarly named component in Svelte.)
The <await>
tag simplifies dealing with asynchronous output.
A simple and surprise-free framework
Marko lives up to its promise to be simple to learn. For one thing, it sports only a small number of essential core tags to learn. For another, these tags are fairly straightforward and work in harmony with the principle of least surprise. And Marko is a full-stack framework, so you are also getting server-side rendering out-of-the-box, with integrations for servers like Express.
Combined with the intuitive component definition system, bundlers for Webpack and Rollup, and top-shelf performance, Marko makes a strong case to be your next JavaScript framework.