DEV Community

Cover image for A First Look at MarkoJS
Ryan Carniato
Ryan Carniato

Posted on • Updated on

A First Look at MarkoJS

As some of you may know I recently joined the Marko team, but I've been working on a future version and haven't actually got to dig into the current. So join me as I learn about how Marko works.

Today we are going to look at building a simple application using MarkoJS. What is MarkoJS you ask? It's JavaScript UI Framework developed at eBay in 2013 with a keen focus on server side rendering. More than being built at eBay, the majority of eBay is built on it.

If you haven't heard of it before you are in shared company. Even though built by a larger tech company, Marko has never had the exposure or carried the same clout as libraries like React or Angular.

Marko has its unique heritage and has very obviously inspired libraries like Vue or Svelte. But most amazingly is the things it has done best since the beginning it is still the best at half a decade later. Things like automatic partial hydration, streaming while you load/render, and having the fastest JS Framework server rendering.

Getting Started

Going to the website at https://markojs.com/ I can see right away Marko uses Single File Components similar to Vue and Svelte*. The second thing I notice is the syntax is a bit unusual.

<div.count>
  ${state.count}
</div>
<button.example-button on-click("increment")>
  Click me!
</button>
Enter fullscreen mode Exit fullscreen mode

It looks like HTML but it has additional special syntax on the tags. Marko views itself as markup-based language. A superset of HTML. This is like the antithesis of "It's just JavaScript".

It makes sense since Marko has its roots in server side template languages like Jade, Handlebars, or EJS. And that has influenced its design immensely, and also served as a high bar to reach in terms of SSR rendering performance.

*Note: Marko does support splitting a component across multiple files if desired: https://markojs.com/docs/class-components/#multi-file-components

Trying My First Sample App

So let's take the Marko CLI for a test run. You can get started with Marko with:

npx @marko/create
Enter fullscreen mode Exit fullscreen mode

There is a short interactive cli asking for project name and what template I'd like to use. Let's choose the default template.

Alt Text

This creates a template with a basic folder structure already built. It looks like a pretty standard setup with a src directory with components and pages directories. Firing it up in VSCode it looks like:

Alt Text

Exploring the Project

The first thing I guess to notice is there is no index.js. No entry point. It appears that Marko is built with Multi-Page Apps in mind. You just make a page in the Pages directory and that is your route.

There is an index.marko which serves as the landing page:

<app-layout title="Welcome to Marko">
  <mouse-mask.container>
    <header>
      <img.logo src="./logo.svg" alt="Marko"/>
    </header>
    <main>
      <p>Edit <code>./pages/index.marko</code> and save to reload.</p>
      <a href="https://markojs.com/docs/">
        Learn Marko
      </a>
    </main>
  </mouse-mask>
</app-layout>

style {
  .container {
    display:flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    font-size:2em; 
    color: #fff;
    background: #111;
    height:100%;
    width:100%;
  }
  img.logo {
    width:400px;
  }
}
Enter fullscreen mode Exit fullscreen mode

This page has a markup block and a style block. The markup starts with layout components that wrap the content of the page which seems to be a logo and docs site link.

Looking at the app-layout component we do in fact see our top level HTML structure:

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="description" content="A basic Marko app.">
  <title>${input.title}</title>
</head>
<body>
  <${input.renderBody}/>
</body>
</html>

style {
  html, body {
    font-family: system-ui;
    padding: 0;
    margin: 0;
  }
  code {
    color: #fc0;
  }
  a {
    color: #09c;
  }
}
Enter fullscreen mode Exit fullscreen mode

So the pattern seems to be an entry point for each page and we can share components between them to create common layouts and controls.

input is the equivalent to props in some libraries. And input.renderBody looks to be the replacement for props.children. There is a subtle difference in that you can think of renderBody's as function calls. The children aren't created until that part of the template is executed.

The last component mouse-mask does some manipulation of the mouse input to create an interesting visual effect over our logo. Not going to focus on that for the moment though. Let's just run the example.

Running the Example

We can startup Marko's Dev server by running:

npm run dev
Enter fullscreen mode Exit fullscreen mode

This automatically starts it building in watch mode and serving our files over port 3000. Loading it in the browser, we can see as we move our mouse over the page we can see the visual effect.
Alt Text

We can also try the production build with npm run build
And then view it using npm start. A quick view in the chrome inspector shows this simple example weighs in at 15.2kb. Looking at the chunks it is fair to say Marko weighs in around 13kb.

Not the smallest library but that is comparable to Inferno, or Mithril and comes in under any of the most popular libraries.

Making it my Own

