Marko in May 2026

TL;DR
  • Less over-serialization, so resumable pages ship smaller payloads
  • Pages with no interactivity emit no client assets at all
  • A smaller DOM runtime bundle
  • Fixes for SVG class bindings, ${ in attribute values, and attribute tags

May was a tightening pass on output size. Most of the month went to serializing less of a page's resume data and trimming the DOM runtime, with a batch of correctness fixes alongside.

Serialization

Marko serializes only the state a page needs to resume in the browser. This month sharpened that boundary, so more of a page is recognized as static and left out of the serialized output.

A subtree with no client-side state no longer contributes resume data, even when it sits inside a reactive construct. A conditional branch inside a loop, for example, previously serialized resume data for its contents even when nothing in them was interactive:

<for|item| of=input.items>
  <if=item.featured><span class="badge">${item.label}</span></if>
</for>
for|item| of=input.items
  if=item.featured
    span class="badge" -- ${item.label}

Now the loop renders as plain HTML with nothing serialized for it (marko#3183, resolving marko#3178).

The same reasoning extends through stateful parents. Static content passed from a grandparent, through a parent, to a child used to be serialized whenever the intermediate parent carried its own state, such as an event handler. That content is now threaded through without serializing the code to regenerate it (marko#3184).

The first version of the static-content optimization trimmed too aggressively and could skip serializing a child that had its own reactive state. A follow-up tightened the guard, so a component whose own state pulls content into its scope still serializes correctly (marko#3198).

Resume functions now inline their serialized state where possible, shaving more bytes from the HTML sent over the wire (marko#3195). For background on what crosses the boundary and why, see Serializable State and Reducing Hydration Data.

Static Pages

A page that renders no interactive components now emits no client assets at all. When no template on the page requires client initialization, Marko skips writing the runtime and asset references into the HTML, so a fully static route ships zero JavaScript (marko#3180). This is the lower end of JS Scales from Zero reaching its floor.

Bundle Size

The DOM runtime is smaller this month. A round of micro-optimizations trimmed the generated runtime (marko#3196), and switching the Tags API runtime to named imports lets the bundler inline helpers more effectively, further reducing the shipped bundle (marko#3192). The mechanics of how this code splits per page are covered in Fine-Grained Bundling.

Fixes

A few long-standing rough edges were corrected.

Setting class on an SVG element threw a TypeError, because SVG exposes className as a read-only value rather than a writable string. Class bindings on SVG elements now apply through setAttribute and work for both HTML and SVG (marko#3199).

A literal ${ in an attribute value raised an "Invalid Raw" compile error on templates that also carried client state. The sequence is now handled, so text that merely looks like an interpolation is accepted:

<input type="text" placeholder="search for ${ in the docs">

Member expressions inside an attribute tag now capture surrounding local variables correctly, fixing an oversight in how those closures were translated (marko#3188).

Full details for every change are in the release notes of each package on GitHub.


Contributors

Helpful? You can thank these awesome people! You can also edit this doc if you see any issues or want to improve it.