<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: defunkt-dev</title>
    <description>The latest articles on DEV Community by defunkt-dev (@defunktdev).</description>
    <link>https://dev.to/defunktdev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4023504%2Ffcb3eacf-8c3c-4fc1-9030-cdc50369c8ac.png</url>
      <title>DEV Community: defunkt-dev</title>
      <link>https://dev.to/defunktdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/defunktdev"/>
    <language>en</language>
    <item>
      <title>This Framework Was Streaming HTML Before It Was Cool. Learn It in 135 interactive in-Browser Lessons 🚀</title>
      <dc:creator>defunkt-dev</dc:creator>
      <pubDate>Mon, 03 Aug 2026 16:07:16 +0000</pubDate>
      <link>https://dev.to/defunktdev/this-framework-was-streaming-html-before-it-was-cool-learn-it-in-135-interactive-in-browser-19e0</link>
      <guid>https://dev.to/defunktdev/this-framework-was-streaming-html-before-it-was-cool-learn-it-in-135-interactive-in-browser-19e0</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/defunktdev/this-framework-was-streaming-html-before-it-was-cool-learn-it-in-135-browser-lessons-4l75" class="crayons-story__hidden-navigation-link"&gt;This Framework Was Streaming HTML Before It Was Cool. Learn It in 135 Browser Lessons&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/defunktdev" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4023504%2Ffcb3eacf-8c3c-4fc1-9030-cdc50369c8ac.png" alt="defunktdev profile" class="crayons-avatar__image" width="399" height="399"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/defunktdev" class="crayons-story__secondary fw-medium m:hidden"&gt;
              defunkt-dev
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                defunkt-dev
                
              
              &lt;div id="story-author-preview-content-4284361" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/defunktdev" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4023504%2Ffcb3eacf-8c3c-4fc1-9030-cdc50369c8ac.png" class="crayons-avatar__image" alt="" width="399" height="399"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;defunkt-dev&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/defunktdev/this-framework-was-streaming-html-before-it-was-cool-learn-it-in-135-browser-lessons-4l75" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 1&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/defunktdev/this-framework-was-streaming-html-before-it-was-cool-learn-it-in-135-browser-lessons-4l75" id="article-link-4284361"&gt;
          This Framework Was Streaming HTML Before It Was Cool. Learn It in 135 Browser Lessons
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tutorial"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tutorial&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/frontend"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;frontend&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/defunktdev/this-framework-was-streaming-html-before-it-was-cool-learn-it-in-135-browser-lessons-4l75#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            7 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>This Framework Was Streaming HTML Before It Was Cool. Learn It in 135 Browser Lessons</title>
      <dc:creator>defunkt-dev</dc:creator>
      <pubDate>Sat, 01 Aug 2026 02:35:20 +0000</pubDate>
      <link>https://dev.to/defunktdev/this-framework-was-streaming-html-before-it-was-cool-learn-it-in-135-browser-lessons-4l75</link>
      <guid>https://dev.to/defunktdev/this-framework-was-streaming-html-before-it-was-cool-learn-it-in-135-browser-lessons-4l75</guid>
      <description>&lt;h2&gt;
  
  
  What this about?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A free interactive tutorial for &lt;a href="https://markojs.com/" rel="noopener noreferrer"&gt;Marko 6&lt;/a&gt;. Real dev server in a WebContainer, covering 0 kB pages, resumability, out-of-order streaming, SSE, and app federated microfrontends&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When React 18 shipped Suspense-aware incremental streaming SSR in 2022, it felt like a breakthrough.&lt;a href="https://markojs.com/" rel="noopener noreferrer"&gt;Marko&lt;/a&gt; has been streaming HTML since 2014 😏. It powered eBay's product pages back when server rendering was considered a legacy technique. It ships zero JavaScript for static pages. Not a small runtime. Zero. It had a compiler before frontend frameworks had a compiler!&lt;/p&gt;

&lt;p&gt;The framework was never the problem. The problem was that trying it meant a lot of setup time.&lt;/p&gt;

