The Marko Team has been working on a new rendering engine which is slated to become the core engine for Marko in a similar way Fiber(React), Glimmer(Ember), and Ivy(Angular) have been for their respective libraries. Today I want to give you a first peek into what this is going to look like.
A lot has changed since the release of Marko 4 in 2017. Most of the effort has been managing migrations, and updating tooling (ie.. the move to Babel, Webpack, Rollup). Marko 5 is in alpha and represents the modernization of the toolchain. But what about the architectural considerations?
The FLUURT(Fast Lean Unified Update and Render Target) engine is being developed with a few key goals in mind:
- Improve client-side performance
- Improve development experience
These are an acknowledgement of the increasing need for a dynamic and interactive experience on the client. Marko has long had one of the best server-side implementations but as frameworks like Next show up, and even newer compiled approaches like Svelte it is clear it is time to take the next steps.
Updated April 2022: FLUURT as a codename has been has been superseded by Marko 6 now that it is clear that these changes will be arriving in the next major version. I will leave the original article intact but you can treat these as equivalent.
Marko is an interactive templating language first and foremost so we should play to our strengths. We have the ability to compile our templates as desired. So to best accomplish our goals we've decided to attack the problem by building a new foundation on the client.
Being a declarative language with control over templating syntax, using the semantics of fine-grained reactivity is a clear way we can achieve both our goals. Relying on a small set of reactive primitives with code generation drastically reduces runtime size and complexity.
The approach the FLUURT is using is what I'm calling fine-grained compile-time reactivity. This is basically a hybrid between what Svelte does with its compiler and fine-grained reactivity found in libraries like Vue, Solid, or MobX.
The way this works is pretty novel. In a way it is very similar to how Svelte compiles away the reactive system. But instead of compiling things into components that re-run on state change(thanks to a
$invalidate call), FLUURT splits a component into multiple functions. One for each reactive atom(signal) that when executed with a new value conditionally calls any downstream work.
And this extends beyond a simple template as these functions are exported so that parent consumers of the component can selectively import the methods they need if the data they pass in is dynamic. Of course, this is all automatically handled by the compiler so the developer does not need to do anything special.
The end result is compiling away the reactivity but with an execution model very similar to something like SolidJS. Marko basically compiles away any notion of components.
2. First Class Composition
Language design can be challenging but we know that it is of utmost importance to make things consistent. To accomplish this we want to bring reactivity into the language of Marko in an extensible way.
The proposal is that our primitives are just Marko tags. This means that they can be co-located, nested, and composable. Co-located means that they can live in the template where they are used; nested means that they can be mounted/unmounted independent of the component; composable in that they can be constructed and extracted independently of the component file.
One would define a reactive value (ref/observable/signal) with a
let tag. And a derivation (computed/memo/$) with a
const tag. And writing your own can be used and consumed in the same way.
The ability to put these primitives nested in the template creates a cut and paste development experience, where the cost of refactoring is greatly reduced as code can mostly be moved around at will without change.
3. Sub-Component Hydration
From these parts you might be able to see that most of the library works independently of components. One benefit is this approach reduces the overall overhead of having components.
But more interesting, is that this allows for a new type of hydration. We can hydrate along reactive boundaries rather than component ones. We can split the stateful and static parts of the template and ship only parts of components and their descendants to the browser.
Classically with partially hydrated apps, as you might find in Marko or ElderJS, once you hit a stateful component you need to have all the JS code below that point. But FLUURT introduces the ability to break up our islands even smaller. It's more like Hawaii than Taiwan.
The amount of end-user code shipped to the client can be drastically reduced.
There is a lot to be excited about in the upcoming FLUURT engine. It unlocks performance techniques yet to be seen in any major framework. It provides a development experience where writing less code isn't just about the number of characters you commit. And it finally gives Marko the tools it needs to be as much of a force in the client as it has been on the server.
This is just the introduction. Look forward to follow-up articles where I will dig into each area in more depth.
Marko: Designing a UI Language
Check out Marko on Github, Follow us on Twitter, or Join us on Discord to keep apprised of the latest updates.
Top comments (7)
Haha, that's a very interesting name! Saying "FLUURT engine" during discussions is definitely going to make a few people laugh.
Regardless, it's exciting to see progress and most of all the fact that Marko is solving problems in a different way. The syntax looks pretty intimidating though. It looks like a hybrid of JS and XML, like markup that is actually capable of expressing logic. Don't you think that'll make documentation efforts tougher?
I believe sub-component hydration will be a game-changer for depth-heavy components like pages. Do you think that because of this, progressive loading can be further improved? Like currently, you'd go from page -> general layout of the UI -> rest of the UI. Maybe with sub-component hydration, I can load less critical components like text for example the last while prioritizing interactive components like buttons?
Edit: Also, don't you think that the use of let/const as tags will introduce any confusion? From what I understand, they serve different purposes right?
We do share some of these concerns. In the next article I'm going to focus squarely on syntax. I think that the XML syntax for Marko has always been a bit of a barrier and at certain point to reach our goals it just made sense to embrace it. The hope is now that people have seen primitives like Hooks or the Composition API this will be an easy translation. These systems always have framework specificity no matter how we try to hide them. Svelte's $ sign has both assignment and function call semantics. This isn't really any different.
constis an interesting choice too. But I think you will find as we show more examples and you get a chance to try writing the code they actually make more sense than you'd expect. In our reactive world there are mutable bindings and constant bindings. This might be a bit too cerebral to translate but our hope is that you won't be thinking much about this as you get going.
What we hope that will help documentation is reining in all the current syntax Marko has into a consolidated set, that is both used internally by Marko and externally in end-user code. As always part of the reason to showcase this stuff early is to get feedback so we can make improvements.
Subcomponent Hydration is case that definitely is for more complicated sites and applications. Marko already aggressively partially hydrates, but we still see so many places due to component boundaries we need to ship more JS than we'd like. The only place you absolutely need to share the full JS is where the client is responsible for rendering, like under a conditional or dynamic loop. There are many places where the parent components are stateful, or have events etc, but 3 quarters of their template is still static. Maybe there are nested components on the static side as well. This way we choose to not include any of that code. It isn't enough to identify this is the beginning of the dynamic Island.
This can inform hydration strategy definitely. However, we do see value in separating the mechanics here from the UX. Sometimes arbitrary cascading loading/hydration is not desirable so expect to see more control over async boundaries and placeholders in this next version. There are few different ways to attack progressive hydration and we will be looking at them. But the first big win is to just combine our streaming with this new ability to ship less JS hydration code.
One of the problems with JSX is that people treat it as markup even when it's only an "XML-like syntax extension to ECMAScript". Similarly here - it's not markup.
Other than that templating systems like Nunjucks (based on Jinja) can accomodate a lot of logic (for better or worse) - so the proposed syntax just seems to cut down on the braces and percent characters and is in line with Marko's philosophy: Marko is HTML re-imagined as a language.
Perhaps initially. But eventually:
let- define new observable
const- define computed (i.e. has one or more dependencies)
Thanks for the update.
This seems to pick up where Michael Rawlings: Maybe you don’t need that SPA (2020-May) left off.
Readers interested in some more background:
That's a really good point. This is the culmination of years of various research between myself and the rest of Marko team. We're getting to a point now where we can start sharing these ideas and get feedback.
Another good one that is hidden in the article is:
wait marko was developed by ebay, right?
Yes, and still is the primary library ebay.com is built on. In 2017 it was given to the JS Foundation to "ensure that it remains a healthy open source project", shortly before Patrick the original author moved on from eBay and the project.
The Future of Marko
eBay employs a small team to continue to maintain and develop the project, which I recently joined this past summer.