The first thing they realized were the existing tools weren't going to solve the problem. So Marko was created with exactly this in mind. But that was years ago and we are seeing other frameworks like React and Astro starting to adopt some of techniques Marko uses. So what has Marko been working on?
Marko was really built with only 2 main things in mind. It needed to have progressive server rendering. We need to have the page on the client as soon as possible without waiting for Async but we need to support SEO.
That is immediate rendering with content progressively loading in without pulling in the big framework bundles. Nothing else does that today. But a few libraries are looking at doing parts of it.
Maybe the best way to picture this for those up to date on the latest tech, is picture if you wrote an app with HTML based template language and used a compiler like Svelte to automatically generate Astro-like Islands out of only the code that needs to run in the browser, and it's all served to you using something like the upcoming React 18's Suspense for SSR.
Yep. 2014. Sure things were a bit more manual than they are today, but the core pieces were there. This is a great start to a story but then the difficulty sets in.
How do you possibly achieve such futuristic development in 2014? Well you pretty much need to write your own Parser, Compiler, and Bundler. It wasn't enough to handle the templates but in order to package things differently for the server you need a bundler. So the team created Lasso. The idea with Lasso was to compile and serve templates on demand instead of upfront. This way dev server startup times could be fast and incremental rebuilds were possible.
This was important as Marko being one of the earliest library with truly isomorphic development, where the same templates worked on server and browser, needed to coordinate multiple builds on code changes. Honestly it wasn't until Snowpack 3 or Vite 2 that there was a true successor.
So supporting the growth and tooling around Marko was definitely the focus for the next couple years. Partial Hydration got smarter, and architecture was streamlined. The next groundbreaking release was Marko 4 in 2017 where Marko starting to be conscious of browser performance opted into using a Virtual DOM to handle client rendering.
The migration to Marko 4 was also a big effort at eBay as well. Internally Marko had its roots as early as 2012 and you can imagine even with automated migration scripts there were challenges. To put it in perspective for React devs that time span bridges the gap before React existed in Open Source, through the
createClass days, to ES6 classes and almost to Hooks.
The Marko team now only 2 people, simultaneous supported migrating the eBay platform written mostly on Marko and upgrading the tooling around Marko to be more modern. This included the move to Babel, replacing Lasso with other bundlers that didn't quite fill the gap, support for Testing Library, Jest and Storybook. The majority of this work happened over 2018-2019 and would become Marko 5.
The project, codenamed FLUURT, was an idea that had been floating around really since the release of Marko 4 but there had been no time to pursue it. FLUURT is an acronym that Michael Rawlings had come up with that stood for Fast Lean Unified Update & Render Target.
The concept is that with sufficient knowledge from compiler analysis it would be possible to produce the optimal code for any target platform. Whether that be server, browser, mobile, or even a different JS Framework.
This is really 2-part effort. There is the method and language for analysis, and then there is the compilation and runtime to support it. Both are immensely difficult challenges.
The first carries with it all the stigma and DX concerns with understanding how languages function. I've written about it in Marko: Designing a UI Language. Some people will not be happy with it but Marko's new Tags API is like a marriage between something like React's Hooks and Svelte's
$: syntax. It has all the compiled magic without losing any of the composability.
Composability is king. But so is clear analyzable syntax. Blending both is key incidentally to achieving the granularity that we want for code elimination in the browser for Partial Hydration. We really needed to go component-less not only as a technology but as a language. Luckily this aligns with Marko's earliest goal of being a superset of HTML. Writing and maintaining code should be as easy as working with HTML templates.
Generating the suitable client side approach has been a bit of trial and error. There are many considerations and details. From the ability remove even more static code from the browser to handling of Async consistency and transitions that needed to be ironed out.
The second approach was a runtime reactive approach with precompiled dependencies. This reduced the overhead of subscriptions and got performance in Inferno-like range in the browser. Static dependencies, while saving us from having to run computations to determine dependencies like other runtime reactive libraries (MobX, Vue, Solid), required the dependencies to be reactive variables themselves. This led to over-wrapping of expressions and used up more memory. It also still put considerable weight on template boundaries.
We spent most of the fall on the 2nd attempt before shifting our focus on releasing Marko 5 and related tooling like Vite and universal Hot Module Replacement. However this effort wasn't without value. We had used it to develop 2 key new features for the Marko compiler.
First, we added an analysis pass that gather's metadata about all your Marko files so that as the compiler transforms the code we can make informed decisions based on the contents of child templates that are imported. Secondly, we pulled the core parts of the bundler into Marko's compiler so that we have a generic solution to handling code elimination for automatic Partial Hydration. While this lets it be bundler agnostic, more importantly, it gives us the ability to do wider sweeping changes on the final output.
Coming back refreshed Michael realized we could compile away the reactivity without the limitations of local compilation. We had already built the pieces we needed and the answer ironically is the simplest one we had to date.
What if the compiler could split a template into multiple exports that were tree-shakable, around the different inputs(props) they accepted. A parent could decide, based on the statefulness of its own data it was passing, which exports it needs to import. And then through the use of shared scope and inlined calls of those imported methods you could effectively compile away all reactivity but keep a granular update model.
This doesn't have the problems of the compiled reactivity as you aren't making signals or computations anymore but passing the data as is with simple dirty checks. If that sounds familiar, it should. It's basically how Svelte works on a localized scope, except Marko's version transcends files.
We will be releasing Marko's Tag API, in Marko 5 ahead of the release of the new compiler and runtime. We can leverage Marko's cross template analysis to give the minimum feature set so that you can get started with the new features and syntax.
Together with Marko's already powerful Partial Hydration and Streaming Server rendering we can deliver on the developer experience. This will also give a good opportunity for feedback. We've been working tirelessly long behind closed doors and we need to do better at making our efforts visible.
We now are tracking our projects more visibly on Github and intend to give more regular updates. We will follow that in the fall with the beta release of the next version of Marko. Sometimes good things take a long time. But it will be well worth the wait.