That's all fine. But I want to make my own site out of this. So I deleted everything except the app-layout component and emptied the Marko template.

I'm no CSS expert but I figured I could throw together a quick directory for a personal blog inspired by the design of a popular developer's blog:

Alt Text

For this exercise I just threw some data at the top of the index.marko file. I also included a function to properly format the dates.

static const POSTS = [
  {
    title: "Making Sense of the JS Framework Benchmark",
    caption: "A closer look at the best benchmark for JS Frameworks",
    link: "https://dev.to/ryansolid/making-sense-of-the-js-framework-benchmark-25hl",
    date: "10/29/2020",
    duration: 9
  },
  {
    title: "Why I'm not a fan of Single File Components",
    caption: "Articial boundaries create artificial overhead",
    link: "https://dev.to/ryansolid/why-i-m-not-a-fan-of-single-file-components-3bfl",
    date: "09/20/2020",
    duration: 6
  },
  {
    title: "Where UI Libraries are Heading",
    caption: "Client? Server? The future is hybrid",
    link: "https://dev.to/ryansolid/where-web-ui-libraries-are-heading-4pcm",
    date: "05/20/2020",
    duration: 8
  },
  {
    title: "Maybe Web Components are not the Future",
    caption: "Sometimes a DOM element is just a DOM element",
    link: "https://dev.to/ryansolid/maybe-web-components-are-not-the-future-hfh",
    date: "03/26/2020",
    duration: 4
  },
]

static function formatDate(date) {
  const d = new Date(date);
  return d.toLocaleDateString("en-US", {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  });
}
Enter fullscreen mode Exit fullscreen mode

Notice the use of the word static as this tells Marko's compiler to run this once on loading the file and it exists outside of the template instance.

From there I added some markup to render this data. It's mostly HTML. Interestingly enough Marko doesn't need any sort of delimiter for attribute assignment. There are no { } or the like.

<app-layout title="Solidarity.io">
  <main class="container">
    <h1>Solidarity</h1>
    <aside class="intro-header">
      <img class="avatar" alt="avatar" src="https://pbs.twimg.com/profile_images/1200928608295849984/1A6owPq-_400x400.jpg">
      A personal blog by
      <a href="https://twitter.com/RyanCarniato" target="_blank">Ryan Carniato</a>
    </aside>
    <ul class="blog-list">
      <for|post| of=POSTS>
        <li class="blog-list-item">
          <h3>
            <a href=post.link target="_blank">${post.title}</a>
          </h3>
          <small>
            ${formatDate(post.date)} •
            <for|coffee| from=0 to=(post.duration/5)>
              ☕️
            </for> 
            ${post.duration} minute read
          </small>
          <p>${post.caption}</p>
        </li>
      </for>
    </ul>
  </main>
</app-layout>

style {
  .container {
    display:flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    color: #fff;
    background: #333;
    height:100%;
    width:100%;
    min-height: 100vh;
  }
  .avatar {
    width: 50px;
    border-radius: 50%;
  }
  .blog-list {
    list-style-type: none;
    margin: 0;
    padding: 0;
  }
  .blog-list-item h3 {
    font-size: 1rem;
    margin-top: 3.5rem;
    margin-bottom: 0.5rem;
  }
  .blog-list-item a {
    color: light-blue;
    text-decoration: none;
    font-size: 2em;
    font-weight: 800
  }
}
Enter fullscreen mode Exit fullscreen mode

The key to this example is using the <for> component. I use it both to iterate over the list of posts and to iterate over the range to show my cups of coffee (one per 5 mins of reading time).

This is definitely the biggest syntax difference:

<for|post| of=POSTS>
  <a href=post.link>${post.title}</a>
</for>
Enter fullscreen mode Exit fullscreen mode

What is this even doing? Well the pipes are something Marko calls Tag Parameters. It is basically a way to do the equivalent of render props. If this were a React Component we'd write:

<For of={POSTS}>{
  (post) => <a href={post.link}>{post.title}</a>
}</For>
Enter fullscreen mode Exit fullscreen mode

And that's it. The end result is we have our simple blog landing page. Just to see how it looks I made the production build and ran it. Everything looks good. But I think the most noticeable thing is the JS bundle size.

There is None

Right, we didn't do anything that required JavaScript in the client so we didn't need to ship the Marko runtime or any bundled JS to the client. Marko is optimized out of the gate with no manual interference to only ship the JavaScript you need.

Conclusion

Well this wasn't meant to be deep. Just a first look into running MarkoJS.