&lt;p&gt;So I built a tutorial that removes the setup. 135 lessons across 12 parts, running in your browser:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://markojs-tutorial.netlify.app/" rel="noopener noreferrer"&gt;markojs-tutorial.netlify.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No install, no signup. A real &lt;a href="https://github.com/marko-js/run" rel="noopener noreferrer"&gt;&lt;code&gt;@marko/run&lt;/code&gt;&lt;/a&gt; app boots inside a StackBlitz WebContainer in your tab. Lesson text on the left, a live editor with a terminal and preview on the right. Edits hot-reload. Every exercise has a Solve button, and every lesson can be downloaded as a runnable project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Femmfpykdbp6vii12sny6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Femmfpykdbp6vii12sny6.png" alt="Tutorial Preview" width="799" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's built with &lt;a href="https://tutorialkit.dev" rel="noopener noreferrer"&gt;TutorialKit&lt;/a&gt;, plus a custom CodeMirror package that runs &lt;a href="https://markojs.com/" rel="noopener noreferrer"&gt;Marko's&lt;/a&gt; actual VS Code TextMate grammar through shiki's WASM engine, so highlighting in the lesson editor matches your IDE exactly.&lt;/p&gt;




&lt;p&gt;Here are the patterns the tutorial teaches, and why they're worth your time.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Your page is HTML, your interactivity is surgical
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;.marko&lt;/code&gt; file is HTML with a few additions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;let/count=0/&amp;gt;

&amp;lt;button onClick() { count++ }&amp;gt;
  Clicked ${count} times
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a complete reactive component. No imports, no &lt;code&gt;useState&lt;/code&gt;, no export. &lt;code&gt;&amp;lt;let&amp;gt;&lt;/code&gt; declares state, and assigning to it is what triggers the update.&lt;/p&gt;

&lt;p&gt;The interesting part is what the compiler does next. Marko splits every template into two programs, one for the server and one for the client (Btw, marko had multiple compilation outputs before Svelte), and the client program contains only what the browser actually needs. One lesson has you build a page, run &lt;code&gt;node inspect.mjs&lt;/code&gt;, and read the resulting bundle yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Static markup becomes plain HTML and ships no JS&lt;/li&gt;
&lt;li&gt;Server-only computation (a &lt;code&gt;formatPrice&lt;/code&gt; helper) stays on the server and never appears in the bundle&lt;/li&gt;
&lt;li&gt;The one &lt;a href="https://www.patterns.dev/vanilla/islands-architecture/" rel="noopener noreferrer"&gt;interactive widget&lt;/a&gt; is the only thing that ships&lt;/li&gt;
&lt;li&gt;And an interesting thing to note, Marko does sub-component hydration. This goes beyond islands.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A fully static &lt;a href="https://markojs.com/" rel="noopener noreferrer"&gt;Marko&lt;/a&gt; page ships 0 kB of client JavaScript. Not hydrated cheaply. Not shipped at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Resumability: the client doesn't re-run your code
&lt;/h2&gt;

&lt;p&gt;Traditional hydration re-executes the client-side component logic for each hydrated boundary so the framework can reconstruct its runtime and wire up interactivity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://markojs.com/" rel="noopener noreferrer"&gt;Marko 6&lt;/a&gt; &lt;strong&gt;resumes&lt;/strong&gt; instead. The tutorial demonstrates it with a component that has a render-time &lt;code&gt;console.log&lt;/code&gt; and a button. The log shows up once, in the server terminal. The browser console stays silent. The button still works. The client never re-ran the render logic; it picked up serialized markers from the HTML and continued from there.&lt;/p&gt;

&lt;p&gt;Your render code runs once. You watch it happen live.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Streaming: three tags cover everything
&lt;/h2&gt;

&lt;p&gt;The tutorial dedicates whole parts to this (3, 10 and 11), including recreations of Marko's classic streaming demos.&lt;/p&gt;

&lt;p&gt;Streaming means sending HTML chunk by chunk as each piece is ready, instead of buffering the whole page. The browser starts parsing, and starts fetching your CSS, fonts and JS, while your slow database query is still running.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;&amp;lt;await&amp;gt;&lt;/code&gt; for in-order streaming
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;h1&amp;gt;Dashboard&amp;lt;/h1&amp;gt;

&amp;lt;await|signups|=loadSignups()&amp;gt;   &amp;lt;!-- 1s --&amp;gt;
  ${signups.total} new signups
&amp;lt;/await&amp;gt;

&amp;lt;await|traffic|=loadTraffic()&amp;gt;   &amp;lt;!-- 3s --&amp;gt;
  ${traffic.views} views
