Marko in September 2026

TL;DR
  • Unkeyed loops, dynamic tags, and lazily loaded pages ship less JavaScript
  • The Prettier plugin no longer deletes code after a syntax error or drops comments
  • More TypeScript syntax works directly in templates
  • Clearer compile errors and a new Compiler API reference
  • Persisted pages are close, built on a more rigorous compiler analysis that already benefits every app

September shipped 25 releases across the compiler, runtime, editor tooling, and integrations. Pages got smaller, the formatter stopped losing code, more TypeScript works in templates, and compile errors got clearer. Persisted pages are nearly here, and the compiler analysis behind them has already landed for every app.

Smaller Bundles

Pages ship less without any template changes.

A <for> without by= no longer ships the code for reordering keyed rows, saving about 190 to 240 bytes brotli on pages with only unkeyed loops (marko#4231), and a by= key is treated as constant within its row (marko#4254).

A dynamic tag's body is bundled only when the browser can render it, so string tag names, names that resolve to known templates, and wrappers chosen only by input stay out of the client bundle (marko#4203, marko#4222, marko#4164). The same goes for static content passed to a local <define>, content passed only through a native spread, and attribute tags a child never reads (marko#4202, marko#4249, marko#4341).

With lazy loading, a parent template that only revives through its lazily loaded child stays out of the eager bundle (marko#4314), and tags lazily loading the same template share one loader (marko#4269). Registrations, streamed resume data, and <try> renderers also got smaller (marko#4201, marko#4188, marko#4093).

Formatting

The Prettier plugin no longer changes what a template means. A template with a syntax error used to print only the part before the error, so prettier --write deleted the rest of the file; it now fails with a code frame and leaves the file alone (prettier#147).

Comments inside an open tag are kept (prettier#154, prettier#153), backslashes in text no longer multiply on each run (prettier#151), spaces that render beside inline tags are preserved (prettier#156), and formatting without a JavaScript parser keeps async methods and scriptlet $ (prettier#150). In editors, Format Document works alongside other Prettier plugins such as Tailwind's (language-server#603), and quick fixes keep comments, types, and escapes (marko#4238, marko#4247, marko#4302).

Improvements

The rest of the month's feature work went into error messages and documentation.

Error Messages

Extra <for> parameters and a load import used before its import statement are now compile errors, and a string content on a native tag is a type error (marko#4179, marko#4279, marko#4304). <DIV> reports that tag names are case-sensitive (marko#4178), spreading arguments into a custom tag is caught at compile time (marko#4092), a root interface or enum gets the hint to use static (marko#4265), and <effect> with a body points at <script> (marko#4266). @marko/vite prints compile errors with their file:line:column and one code frame, naming the child template when the error is in one (vite#318, marko#4223). Long lines are trimmed in code frames, and parsing is linear in sibling count, so a template with 16,000 siblings parses in 0.35 seconds rather than 2.9 (marko#4300).

TypeScript Syntax

More TypeScript works directly in templates. A trailing non-null assertion ends an attribute value instead of swallowing the next attribute or the end of the tag, void ends a type annotation, and delete and ...new read as the JavaScript they are (htmljs-parser#253, htmljs-parser#254, htmljs-parser#255).

static const flags = new Map([
  ["en", "/flags/gb.svg"],
  ["fr", "/flags/fr.svg"],
]);
<img alt="" src=flags.get(input.locale)>
static const flags = new Map([
  ["en", "/flags/gb.svg"],
  ["fr", "/flags/fr.svg"],
]);

<img alt="" src=flags.get(input.locale)!>
static const flags = new Map([
  ["en", "/flags/gb.svg"],
  ["fr", "/flags/fr.svg"],
]);
img alt="" src=flags.get(input.locale)
static const flags = new Map([
  ["en", "/flags/gb.svg"],
  ["fr", "/flags/fr.svg"],
]);

img alt="" src=flags.get(input.locale)!

Highlighting in VS Code, tree-sitter editors, and TextMate follows the same rules (language-server#608, tree-sitter#15, marko-tmbundle#21).

Documentation

A new Compiler API reference covers compiling templates and every compiler option (website#298), and let vs const is now a full article (website#289). The TypeScript guide recommends that tag libraries publish the .d.marko files @marko/type-check emits (website#294), and <html-script> and <html-style> now note that their interpolations run as code (website#299).

Docs Site

The playground can install component libraries through package.json (website#292), docs search finds the section for a bare symbol such as @ or ${ (website#295), and the mobile docs menu fits a whole section on one screen (website#239).

Native Tags

The native <search> element compiles and type-checks like any other HTML element (marko#3313).

Fixes

Fixes this month centered on streaming, resume, and less common template shapes.

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

Coming Soon

Persisted pages, which keep a page alive across navigations by patching it in place, are very close. Every template now renders as patches, the server no longer resends shells the page already holds, and lazy templates, <try>, and streaming content patch the way they render in a document.

The work also pushed the compiler's analysis to be exact about which values the server drives and which the browser owns. That rework has already landed on main for every app, and several of this month's fixes came out of it.

Marko has also joined the Opus 5.5 video generation bandwagon with a persisted pages trailer. Check it out!

Community

loman wrote Apparently the Frontend Framework I Wanted Already Existed on daily.dev, about discovering that Marko already was the framework they had wanted to build: HTML first, with reactive dependencies worked out at compile time and static content left static.

Further Reading


Contributors

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