I will say it definitely has a syntax to get used to. I think it is interesting that for a Tag based UI Language it has a lot of the same features you'd find in just JavaScript libraries. Patterns like HoCs(Higher Order Components) and Render Props seem to be perfectly applicable here.

The experience was so similar to developing in other modern JavaScript frameworks I forgot for a second it was server oriented that defaults to shipping minimal JavaScript to the browser. In our case since this was completely static there as no JavaScript sent.

I'm a client-side oriented at heart so this was definitely a departure for me. No JavaScript by default is a new world of possibilities for a whole category of sites.

I hope you will join me next time when I continue to explore MarkoJS and unveil all its powerful features.

Discussion (20)

Collapse
xyn profile image
Mydrax

Wonderful post Ryan! Personally, I'm not a fan of tag templates and how a for/of loop is actually a component. Never liked that stuff in React either, I'd just write Javascript in parenthesis. I think if the syntax had to be like how it is, then something that looks like the v-for binding in Vue might be a bit more intuitive. Maybe I feel like that because I'm used to doing something different from how Marko does it.

Maybe you could throw the code in a gist next time, it'll be easier for readers to mess around with the code that way. Streaming while you're rendering sounds exciting, maybe you could touch on that next?

Collapse
ryansolid profile image
Ryan Carniato Author

Yeah that's a good suggestion. Or put up the repo just didn't seem like enough code. Unfortunately being server first it is a bit clunkier in things like CodeSandbox. This is a bit new for me since I'm primarily used to looking at client setups.

Yeah the streaming solution is really cool. And it is the default rendering in the cli. So technically it is at work there, but need to add some async data loading to leverage. I will see if I can come up with a good example to leverage it.

Collapse
seanmclem profile image
Seanmclem • Edited

React doesn't have loop components. Or really any bundled components. Or template really

Collapse
ryansolid profile image
Ryan Carniato Author

I think he means he doesn't like using HTML tag looking stuff at all. Like Components syntax period and probably prefers something like HyperScript Helpers syntax. github.com/ohanhi/hyperscript-helpers

Collapse
peerreynders profile image
peerreynders • Edited

I think I became aware of MarkoJS in early 2018 - but it seems to have been pretty quiet since then - hype wise at least.

Would it be an accurate guess that MarkoJS uses distinct but integrated rendering approaches on the server and client side?

The mainstream JS frameworks tend to have SSR "bolted on" after the fact by running a surrogate client environment on the server but it sounds like MarkoJS has a more phased approach:

  1. Render as much as possible on the server first - with server style rendering techniques.
  2. On the client take full advantage of the server pre-rendered content but have the dynamic behaviour (more or less) "hook" into that content.

I've been working on a future version

Something to do with this, from Maybe you don’t need that SPA?

These new reactive primitives will replace the VDOM in our runtime ...

Collapse
ryansolid profile image
Ryan Carniato Author

Yes, Marko's hybridness is part of its DNA. Marko started its life more like Jade/Pug with a light hydration layer. Marko 4 in 2017 brought the VDOM which was pretty late to the party, but it is largely since client rendering wasn't its focus.

The unique feature being a combination of separating out JS component code that doesn't need to be shipped to the client, and out of order streaming async content. This means it doesn't wait to render the static parts of the page rendering placeholders and then inserts the async rendered sections in the same stream via script tag insertion as it finishes. Both of these are hype new experimental features in popular frontend libraries and are getting names like Island's Architecture or Suspense, but Marko has been using these in production for years.

But addressing the elephant in the room. You are right there has been a dip in momentum. Patrick the creator of Marko moved on in late 2017 and that definitely affected its presence in the community. The team continued the effort of supporting eBay and has been working to modernize tooling but we definitely need to improve public transparency.

As for what I've been doing on the team, definitely. It's been a lot of designing, but we've been working on a new client runtime. As one would expect with reactivity and client profiling being my strong points. I'm comfortable saying the runtime will be significantly smaller, and client performance in a tier very few UI libraries have seen. This though only serves as a foundation for being able to achieve the type of partial hydration in the article. Still a lot more to be done, but it's definitely really interesting work given how ahead of the curve Marko is in certain areas, and where it is classically weak are my specialties.

Collapse
starptech profile image
Dustin Deus • Edited

I tried Marko.js years ago. I developed several apps and contributed to MarkoJS. Due to the lack of adaption in the community, I switched to Next.js. MarkoJS is primarily driven by eBay. I can't recommend it anymore. This has several reasons:

  • A lot of undocumented "special" cases if you deal with server-side-rendering and CSR.
  • Webpack support took years because eBay didn't use it.
  • No Typescript support.
  • Very opinionated.
  • You have to learn another client-side framework.
  • No developer documentation. You know what I mean if you start a project with it. The current documentation only scratches the surface.