&amp;lt;/await&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stream holds at each &lt;code&gt;&amp;lt;await&amp;gt;&lt;/code&gt; and releases in document order. Simple, no client JS, but the slow section blocks everything below it.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;&amp;lt;try&amp;gt;&lt;/code&gt; with &lt;code&gt;&amp;lt;@placeholder&amp;gt;&lt;/code&gt; for out-of-order streaming
&lt;/h3&gt;

&lt;p&gt;Wrap the slow section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;try&amp;gt;
  &amp;lt;await|traffic|=loadTraffic()&amp;gt;
    ${traffic.views} views
  &amp;lt;/await&amp;gt;

  &amp;lt;@placeholder&amp;gt;
    Crunching traffic numbers…
  &amp;lt;/@placeholder&amp;gt;
&amp;lt;/try&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the server doesn't hold. It streams the placeholder immediately, keeps going with the rest of the page, and when the promise settles it streams the real content, which replaces the placeholder in place. Content below the fold can arrive before content above it. Out of order, on purpose. Note, that while it streams out-of-order, the order is proper at paint.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;&amp;lt;@catch&amp;gt;&lt;/code&gt; for errors
&lt;/h3&gt;

&lt;p&gt;Same &lt;code&gt;&amp;lt;try&amp;gt;&lt;/code&gt; block, rejection path. In the progressive-rendering lesson, the main section races a 5-second timeout with &lt;code&gt;Promise.race&lt;/code&gt;, and &lt;code&gt;&amp;lt;@catch&amp;gt;&lt;/code&gt; renders the fallback if it loses.&lt;/p&gt;

&lt;p&gt;One more thing the tutorial makes a point of: &lt;code&gt;&amp;lt;await&amp;gt;&lt;/code&gt; is the same tag on the client. When a browser interaction swaps the promise you're awaiting, the same &lt;code&gt;&amp;lt;await&amp;gt;&lt;/code&gt; re-renders the result client-side. One mental model for both runtimes.&lt;/p&gt;

&lt;p&gt;The streaming-search lesson recreates Marko's old v3 demo: a single Express handler, &lt;code&gt;template.render({}).pipe(res)&lt;/code&gt;, and 30 search results arriving in batches down one held-open HTTP response. Pure HTML, no client JavaScript. You can watch the raw HTTP body arrive in chunks.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Rendering a live stream with recursion
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;for&amp;gt;&lt;/code&gt; can't iterate an async stream, so how do you render "one item per event until the stream ends"? You recurse.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;define/Wait&amp;gt;
  &amp;lt;try&amp;gt;
    &amp;lt;await|messages|=Promise.any([
      once(messageStream, "done"),
      once(messageStream, "data"),
    ])&amp;gt;
      &amp;lt;if=messages &amp;amp;&amp;amp; messages.length&amp;gt;
        &amp;lt;Notice message=messages[0].message/&amp;gt;
        &amp;lt;Wait/&amp;gt;                     &amp;lt;!-- render the next one --&amp;gt;
      &amp;lt;/if&amp;gt;
    &amp;lt;/await&amp;gt;
    &amp;lt;@placeholder&amp;gt;Waiting for the stream…&amp;lt;/@placeholder&amp;gt;
  &amp;lt;/try&amp;gt;
&amp;lt;/&amp;gt;

&amp;lt;Wait/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;&amp;lt;define&amp;gt;&lt;/code&gt; creates a reusable snippet named &lt;code&gt;Wait&lt;/code&gt;. It awaits the next stream event, renders one &lt;code&gt;&amp;lt;Notice&amp;gt;&lt;/code&gt;, then invokes itself, so each notice nests inside the last as its own branch of the render tree. When the stream signals done, the &lt;code&gt;&amp;lt;if&amp;gt;&lt;/code&gt; fails and the recursion stops.&lt;/p&gt;

&lt;p&gt;The payoff: every notice hydrates independently and stays interactive (each has its own dismiss button), even though they arrived one at a time over a live stream. I haven't seen this pattern documented anywhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. SSE and EventSource
&lt;/h2&gt;