Collapse
ryansolid profile image
Ryan Carniato Author

I think these are pretty fair criticisms. I think the discrepancy between client and server isn't particularly obvious (like dealing with async) and as a server first library in a sea of client side first libraries what it doesn't have on the client is much more obvious. As you said eBay didn't have the use case so it wasn't prioritized.

At the same time Webpack was being developed they were building their own bundler aimed at doing realtime on the fly compilation, again to suit their purposes. It took years to bring everything over to the common tooling especially since that tooling early on did not cover eBays cases.

I think TypeScript is going to take a bit more time. When you consider how long it has taken Vue and Svelte with the communities behind them. It is on the list, but as you can imagine its a pretty long list.

I think the rest of concerns are fairly typical and we're always looking at improving on that. I definitely can see how the lack of community and support would have left you on a sour note and having only joined the project I don't have the long history here. But for me coming across this is like a treasure trove. The technology and approaches developed here standout on their own still years later, so I'm looking at how we can change the narrative.

Collapse
starptech profile image
Dustin Deus • Edited

I think I wasn't clear enough. The API of MarkoJS is very hard to grasp. A lot of heuristics happens under the hood instead of defining a clear API (github.com/marko-js/marko/issues/1285, github.com/marko-js/marko/issues/1291, github.com/marko-js/marko/issues/1276, github.com/marko-js/marko/issues/1290). The productivity boost between Marko.JS and Next.js is tremendous. Next.js rely on React.js and open opportunities you can't archive with Marko.JS. You have to set your clock to zero. Try to find docs about component testing. You will end up researching GitHub. You don't find anything from others except eBay. It's a big deal for eBay but doesn't use it for your own business, just have fun with it in your free time. I did the mistake.

Except HTML-Streaming MarkoJS isn't special compared to other CSR/SSR frameworks like Next.js. It's quite the opposite. In Next.js you can use different rendering techniques and rely on a huge and healthy ecosystem.

Thread Thread
ryansolid profile image
Ryan Carniato Author

It looks like you hit some annoying issues. There are some parts of Marko's legacy that are a bit awkward. I'd love to do some cutting but it's difficult to change things on active users.

The APIs are starkly different. I think that was a bit of culture shock to me too, when I was trying to find my bearings. Like why name this differently .. why is it input.renderBody or not props.children, and then I realize Marko was developing this stuff back before things had consolidated. It's more than set the clock to 0. It's almost like Sliders, where you have moved to a parallel universe where everything did not go to using SPAs.

I've been thinking a lot about how we can streamline this API experience but it's tricky when there isn't prior art. I see you had some difficulties with some of the "magic" heuristics on how Marko automatically determines partial hydration. Those are areas that definitely need some de-mystification and we are actively working on improving. Again this is a bit of an exploratory process as to my knowledge no other framework has these features. This partial hydration has been a hot topic for a while but very little progress has been made comparatively. It's tricky to do without it being some manual thing. I'd love to see Marko's solution be less brittle and we are working on that. But as you've suggested it has been working for eBay who rely on page load performance more than most.

I think on one hand you see eBay and think maybe React or Angular type scenario. But Marko is part of the Open JS Foundation and isn't owned by eBay. It has been primarily the work of 2 maintainers for the past 3 years for better or for worse. So put that in perspective when you are looking at this library. It isn't perfect, and the ecosystem is definitely underdeveloped.

So I'm not suggesting you drop your production Next.js projects for it. Just that I think it's worth checking out as it does offer some unique features that haven't been replicated and that seem to be a hotter topic these days. When combined with the fact the Marko team already working on next-gen solutions in these areas you have something interesting.

That is what interested me. I'm an early adopter and I look for technology that is ahead of the curve and with Marko and isomorphic rendering it's not much competition. I'm not measuring this in terms of practical things like ecosystem and popularity. I'm looking at the raw technology, and the model. I think there is a potential to harness incredible power on the server here the same way I've shown client-side performance with SolidJS. The kind of things that takes years to replicate.

Collapse
joachimzeelmaekers profile image
Joachim Zeelmaekers

Never heard of Marko.js! Will give it a try! Thanks for the introduction!

Collapse
dendihandian profile image
Dendi Handian

So there are must be so many javascript libraries for the 5th contender, but so far only Angular, React, Vue and Svelte who stands out.

Collapse
peerreynders profile image
peerreynders