&lt;p&gt;Part 9 covers the browser's native &lt;code&gt;EventSource&lt;/code&gt; API, the simplest real-time primitive on the web:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/ticker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* a new event arrived */&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One long-lived HTTP response. The server writes &lt;code&gt;data:&lt;/code&gt; frames whenever it wants, the browser fires events. No WebSocket handshake, no library, and reconnection is built in.&lt;/p&gt;

&lt;p&gt;Part 10 then combines SSE with the next pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. App federation: micro-frontends without the iframe tax and the hassle of module federation
&lt;/h2&gt;

&lt;p&gt;Module federation shares code between apps. App federation shares rendered apps. Part 10 teaches it with &lt;a href="https://github.com/marko-js/micro-frame" rel="noopener noreferrer"&gt;&lt;code&gt;@micro-frame/marko&lt;/code&gt;&lt;/a&gt;, running a host app and a remote app at the same time in the same browser tab. Two dev servers, one WebContainer.&lt;/p&gt;

&lt;p&gt;The host embeds the remote with one tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;micro-frame src="http://localhost:3001/fragment" client-reorder timeout=0&amp;gt;
  &amp;lt;@loading&amp;gt;Loading the remote notice…&amp;lt;/@loading&amp;gt;
  &amp;lt;@catch|err|&amp;gt;The remote is unavailable: ${err.message}&amp;lt;/@catch&amp;gt;
&amp;lt;/micro-frame&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;&amp;lt;micro-frame&amp;gt;&lt;/code&gt; fetches the remote's fully rendered HTML and transcludes it in place. You get iframe-style ownership isolation with none of the iframe box. The remote team deploys independently, and the host only knows a URL. Loading and error states are just attribute tags.&lt;/p&gt;

&lt;p&gt;The SSE chapter pushes this further with a faucet-and-slot pair:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!-- the faucet: opens ONE SSE stream, renders nothing --&amp;gt;
&amp;lt;micro-frame-sse timeout=0 name="notices"
  src=`http://localhost:3001/sse-notices?slotName=main`
  read(ev) { return [ev.lastEventId, JSON.parse(ev.data), false]; }
/&amp;gt;

&amp;lt;!-- the slot: streamed chunks land here --&amp;gt;
&amp;lt;micro-frame-slot from="notices" slot="main" timeout=0 client-reorder&amp;gt;
  &amp;lt;@loading&amp;gt;Waiting for notices…&amp;lt;/@loading&amp;gt;
&amp;lt;/micro-frame-slot&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The remote pushes rendered HTML fragments as SSE frames, each tagged with a slot id, and the host drops each chunk into the matching slot. The multi-slot lesson streams different fragments to different regions of the host page over a single connection. Every arriving fragment hydrates and stays interactive.&lt;/p&gt;

&lt;p&gt;A live-updating, independently deployed micro-frontend, with loading and error states, in roughly ten lines of template.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. The Hacker News app
&lt;/h2&gt;

&lt;p&gt;Part 12 tours the official Marko Hacker News reader, adapted to Marko 6, running live in your browser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;File-based routing (&lt;code&gt;+page&lt;/code&gt;, &lt;code&gt;story.$id+page&lt;/code&gt;, &lt;code&gt;+layout&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;server import&lt;/code&gt; keeping the API client server-side&lt;/li&gt;
&lt;li&gt;Story lists streaming in through &lt;code&gt;&amp;lt;await&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A self-recursive &lt;code&gt;comment.marko&lt;/code&gt; rendering nested threads to any depth&lt;/li&gt;
&lt;li&gt;Collapse toggles with &lt;code&gt;&amp;lt;let&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the bundling payoff, measured: the list and user pages ship 0 kB of client JS. Only the story page ships anything, and that's just the bytes for its collapse toggle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the format matters
&lt;/h2&gt;

&lt;p&gt;Everything actually runs in your browser, in an interactive manner. The dev server, the terminal, hot reload, even two federated apps at once, all inside a WebContainer in your tab. &lt;/p&gt;

&lt;p&gt;Every exercise ships its starting files and its solution. Stuck? Solve shows the finished state. Want to keep going? Download the lesson as a complete runnable project.&lt;/p&gt;

&lt;p&gt;And the scope goes well past the basics: fundamentals, reactivity, async and error boundaries, components in depth, &lt;code&gt;@marko/run&lt;/code&gt; full-stack routing, streaming, progressive rendering, SSE, micro-frontends, GraphQL, SPA routing, CSP nonces, deployment adapters for Node, static and Netlify Edge, and using Marko without its meta-framework at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://markojs-tutorial.netlify.app/" rel="noopener noreferrer"&gt;markojs-tutorial.netlify.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Jump to Part 11 (Streaming) for the fireworks, or Part 1 if you're new&lt;/li&gt;
&lt;li&gt;Break something, hit Solve, download the lesson&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The repo is open source at &lt;a href="https://github.com/defunkt-dev/markojs-tutorial" rel="noopener noreferrer"&gt;github.com/defunkt-dev/markojs-tutorial&lt;/a&gt;. &lt;strong&gt;Stars ⭐️ and issues welcome.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The ecosystem spent a decade arriving at streaming HTML, islands, and shipping less JavaScript. &lt;a href="https://markojs.com/" rel="noopener noreferrer"&gt;Marko&lt;/a&gt; had the answers the whole time. Now there's a place to learn them 🚀, in your browser, for free &amp;amp; we are taking it to the world for &lt;a href="https://www.lorenstew.art/blog/10-kanban-boards" rel="noopener noreferrer"&gt;everyone&lt;/a&gt; to see.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>frontend</category>
    </item>
    <item>
      <title>auto-animate now speaks marko</title>
      <dc:creator>defunkt-dev</dc:creator>
      <pubDate>Wed, 15 Jul 2026 08:50:08 +0000</pubDate>
      <link>https://dev.to/defunktdev/auto-animate-now-speaks-marko-5c7</link>
      <guid>https://dev.to/defunktdev/auto-animate-now-speaks-marko-5c7</guid>
      <description>&lt;p&gt;As of &lt;code&gt;@formkit/auto-animate@0.10.0&lt;/code&gt;, the library ships an official Marko adapter (&lt;a href="https://github.com/formkit/auto-animate/pull/239" rel="noopener noreferrer"&gt;PR #239&lt;/a&gt;). &lt;/p&gt;

&lt;p&gt;If you write Marko 6, you can now get animated list additions, removals, and reorders with one tag and no per-item work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @formkit/auto-animate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import AutoAnimate from "@formkit/auto-animate/marko"

&amp;lt;let/items = [
  { id: 1, text: "Mango" },
  { id: 2, text: "Papaya" },
]&amp;gt;

&amp;lt;ul/listRef&amp;gt;
  &amp;lt;for|item| of=items by="id"&amp;gt;
    &amp;lt;li&amp;gt;${item.text}&amp;lt;/li&amp;gt;
  &amp;lt;/for&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;AutoAnimate parent=listRef/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole integration. Mutate &lt;code&gt;items&lt;/code&gt; however you like (push, filter, reverse, shuffle) and the DOM changes animate. There's a live demo covering the full API, server-rendered with &lt;code&gt;@marko/run&lt;/code&gt;: &lt;strong&gt;&lt;a href="https://codesandbox.io/p/github/defunkt-dev/auto-animate-marko-demo/main" rel="noopener noreferrer"&gt;demo&lt;/a&gt;&lt;/strong&gt; · &lt;strong&gt;&lt;a href="https://github.com/defunkt-dev/auto-animate-marko-demo" rel="noopener noreferrer"&gt;source&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The rest of this post is the part that's actually interesting: what shape an auto-animate adapter should take in a framework that has no directives, no hooks, does not perform a hydration re-render during initialization/resume (it resumes the server-rendered DOM in place) and what we learned when the published package met the newest Vite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Marko in sixty seconds
&lt;/h2&gt;

&lt;p&gt;If you haven't met &lt;a href="https://markojs.com/" rel="noopener noreferrer"&gt;Marko&lt;/a&gt;: it's an HTML-based language for building reactive UIs, born at eBay (it powers eBay.com) and now under the OpenJS Foundation. Nearly any valid HTML is valid Marko; you add reactivity with tags instead of a component API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;let/count=0&amp;gt;
&amp;lt;button onClick() { count++ }&amp;gt;
  Clicked ${count} times
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three properties matter for this article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fine-grained by compilation.&lt;/strong&gt; The compiler analyzes which parts of a template are actually interactive and ships JavaScript only for those, at sub-template granularity. Static content ships zero JS. This goes further than islands: you don't declare server versus client components, the compiler works it out from the state tree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resumable, not hydrated.&lt;/strong&gt; The server's HTML &lt;em&gt;is&lt;/em&gt; the app. The client picks it up where the server left off, with no re-render pass to rebuild a component tree. (This is the property the SSR section below hinges on.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiny output.&lt;/strong&gt; In an &lt;a href="https://www.lorenstew.art/blog/10-kanban-boards" rel="noopener noreferrer"&gt;independent comparison&lt;/a&gt; that built the same kanban app in ten frameworks, Marko shipped the smallest bundles of all: 6.8 kB compressed for the homepage and 28.8 kB for the full interactive board, versus 176.1 kB for Next.js. Our own numbers agree: every fully interactive route in the demo linked above ships about 10 kB of gzipped JavaScript in total, framework runtime &lt;em&gt;and&lt;/em&gt; animation library included. Marko's runtime is estimated to be 7KB Gzipped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Marko also streams HTML (in-order and out-of-order) as it's ready and has built-in TypeScript support. &lt;code&gt;@marko/run&lt;/code&gt; is its file-based SSR meta-framework, which the demo uses.&lt;/p&gt;

&lt;h2&gt;
  
  
  What auto-animate is (and isn't)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/formkit/auto-animate/" rel="noopener noreferrer"&gt;auto-animate&lt;/a&gt; does exactly one thing: when the direct children of an element are added, removed, or moved, it FLIP-animates the change. No springs, no gestures, no orchestration, no timelines. Its about 14K stars are for the effort-to-payoff ratio, not for depth. It's the animation you were never going to hand-write for every CRUD list in your app, delivered in one line.&lt;/p&gt;

&lt;p&gt;That small surface matters for this story. It's why a complete Marko adapter fits in a ~45-line tag, and why the demo linked above covers the complete Marko-adapter surface in five routes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The adapter's shape: why a tag
&lt;/h2&gt;

&lt;p&gt;Every framework adapter in this library mirrors its host framework's idiom. React and Preact get a hook that returns a ref. Vue and Angular get a directive you sprinkle on the parent element. Solid gets a directive too. Svelte uses actions.&lt;/p&gt;

&lt;p&gt;Marko has none of those primitives, and it turns out not to need them. The adapter is a tag with three attributes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;AutoAnimate
  parent=listRef      // a native element tag-variable
  options={ duration: 300, easing: "ease-in-out" }  // or a plugin function
  enabled=showMotion  // reactive boolean
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;parent&lt;/code&gt; is a Marko tag-variable: write &lt;code&gt;&amp;lt;ul/listRef&amp;gt;&lt;/code&gt; and pass &lt;code&gt;listRef&lt;/code&gt;. The target is explicit and typed, with no wrapper element and no DOM query.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;options&lt;/code&gt; takes the same object as the core (&lt;code&gt;duration&lt;/code&gt;, &lt;code&gt;easing&lt;/code&gt;) or a plugin function. It's read once when the tag mounts, matching the core's behavior (there is no &lt;code&gt;setOptions&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;enabled&lt;/code&gt; is reactive. Flip it and the adapter calls the underlying controller's &lt;code&gt;enable()&lt;/code&gt;/&lt;code&gt;disable()&lt;/code&gt; for you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest trade-off versus a directive: two touch points instead of one. You name the parent &lt;em&gt;and&lt;/em&gt; place a tag, where Vue users write a single &lt;code&gt;v-auto-animate&lt;/code&gt;. What you get back is more than the ceremony costs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reactive enable/disable as a plain attribute.&lt;/strong&gt; In hook-based adapters, runtime toggling means capturing a second return value and calling methods on it. In directive-based ones it's awkward or impossible. In Marko it's an attribute bound to any reactive value. The demo drives it with a two-way-bound checkbox (&lt;code&gt;checked:=enabled&lt;/code&gt;) and nothing else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;let/enabled = true&amp;gt;
&amp;lt;input type="checkbox" checked:=enabled/&amp;gt;

&amp;lt;ul/listRef&amp;gt; ... &amp;lt;/ul&amp;gt;
&amp;lt;AutoAnimate parent=listRef enabled=enabled/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Auto-discovery.&lt;/strong&gt; The package ships a &lt;code&gt;marko.json&lt;/code&gt;, so Marko's taglib discovery exposes &lt;code&gt;&amp;lt;auto-animate&amp;gt;&lt;/code&gt; in your templates without an import statement. The explicit &lt;code&gt;import AutoAnimate from "@formkit/auto-animate/marko"&lt;/code&gt; form works too, and is what the demo uses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lifecycle correctness for free.&lt;/strong&gt; The tag's &lt;code&gt;&amp;lt;lifecycle&amp;gt;&lt;/code&gt; owns setup, the reactive &lt;code&gt;enabled&lt;/code&gt; diff, and teardown (&lt;code&gt;controller.destroy()&lt;/code&gt; when the tag unmounts). Unmount the tag, or the branch containing it, and cleanup happens. There's no effect-ordering to reason about.&lt;/p&gt;

&lt;p&gt;And if you don't want the tag at all, the core function is one import away:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;client import autoAnimate from "@formkit/auto-animate"

&amp;lt;div/panelRef&amp;gt; ... &amp;lt;/div&amp;gt;
&amp;lt;lifecycle onMount() { autoAnimate(panelRef()); }/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same escape hatch every other framework's docs show; now Marko has it too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does it survive resume? (And why that question is different here)
&lt;/h2&gt;

&lt;p&gt;Let's be precise about what's special and what isn't. Server-rendering auto-animate is not a Marko-only trick. React under Next and Vue under Nuxt server-render lists fine, and after hydration a &lt;code&gt;useEffect&lt;/code&gt;/&lt;code&gt;onMounted&lt;/code&gt; attaches the animation. Same outcome. Qwik, which also resumes rather than hydrates, has an adapter too.&lt;/p&gt;

&lt;p&gt;What's different in Marko is the &lt;em&gt;mechanism&lt;/em&gt;, and it changes how the adapter must be built. Marko never re-renders on the client. There is no hydration pass that reconstructs the component tree and hands you a fresh ref. The HTML the server printed &lt;em&gt;is&lt;/em&gt; the app, and Marko resumes it in place. So the adapter can't hang off "the render that happens on the client," because there isn't one. Instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; and its rows arrive as real server HTML.&lt;/li&gt;
&lt;li&gt;On resume, the tag's &lt;code&gt;onMount&lt;/code&gt; runs (a client-only lifecycle phase).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;input.parent()&lt;/code&gt; dereferences the tag-variable, which points at the server-printed DOM node.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;autoAnimate()&lt;/code&gt; attaches its &lt;code&gt;MutationObserver&lt;/code&gt; to that node.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The subtle failure mode this design avoids: the adapter never depends on a sibling effect having run first, because the parent is a real DOM node reachable through the ref the moment resume reaches the tag. Marko's leaf-first resume ordering, a genuine trap for other integration shapes, simply doesn't apply here.&lt;/p&gt;

&lt;p&gt;You can verify the whole story in ten seconds: open the &lt;a href="https://codesandbox.io/p/github/defunkt-dev/auto-animate-marko-demo/main" rel="noopener noreferrer"&gt;demo&lt;/a&gt;, view source, and find the &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; rows sitting in the server HTML. No client render produced them; the adapter attached to them anyway. There's a passing SSR test suite upstream asserting exactly this, including plain object options after resume and an inline-template plugin case. Runtime plugin functions passed through server input are a different case and should be treated as client-only.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full API in five routes
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://codesandbox.io/p/github/defunkt-dev/auto-animate-marko-demo/main" rel="noopener noreferrer"&gt;demo&lt;/a&gt; is structured so each route teaches one thing, and the five together are the complete surface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;/&lt;/strong&gt; covers add / remove / shuffle / reverse with the tag, plus the view-source SSR check.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;/options&lt;/strong&gt; covers &lt;code&gt;duration&lt;/code&gt; and &lt;code&gt;easing&lt;/code&gt;, shown as a side-by-side race: two identical lists mutated by the same click, one at 150ms, one at 1500ms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;/plugin&lt;/strong&gt; covers a custom plugin returning &lt;code&gt;KeyframeEffect&lt;/code&gt;s per action (&lt;code&gt;add&lt;/code&gt; / &lt;code&gt;remove&lt;/code&gt; / &lt;code&gt;remain&lt;/code&gt;) for a bounce-in, shrink-out, springy-slide feel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;/enabled&lt;/strong&gt; covers the reactive &lt;code&gt;enabled&lt;/code&gt; attribute on a checkbox.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;/dropdown&lt;/strong&gt; covers the raw-core escape hatch, no tag involved.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One perceptual finding from building /options is worth passing on, because you'll hit it too: &lt;strong&gt;at the same duration, removals look slow and additions look instant.&lt;/strong&gt; That's not a bug and not the adapter. It's the library's default keyframes. A removed row is kept in the DOM as an absolutely-positioned ghost and fades out visibly for the full duration while its siblings slide up. An entering row never moves: it's held &lt;em&gt;completely invisible for the first half&lt;/em&gt; of its animation, then fades in (at 1.5x your configured duration, per the core source). A delayed fade with no motion reads as a pop, however long it runs. If you want loud entrances, that's what the plugin API is for. The /plugin route's bounce-in is the proof.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we learned shipping it: a Vite 8 gotcha
&lt;/h2&gt;

&lt;p&gt;The adapter merged, 0.10.0 published, the &lt;code&gt;./marko&lt;/code&gt; export went live. Then the first fresh &lt;code&gt;npm install&lt;/code&gt; into a new &lt;code&gt;@marko/run&lt;/code&gt; app crashed the dev server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[UNRESOLVED_IMPORT] Could not resolve '../index' in
node_modules/@formkit/auto-animate/tags/auto-animate.marko
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cause is a packaging subtlety. In the source repo, the tag imports the core as &lt;code&gt;"../index"&lt;/code&gt;, extensionless, which resolves to &lt;code&gt;src/index.ts&lt;/code&gt; and passes every test. The release build copies the tag into the tarball next to the compiled &lt;code&gt;index.mjs&lt;/code&gt;, keeping the import as-is. Older tooling would guess the extension. Vite 8's rolldown-based dependency optimizer applies strict ESM resolution inside a &lt;code&gt;type: module&lt;/code&gt; package and refuses to guess. Boom.&lt;/p&gt;

&lt;p&gt;No spelling of that import works in both places. Using &lt;code&gt;../index.mjs&lt;/code&gt; would break the repo's tests, and &lt;code&gt;../index.ts&lt;/code&gt; would break consumers. So the package-side fix needs to make the published .marko file resolvable under Vite 8’s dependency optimizer. Rewriting the import during the release copy step is one possible fix; excluding the package from optimizeDeps is the current consumer workaround. &lt;/p&gt;

&lt;p&gt;Until that lands upstream, the workaround is one config entry, which also reflects a general truth about &lt;code&gt;.marko&lt;/code&gt; taglib packages (they should be compiled by the Marko plugin, not prebundled):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vite.config.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;marko&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="na"&gt;optimizeDeps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@formkit/auto-animate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The demo ships with this and documents it. It's also a neat illustration of why "the tests pass" and "the published package works" are different claims: the upstream test suites import the tag from source, so no CI run ever exercises the installed tarball under a consumer's bundler. The only way to catch this class of bug is to do what you'd tell a user to do. Install the real release into a fresh app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Install: &lt;code&gt;npm install @formkit/auto-animate&lt;/code&gt; (0.10.0+)&lt;/li&gt;
&lt;li&gt;Import: &lt;code&gt;@formkit/auto-animate/marko&lt;/code&gt;, or use &lt;code&gt;&amp;lt;auto-animate&amp;gt;&lt;/code&gt; via taglib auto-discovery&lt;/li&gt;
&lt;li&gt;Demo: &lt;a href="https://codesandbox.io/p/github/defunkt-dev/auto-animate-marko-demo/main" rel="noopener noreferrer"&gt;https://codesandbox.io/p/github/defunkt-dev/auto-animate-marko-demo/main&lt;/a&gt; · source: &lt;a href="https://github.com/defunkt-dev/auto-animate-marko-demo" rel="noopener noreferrer"&gt;https://github.com/defunkt-dev/auto-animate-marko-demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The adapter PR: &lt;a href="https://github.com/formkit/auto-animate/pull/239" rel="noopener noreferrer"&gt;formkit/auto-animate#239&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you ship something with it, or hit an edge the five routes don't cover, I'd like to hear about it.&lt;/p&gt;

&lt;p&gt;by &lt;strong&gt;&lt;em&gt;defunkt-dev&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>frontend</category>
    </item>
  </channel>
</rss>