It's not a popularity contest. One has to assume that MarkoJS exists because it's the best fit for eBay's particular use case - and because it's open sourced others can use it. Sometime before 2017 they moved from a more traditional Java-based web stack to MarkoJS to accelerate their iteration cycles.

What sets MarkoJS apart from current mainstream JS frameworks is that it evolves and extends the traditional server-rendering model rather than starting out with a client-side rendering approach.

For client-side rendering frameworks SSR is typically added after the fact (React -> Next.js, Vue.js -> Nuxt.js, Svelte -> Svelte Kit).

And unlike more recent server-side entries like Nest.js MarkoJS's rendering capabilties encompass both the server and client-side - with emphasis on server-side performance.

Current efforts seem to be geared towards reducing the client-side JS payload size and computational requirements - so that the resulting UX will still be acceptable even on (less than) modest client device hardware.

So MarkoJS isn't about "emulating native apps" but building commercial web applications that a company like eBay can benefit from. And there are likely lots of other use cases that could benefit from that as well.

A clean start for the web

My first thought is that there are two webs: ... The document web ... The "application" web

The truth is that there is a full spectrum from the document to application web and I think that MarkoJS aims for the "highly interactive document" to "application with static parts" slice.

Given the possible range of use cases "one-framework" is just as unrealistic as one-language. Currently the industry's obsession with native-envy and "web application" glamour (vs. just a web site) seems to make React/Next.js the de facto standard regardless of the potential trade-offs (in TCO and UX).

And if it was a popularity contest ... then there is the vocal faction of developers who won't even look at MarkoJS because "it doesn't support TypeScript out of the box". Svelte had to deal with it - adding support this year. Only time will tell if that vocal faction is sufficiently large to make a noticable difference in adoption going forward.

"Marko extends the HTML language" while Svelte is "using a superset of HTML" and both seem to have a philosophy of investing in design time solutions to reduce runtime requirements (e.g. by using compilers). This trend seems a more natural direction for web technology to evolve along compared to anything else that has appeared since AngularJS in 2010.

Collapse
ryansolid profile image
Ryan Carniato Author

Yeah so many. To be fair Svelte is still relatively new in the 4th spot. There was a time where Ember was established enough to even be uncontested 3rd spot. What I like about the popular libraries is that Angular, React, Svelte all have a clear identity so it is easy for people to choose between them. There has been clear narrative on the client side of things.

That being said what makes Marko particular interesting is it does things that none of those libraries do. There is some work in 3rd party space for sure, but Marko is built on it to the point it is optimized at the core level. I'm not going to say those features are necesary to make good sites and apps. But given the interest around things like Next, Gatsby, Nuxt, Nest, Sapper, etc.. its interesting when there is a mature library built on these constructs and not where they are patched on as an afterthought.

Collapse
qm3ster profile image
Mihail Malo

Have you tried the "concise syntax" at all?
It is kinda like Pug(Jade).

Collapse
ryansolid profile image
Ryan Carniato Author

Torn on this one. I used to love using Jade and CoffeeScript and Stylus, but white space matters less of my preference now. As a library writer the fact that we support this is really quite annoying. Marko actually allows concise and not concise mode in the same file. It limits syntax parsing potential which makes it harder for people no using concise mode. Maybe we will make this stricter in the future, but never want to hurt backwards compatibility. As the new guy though this is one of those things where I wasn't there and am just... Why?....

Collapse
qm3ster profile image
Mihail Malo • Edited

I totally missed the part that you work on Marko now.
Does this mean Solid is kill?

Thread Thread
ryansolid profile image
Ryan Carniato Author

Not at all. These have opposite design philosophies and in many ways opposite strengths. I get the opportunity to explore both. I'm learning a ton and I find the situation very symbiotic.

Both deserve to exist the same way say React and Svelte do. This beneficial for both libraries as it gives insight that these approaches usually don't have from the other side.

Collapse
drsensor profile image
DrsEnsor

I'm considering marko for my hobby project. Do you know if there is vite or snowpack plugin for marko out there?

Collapse
ryansolid profile image
Ryan Carniato Author

Not as of yet. Marko is heavily server-side and those projects have only added SSR support in the last few weeks. Before that Marko wouldn't really be leveraging the benefits. We (or someone interested) is going to have to spend some time there as Marko has decent amount of consideration around server solution since our automatic partial hydration needs to leverage the bundler to ensure we don't send unnecessary code to the browser.

SSR support in Vite and Snowpack is still in its early stages. They've been made with less sophisticated SSR solutions in mind. I've already made one PR against Snowpack but there is more work to be done. Honestly, I'd suggest starting to try Marko with the CLI for now.