<?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>Forem: Alex M</title>
    <description>The latest articles on Forem by Alex M (@foxeyes).</description>
    <link>https://forem.com/foxeyes</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F427682%2F394a3b73-2fee-4a07-a4b2-68ae0e65d068.png</url>
      <title>Forem: Alex M</title>
      <link>https://forem.com/foxeyes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/foxeyes"/>
    <language>en</language>
    <item>
      <title>Symbiote.js: superpowers for Web Components</title>
      <dc:creator>Alex M</dc:creator>
      <pubDate>Sun, 19 Apr 2026 16:37:41 +0000</pubDate>
      <link>https://forem.com/foxeyes/symbiotejs-superpowers-for-web-components-1gid</link>
      <guid>https://forem.com/foxeyes/symbiotejs-superpowers-for-web-components-1gid</guid>
      <description>&lt;p&gt;The whole point of creating and adopting new libraries and frameworks is to solve problems that haven't been solved before — or to solve them more efficiently than existing solutions do.&lt;/p&gt;

&lt;p&gt;Today we'll start a conversation about the kind of problems &lt;a href="https://github.com/symbiotejs/symbiote.js" rel="noopener noreferrer"&gt;Symbiote.js&lt;/a&gt; can solve — and do it far more simply and elegantly than other frameworks.&lt;/p&gt;

&lt;p&gt;Symbiote.js is a lightweight (~6 KB brotli) yet very powerful library built on Web Components. What sets it apart from the competition is its focus on advanced compositional capabilities within HTML markup (both general and template-based) and a more flexible approach to data contexts.&lt;/p&gt;

&lt;p&gt;Symbiote.js is a self-contained solution for building complex modern interfaces. You don't need to assemble the classic sandwich of a global state manager, router, and SSR — it's all built in. At the same time you get maximum creative freedom, with no mandatory dependency on compilers, bundlers, or any closed ecosystem. All of this comes with minimal boilerplate, types, reactivity, a runtime debugger, and everything else useful and modern that we've all grown accustomed to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Composition
&lt;/h2&gt;

&lt;p&gt;Working with interfaces can be roughly divided into two parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Logical — component model and logic + data abstractions&lt;/li&gt;
&lt;li&gt;Structural — composing components and data flows&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Symbiote.js has its own unique strengths in both areas. But right now I'd like to focus specifically on the second part.&lt;/p&gt;

&lt;p&gt;The library is built around HTML. Component templates are standalone HTML strings. External HTML serves as a full-fledged scaffold and a way to define structural dependencies. There is fundamentally no tight coupling to a JS runtime. This lets you manipulate your interface elements at a compositional and declarative level with great flexibility — both on the client and on the server.&lt;/p&gt;

&lt;p&gt;You can hydrate pre-rendered markup, render templates from scratch, or use any hybrid approach. You can create "pure" context-provider components and "dumb" components with no logic of their own that automatically connect to a DOM-structure-based or a fully abstract data context. You can use a single component with completely different custom templates without a single extra line of JS — which is especially useful for embeddable solutions, where a declarative approach to configuration is particularly convenient.&lt;/p&gt;

&lt;p&gt;Let's move on to examples.&lt;/p&gt;

&lt;p&gt;Let's build something straightforward and practical — reusable tabs for our interface.&lt;/p&gt;

&lt;p&gt;Our solution consists of two parts: a tab switcher and a view container that displays the selected content.&lt;/p&gt;

&lt;p&gt;Step one:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Symbiote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@symbiotejs/symbiote&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SuperTabs&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Symbiote&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nx"&gt;init$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*currentTabName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;first&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="nf"&gt;renderCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabEls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[tab]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabEls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="cm"&gt;/** @type {HTMLElement} */&lt;/span&gt; &lt;span class="nx"&gt;el&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;tab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tab&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;current&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*currentTabName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onclick&lt;/span&gt; &lt;span class="o"&gt;=&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*currentTabName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tab&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;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*currentTabName&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="nx"&gt;val&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabEls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="cm"&gt;/** @type {HTMLElement} */&lt;/span&gt; &lt;span class="nx"&gt;el&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tab&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;current&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;current&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;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;SuperTabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rootStyles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt;&lt;span class="s2"&gt;`
super-tabs {
  display: inline-flex;
  gap: 2px;
  [tab] {
    cursor: pointer;
    &amp;amp;[current] {
      background-color: transparent;
      pointer-events: none;
    }
  }
}
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;SuperTabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;super-tabs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step two:&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;class&lt;/span&gt; &lt;span class="nc"&gt;SuperTabsView&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Symbiote&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;renderCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabCtxEls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[tab-ctx]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*currentTabName&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="nx"&gt;val&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabCtxEls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="cm"&gt;/** @type {HTMLElement} */&lt;/span&gt; &lt;span class="nx"&gt;el&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tab-ctx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&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;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;SuperTabsView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rootStyles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt;&lt;span class="s2"&gt;`
super-tabs-view {
  display: block;
  [tab-ctx] {
    display: none;
    &amp;amp;[active] {
      display: contents;
    }
  }
}
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;SuperTabsView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;super-tabs-view&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our tabs are ready and will work seamlessly alongside any other framework — or entirely on their own:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;super-tabs&lt;/span&gt; &lt;span class="na"&gt;ctx=&lt;/span&gt;&lt;span class="s"&gt;"section-select"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;tab=&lt;/span&gt;&lt;span class="s"&gt;"first"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;First&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;tab=&lt;/span&gt;&lt;span class="s"&gt;"second"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Second&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;tab=&lt;/span&gt;&lt;span class="s"&gt;"third"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Third&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/super-tabs&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;super-tabs-view&lt;/span&gt; &lt;span class="na"&gt;ctx=&lt;/span&gt;&lt;span class="s"&gt;"section-select"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;tab-ctx=&lt;/span&gt;&lt;span class="s"&gt;"first"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;First content&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;tab-ctx=&lt;/span&gt;&lt;span class="s"&gt;"second"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Second content&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;tab-ctx=&lt;/span&gt;&lt;span class="s"&gt;"third"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Third content&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/super-tabs-view&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's worth noting in these code examples?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;rootStyles&lt;/code&gt; interface — in this case, components are created without their own Shadow DOM by default. But they can live inside an external Shadow DOM that may exist somewhere up the tree. Or there may be none at all. Either way, styles are applied correctly. Any Tailwinds, Bootstraps, or shared document styles you're using will work just as expected.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;renderCallback&lt;/code&gt; lifecycle hook — it guarantees access to the component's child DOM nodes (the standard &lt;code&gt;connectedCallback&lt;/code&gt; does not provide this guarantee).&lt;/li&gt;
&lt;li&gt;Property initialization with &lt;code&gt;init$&lt;/code&gt; — here we initialize a property and set its default value (the active tab name).&lt;/li&gt;
&lt;li&gt;Property subscription via the &lt;code&gt;sub()&lt;/code&gt; method — the fundamental pattern for working with reactive properties: straightforward Pub/Sub. Subscriptions (and unsubscriptions) and value publishing can happen either fully automatically or explicitly, as shown in the example.&lt;/li&gt;
&lt;li&gt;Property definition via &lt;code&gt;*propName&lt;/code&gt; — declaring a property for the Shared Context. This is no longer a component's own property; it's shared among all components that have the &lt;code&gt;ctx&lt;/code&gt; attribute explicitly set. This works similarly to, for example, the native browser element &lt;code&gt;&amp;lt;input type="radio"&amp;gt;&lt;/code&gt; and its &lt;code&gt;name&lt;/code&gt; attribute.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Also, as you can see, Symbiote.js works perfectly with standard DOM API methods. And unlike many other frameworks, this is absolutely NOT an anti-pattern here — because there is no Virtual DOM.&lt;/p&gt;

&lt;p&gt;Working with the other approach — where you do NOT interact with the DOM directly — is equally easy and convenient. For example, let's create a "dumb" component that simply displays the current tab context value:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Symbiote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@symbiotejs/symbiote&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SuperCurrent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Symbiote&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;SuperCurrent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rootStyles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt;&lt;span class="s2"&gt;`
super-current {
  display: block;
  h2 {
    text-transform: capitalize;
  }
}
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;SuperCurrent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;h2&amp;gt;{{*currentTabName}}&amp;lt;/h2&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;SuperCurrent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;super-current&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage in markup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;super-current&lt;/span&gt; &lt;span class="na"&gt;ctx=&lt;/span&gt;&lt;span class="s"&gt;"section-select"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/super-current&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all it takes — minimal code, and you've linked independent components into a single interacting group. And you can have as many of these components and groups on the page as you need. Naturally, you can bind event handlers and far more complex data structures the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Low-code / no-js
&lt;/h2&gt;

&lt;p&gt;Where is all of this &lt;strong&gt;especially&lt;/strong&gt; useful?&lt;/p&gt;

&lt;p&gt;Imagine you've built a library of such atomic components. From there, someone with no deep JavaScript knowledge can assemble interfaces using nothing but HTML markup. Include a script, place the tags with the right attributes — done. No bundler, no framework to keep in mind — just structure and meaning.&lt;/p&gt;

&lt;p&gt;Teams often consist of various specialists: designers, analysts, marketers, SEO experts... This way, we create a universally accessible technical communication protocol, enabling a wide range of people to contribute without having to dive into the depths of actual programming or pulling developers in for every small change.&lt;/p&gt;

&lt;p&gt;This opens doors for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Designers&lt;/strong&gt; — prototyping interfaces directly in HTML&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content managers&lt;/strong&gt; — configuring content display through attributes, no code required&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embeddable solutions&lt;/strong&gt; — complex widgets that anyone can customize via HTML without digging into the source code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI assistants&lt;/strong&gt; — generating and modifying interfaces on the fly, where a simple and predictable relationship between markup and behavior is critically important&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;The mechanics described in this article are FAR from everything Symbiote.js can do. I deliberately chose not to overload the material and I'm planning a whole series of similar publications with examples and real-world use cases. On your side, you can run an interesting experiment: ask an AI to provide a solution to a similar problem in any other framework you're interested in, and compare the volume and complexity of the resulting code, bundle size, number of dependencies, and so on. I assure you — the results will make you take a much closer look at Symbiote.js.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>R&amp;D: The Art of Managing Uncertainty in Software Development</title>
      <dc:creator>Alex M</dc:creator>
      <pubDate>Thu, 16 Apr 2026 14:23:15 +0000</pubDate>
      <link>https://forem.com/foxeyes/rd-the-art-of-managing-uncertainty-in-software-development-1mee</link>
      <guid>https://forem.com/foxeyes/rd-the-art-of-managing-uncertainty-in-software-development-1mee</guid>
      <description>&lt;p&gt;Here are two facts for you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;R&amp;amp;D is the only path to real innovation - the kind that creates competitive advantages and, potentially, reshapes or even creates entirely new markets&lt;/li&gt;
&lt;li&gt;Most managers and business stakeholders are terrified of R&amp;amp;D tasks, and these tasks often get solved "despite" the process, not "because of" it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's figure out why this happens and what to do about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preamble
&lt;/h2&gt;

&lt;p&gt;I've wanted to share my thoughts on this topic for years, but never got around to it. I've seen it too many times: tasks &lt;strong&gt;with many unknowns&lt;/strong&gt; treated no differently from the rest of the backlog, with the same cookie-cutter formal approaches and the same production metrics applied across the board. And it ALWAYS caused problems. At best - difficulties with planning and realistic deadline estimation. At worst - failed projects and entire teams burning out. I'm guessing many of you can relate.&lt;/p&gt;

&lt;p&gt;The brave soul who takes on an R&amp;amp;D task faces a whole extra set of obstacles: from their own mental traps to a complete lack of understanding of what research work actually involves - from colleagues and management alike. On the other hand, it's exactly these kinds of tasks that make our work genuinely exciting.&lt;/p&gt;

&lt;p&gt;The first step toward solving many problems, including the ones mentioned above, is &lt;strong&gt;acknowledging&lt;/strong&gt; them. Regardless of your title, you need to be able to distinguish R&amp;amp;D from everything else. Hiding from uncertainty behind formalism only delays the painful collision with reality.&lt;/p&gt;

&lt;p&gt;Throughout this article, I'll be drawing on personal experience, so expect a few true stories.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is R&amp;amp;D?
&lt;/h2&gt;

&lt;p&gt;Research And Development - literally what it says on the tin. Development is the part that seems relatively predictable. Research is the space of uncertainties and the previously unknown. R&amp;amp;D tasks vary wildly in complexity and importance. The domain of our ignorance can extend in many directions - not necessarily only the technical one.&lt;/p&gt;

&lt;p&gt;For example, I once worked on a telemedicine service, and most of the unknowns we had to deal with came from the legal and regulatory domain. The legal part was the key challenge and critically important. From a technical standpoint, the project was relatively straightforward from the start. The project never saw the light of day - it simply couldn't outlast the wait for a telemedicine law that it was being built for. The government insiders let us down with their insider knowledge.&lt;/p&gt;

&lt;p&gt;Another example where the uncertainty was purely technical. We were building a virtual clothing try-on service. When I joined the project, there was already a semi-functional prototype that could load clothing models and simulate fabric behavior on a virtual 3D mannequin matched to the potential user's measurements.&lt;/p&gt;

&lt;p&gt;The unknown was whether the heavy simulation process could be scaled at all. Fabric physics is complex stuff, and the prototype could barely run on a dedicated GPU. It seemed impossible to make it work in milliseconds per request. Figuring out HOW to do that - that's R&amp;amp;D.&lt;/p&gt;

&lt;p&gt;Back then, nobody had really heard of neural networks yet, so we built an interpolation engine that could produce a plausible-looking image based on several pre-computed simulations, simply shifting vertices (polygon points) toward the nearest calculated values. That project, by the way, also didn't survive - for the very reason I'm trying to describe here: the people making key business decisions didn't understand what they were dealing with or what risks they should be accounting for. By the time we had a truly working concept, it was already too late.&lt;/p&gt;

&lt;p&gt;This is how people throw millions out the window. Sometimes it's hard to stop when the investment has already passed a critical threshold. You desperately want to recoup what you've spent, and the only way to do that, it seems, is to invest just a little more. This dangerous mental trap is a direct consequence of not having a separate, systematic approach to R&amp;amp;D.&lt;/p&gt;

&lt;p&gt;So what do we do?&lt;/p&gt;

&lt;h2&gt;
  
  
  Managed Chaos
&lt;/h2&gt;

&lt;p&gt;Surprisingly, many people dislike the word "Chaos," treating it as some synonym for disorder. However, chaos as a mathematical concept is an important characteristic of what inevitably surrounds us, whether we like it or not. Any small thing can trigger serious consequences. The states of complex systems are difficult (impossible) to reliably predict over long time horizons.&lt;/p&gt;

&lt;p&gt;At the same time, the field of possible variants and states tends toward a certain common Attractor. To put it simply: we can be wildly wrong about tomorrow's weather, but we always know that winter is generally cold and summer is generally warm (in the corresponding hemisphere). This kind of knowledge, applied to development, comes with experience. An experienced engineer may not be able to name an exact date, but they can feel the "temperature corridor" of a project - where the gravitational center of risk lies, where the zone of relative stability is. They also recognize that the illusion of control through following rituals consumes resources beautifully while guaranteeing, essentially, nothing.&lt;/p&gt;

&lt;p&gt;When it comes to R&amp;amp;D, this is especially relevant. The way forward is to learn to read signals from the real world and apply chaos engineering practices - where the primary data source for analysis is the working system itself, at any stage of its lifecycle. Here we simply apply the scientific method and get rid of "shamanic" practices. The problem is that we're surrounded by plenty of "shamans" who don't appreciate this.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Focus Trap
&lt;/h2&gt;

&lt;p&gt;Focusing on one thing rather than jumping between different aspects of a larger task sounds, at first glance, reasonable. Predictable, controllable... Your average manager likes that. The problem is that it often leads to perfecting one node or aspect of a fundamentally unviable model.&lt;/p&gt;

&lt;p&gt;This is essentially the "local optimization" problem: you're polishing one component to a mirror finish without noticing that the entire mechanism is flawed. In conventional development, you might get away with it - at the cost of a refactor. In R&amp;amp;D, the price is different: you can spend months perfecting a component that turns out to be useless after its first contact with reality.&lt;/p&gt;

&lt;p&gt;In R&amp;amp;D, it's crucial to periodically step back from narrow or intermediate goals, forming and refining the big picture before diving back into the details. Focusing on small tasks gives a pleasant feeling of progress, but be careful not to fall into this trap - celebrating progress on a road to nowhere.&lt;/p&gt;

&lt;p&gt;Here's another example. I worked on a platform for finding and selling auto parts. The complexity was that searches had to run across enormous databases of part numbers, including all possible analogs. You had to find matches across car makes and models, production years, VIN numbers, manufacturer data, supplier and warehouse data - practically in real time. Making this search fast was a non-trivial task, especially considering we were assembling the data from various sources, practically from scratch. One of our microservices was a custom VIN decoding API. We spent a significant amount of time on this task - it turned out to be far less trivial than it looked at first glance. In the end, the project was cancelled and all the work went into the drawer. Our grand plans never even received a fair assessment of feasibility - we simply ran out of time. But while we were grinding away at that VIN decoder, it felt like we were making great progress.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plan B
&lt;/h2&gt;

&lt;p&gt;It's important to understand that in R&amp;amp;D, a negative result is still a valid result. Sometimes a task hits a previously unknown factor and turns out to be simply unsolvable within the given constraints. If your entire project depends on it, you should ideally think about a "Plan B" while still defining the initial conditions.&lt;/p&gt;

&lt;p&gt;Think about what assets you're acquiring along the way and how they might recoup costs, fully or at least partially. Think about possible pivot points. This should be done both upfront and periodically throughout the work. Think in advance about strategies for confronting risks. Many failures can be turned into wins if you think in the right frame.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A good R&amp;amp;D process generates value even on the path to failure.&lt;/strong&gt; Libraries you've written. Datasets you've collected. Expertise the team has acquired. Patents. Articles. Open-source components. All of these are side assets that can be monetized or reused if the main hypothesis doesn't hold up.&lt;/p&gt;

&lt;p&gt;A key practice here is &lt;strong&gt;defining kill-criteria in advance&lt;/strong&gt;. Before work even begins, formulate specific, measurable conditions under which you stop the project. This isn't pessimism - it's professionalism. Without predefined boundaries, it's easy to fall victim to sunk cost fallacy, which was mentioned above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fresh Eyes
&lt;/h2&gt;

&lt;p&gt;Sometimes people who've been stewing in a big project for too long lose the ability to see it clearly from the outside. The effects of mental traps and self-compromises made for psychological comfort tend to accumulate over time. Don't assume this is a sign of someone's incompetence. We're all (still) human, and human behavior is inherent to all of us - even the most brilliant among us.&lt;/p&gt;

&lt;p&gt;That's why a fresh perspective is so important in projects with a high R&amp;amp;D component. Sometimes it's useful to criticize yourself, retrospectively evaluate your decisions, and be willing to reconsider them. Sometimes it's important to hear an outside opinion. Such opinions may seem amateurish and superficial, but often they reveal exactly how things look from outside your cozy process - the one you undoubtedly understand better than anyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Start-Pause-Stop System
&lt;/h2&gt;

&lt;p&gt;One of the practices used for managing uncertainty. Each new step may depend on the results of the previous one. Sometimes the entire process can hang for a long time waiting for some data. Sometimes work should be stopped entirely and the team should switch to something else. Your processes need to be adapted to three basic states. Pausing the main R&amp;amp;D branch automatically pauses all subtasks. Stopping triggers a preservation algorithm (for further reuse of artifacts, for example in another project, or for possible revival and continuation of work at some undefined point in the future). All process participants must be notified in time about changes in global status and be ready to switch as painlessly as possible. This approach helps significantly save resources, makes the process more transparent and manageable for business, and preserves the work done.&lt;/p&gt;

&lt;h2&gt;
  
  
  R&amp;amp;D Metrics
&lt;/h2&gt;

&lt;p&gt;Classic development metrics - velocity, story points, burndown charts - work poorly in R&amp;amp;D. They measure speed of movement, but not its direction. You can "burn" story points at cosmic speed while heading in the wrong direction.&lt;/p&gt;

&lt;p&gt;For R&amp;amp;D, other indicators are more informative:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Number of hypotheses validated&lt;/strong&gt; per unit of time - shows real research velocity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost of validating a single hypothesis&lt;/strong&gt; - helps optimize the experimentation process&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time to first contact with reality&lt;/strong&gt; - the sooner a prototype hits a real environment, the better&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ratio of reusable artifacts to total work volume&lt;/strong&gt; - shows how much value you're generating "along the way"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These metrics don't give an illusion of control - they give real feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Team Requirements
&lt;/h2&gt;

&lt;p&gt;One important consideration is the elevated requirements for team members working in R&amp;amp;D. Unfortunately, this type of work doesn't suit those who haven't yet had the chance to learn the hard way on real projects. Juniors can be valuable in routine development with a clear spec, but R&amp;amp;D demands something different: the ability to work in conditions where the spec is something you must formulate yourself as you go. You can try running such a project with juniors, mid-levels, or offshore contractors, but there's a high probability the result will be painful. R&amp;amp;D demands a high degree of self-organization and SELF-discipline from the team, and those only come with experience. At the same time, the team needs a creative atmosphere, because the best ideas will come from people who are genuinely INTERESTED. This process is practically impossible to manage effectively through micromanagement and pre-defined formal methodologies. This creates a paradox: cutting costs on people will end up costing you more. At the same time, an R&amp;amp;D team should be compact, so that communication overhead doesn't consume all your resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI
&lt;/h2&gt;

&lt;p&gt;Neural networks are excellent for automating routine work and conducting quick intermediate micro-research. They're also extremely useful for creating side artifacts: documentation, demos, tests, etc.&lt;/p&gt;

&lt;p&gt;But there's a nuance. AI works great in the zone of the &lt;strong&gt;known&lt;/strong&gt; - it generalizes patterns from already existing solutions. R&amp;amp;D, by definition, operates in the zone of the &lt;strong&gt;unknown&lt;/strong&gt;. AI is not (yet) capable of generating truly new ideas - it can only combine existing ones within a limited range. This makes it a powerful accelerator for an experienced R&amp;amp;D specialist who knows which questions to ask. But a poor substitute for such a specialist.&lt;/p&gt;

&lt;p&gt;Fully replacing R&amp;amp;D specialists is not yet possible. But AI has already become an excellent tool in the hands of an experienced researcher - and the significance of this tool continues to grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crisis-Mode R&amp;amp;D
&lt;/h2&gt;

&lt;p&gt;Consider a situation where R&amp;amp;D becomes not the source of a crisis, but the answer to one. When the market collapses, a technology becomes obsolete, or an unexpected competitor arrives - it's exactly R&amp;amp;D thinking that enables rapid adaptation.&lt;/p&gt;

&lt;p&gt;Crisis-mode R&amp;amp;D differs from planned R&amp;amp;D in three ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Speed over quality.&lt;/strong&gt; You don't need a perfect product - you need a working prototype that proves the viability of a new model. MVP in the literal sense.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resources are limited.&lt;/strong&gt; Crisis typically means a slashed budget. Paradoxically, this sometimes helps: constraints force more inventive solutions and cut through all the noise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High risk tolerance.&lt;/strong&gt; When there's almost nothing left to lose, space opens up for experiments that would be unthinkable in "peacetime."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In essence, a well-established R&amp;amp;D process is your insurance against crises. A team accustomed to working with uncertainty doesn't freeze up when that uncertainty shows up uninvited. They already know how to ask the right questions, build hypotheses, and validate them quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;R&amp;amp;D is not an anomaly in the production process that needs to be "survived." It's a natural and inevitable part of it, especially in industries where the technological landscape changes faster than you can update your documentation. Trying to squeeze research tasks into the framework of a linear production pipeline is one of the most expensive mistakes a business can make.&lt;/p&gt;

&lt;p&gt;Key principles to remember:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Acknowledge&lt;/strong&gt; R&amp;amp;D tasks as a separate category - don't hide behind generic processes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accept&lt;/strong&gt; uncertainty as a working factor, not a hostile anomaly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invest&lt;/strong&gt; in people - R&amp;amp;D is done by people, not processes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prepare&lt;/strong&gt; a Plan B and define kill-criteria in advance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure&lt;/strong&gt; real progress, not the illusion of progress&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use&lt;/strong&gt; AI as an accelerator, not as a substitute for thinking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And above all: R&amp;amp;D is about &lt;strong&gt;managed&lt;/strong&gt; risk. And the ability to manage risk is what separates engineering from gambling.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>rnd</category>
      <category>management</category>
      <category>programming</category>
    </item>
    <item>
      <title>What's Actually Wrong with Web Components?</title>
      <dc:creator>Alex M</dc:creator>
      <pubDate>Tue, 14 Apr 2026 20:05:49 +0000</pubDate>
      <link>https://forem.com/foxeyes/whats-actually-wrong-with-web-components-3pjk</link>
      <guid>https://forem.com/foxeyes/whats-actually-wrong-with-web-components-3pjk</guid>
      <description>&lt;p&gt;Short answer -- nothing fundamental. Web Components are a set of browser-native APIs that solve real problems in web development. They let you do things that are either impossible or unnecessarily complex without them. And when a task goes slightly beyond standard requirements and needs a creative approach, Custom Elements give you a level of flexibility that framework-specific abstractions simply don't offer.&lt;/p&gt;

&lt;p&gt;Yet somehow, the narrative online keeps cycling back to "Web Components are a failure". I've been using these APIs in production for years -- starting from the early Polymer drafts, through the spec evolution, and up to modern Lit and my own library &lt;a href="https://github.com/symbiotejs/symbiote.js" rel="noopener noreferrer"&gt;Symbiote.js&lt;/a&gt;. Over that time I've shipped large real-time dashboards with graph data flowing through WebSockets, collections of hundreds of thousands of items, and plenty of simpler projects. So I have opinions, and they come from practice.&lt;/p&gt;

&lt;p&gt;One thing before we start: every engineering decision is a trade-off. There are no perfect technologies (if someone claims otherwise, they're selling something). When evaluating any tool, we weigh pros and cons. The existence of cons doesn't automatically invalidate the pros. Each trade-off has a concrete cost. If that cost is acceptable for your case -- the technology deserves serious consideration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Myths That Won't Die
&lt;/h2&gt;

&lt;p&gt;Some criticisms of Web Components are simply inaccurate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"You can only pass strings to Web Components."&lt;/strong&gt; This one references &lt;code&gt;attributeChangedCallback&lt;/code&gt; and suggests JSON-parsing attribute values. But HTML attributes are part of HTML markup -- they're always strings, for any element, not just custom ones. If you need to pass complex data to a component at runtime, use property setters, getters, or Proxies. Standard JS. Every modern framework can set properties on DOM elements through their template syntax, and plain DOM API works just as well. Confusing attributes (HTML serialization) with properties (JS runtime) is a fundamental misunderstanding of how the browser works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Shadow DOM isolation is a problem."&lt;/strong&gt; Shadow DOM isolates a component's internals from external CSS. That's its purpose. If you don't need isolation -- don't use Shadow DOM. Custom Elements and Shadow DOM are independent APIs. You can use Custom Elements with Light DOM just fine, and many production component libraries do exactly that. When you do want Shadow DOM, you still have CSS custom properties (design tokens) and &lt;code&gt;::part()&lt;/code&gt; for controlled styling from outside. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptedStyleSheets" rel="noopener noreferrer"&gt;Adopted Style Sheets&lt;/a&gt; handle shared styles across instances without duplication -- supported in all modern browsers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"The lifecycle starts at DOM attachment."&lt;/strong&gt; It doesn't. A Custom Element's lifecycle starts at the constructor. You can create elements in memory, manipulate them within a DocumentFragment through standard DOM API, and only attach them when ready. This is fundamental to virtualization patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nuances Worth Understanding
&lt;/h2&gt;

&lt;p&gt;These aren't dealbreakers, but they're real and worth knowing about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Global name registry.&lt;/strong&gt; Custom element names share a single flat namespace per document. This is true. The fix is simple: use prefixes (or postfixes) in naming conventions. If a collision happens, the browser throws a clear error at registration time, not a silent failure. You can catch it in integration tests. You can subclass and re-register. Flat namespaces are one of the oldest patterns in computing -- from object keys to domain names. It works fine with minimal discipline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;connectedCallback&lt;/code&gt; / &lt;code&gt;disconnectedCallback&lt;/code&gt; re-firing.&lt;/strong&gt; Moving an element from one DOM location to another triggers both callbacks. This is by design -- the element literally disconnects and reconnects. A single boolean flag on first initialization handles this. It's a one-line solution for a rarely-encountered scenario. The ability to track DOM lifecycle from inside the component is one of the most powerful features of the standard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No de-registration.&lt;/strong&gt; Once registered, a custom element stays registered for the life of the document. True. The browser stores a reference to the constructor. Memory cost is negligible, performance impact is zero. De-registration would actually create harder problems: what happens to existing instances? Which class is active? The current behavior is predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Gotcha
&lt;/h2&gt;

&lt;p&gt;There is one aspect that genuinely surprises newcomers. When the browser parses HTML in a streaming fashion, &lt;code&gt;connectedCallback&lt;/code&gt; fires as soon as it encounters the opening tag -- before the element's children are parsed. DOM queries for child elements return nothing at that point.&lt;/p&gt;

&lt;p&gt;The fix: load your script with &lt;code&gt;defer&lt;/code&gt; or &lt;code&gt;type="module"&lt;/code&gt;. Both ensure execution after parsing completes. In &lt;a href="https://github.com/symbiotejs/symbiote.js" rel="noopener noreferrer"&gt;Symbiote.js&lt;/a&gt;, there's a dedicated &lt;code&gt;renderCallback&lt;/code&gt; lifecycle hook that guarantees child DOM access.&lt;/p&gt;

&lt;p&gt;The inverse scenario exists too: you might get a reference to an element before its class is registered. &lt;code&gt;customElements.whenDefined(name)&lt;/code&gt; returns a Promise for exactly this case.&lt;/p&gt;

&lt;p&gt;This async behavior can be confusing if you're not aware of it. But it's the same category as losing &lt;code&gt;this&lt;/code&gt; context in callbacks -- once you know the pattern, you handle it automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Tooling and Core Standards
&lt;/h2&gt;

&lt;p&gt;Here's an angle that rarely comes up in Web Components discussions. Modern AI coding assistants -- LLMs, autocomplete models, chat-based agents -- have been trained on massive amounts of web platform documentation. The DOM API, Custom Elements, Shadow DOM, HTML templates, CSS custom properties: these are extensively documented on MDN, in W3C specs, and across millions of tutorials and StackOverflow answers.&lt;/p&gt;

&lt;p&gt;When you write a Custom Element using standard APIs, AI tools understand what you're doing. They can generate &lt;code&gt;connectedCallback&lt;/code&gt; logic, suggest attribute observation patterns, autocomplete CSS custom property usage. The entire surface area of these APIs is well-represented in training data.&lt;/p&gt;

&lt;p&gt;Framework-specific abstractions accumulate across versions. React Hooks have different idioms than class components. Angular standalone components differ from NgModule-based patterns. Vue 2 Options API vs. Vue 3 Composition API. Each version shift creates a window where AI models produce outdated or mixed-version code.&lt;/p&gt;

&lt;p&gt;Web Components API has been stable. &lt;code&gt;connectedCallback&lt;/code&gt; works today exactly as it did years ago. That stability maps directly to reliable AI assistance.&lt;/p&gt;

&lt;h2&gt;
  
  
  SEO and Shadow DOM
&lt;/h2&gt;

&lt;p&gt;Search engines have improved their ability to process JavaScript-rendered content, but there's still a practical distinction. Content in Light DOM is part of the main document tree -- crawlers parse it the same way they parse any HTML. Content inside Shadow DOM sits in a separate tree.&lt;/p&gt;

&lt;p&gt;Google's crawler can execute JavaScript and reach into Shadow DOM in many cases, but other search engines and social media parsers (OpenGraph scrapers, link previews) often cannot. If your page content, headings, structured data or link anchors live inside Shadow DOM, you're limiting their visibility.&lt;/p&gt;

&lt;p&gt;The practical approach: use Custom Elements without Shadow DOM for content-bearing components or place such content in Light DOM slots. Keep your semantic HTML, headings and paragraphs in Light DOM where every crawler can reach them. Reserve Shadow DOM for UI widgets that don't carry indexable content -- toolbars, media players, interactive controls. &lt;/p&gt;

&lt;p&gt;This is a real architectural advantage. You get component encapsulation at the JS level (Custom Elements), semantic HTML for crawlers and accessibility tools, and styling flexibility through the cascade -- all without a build step that transforms your markup beyond recognition.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Only Web Components Can Do
&lt;/h2&gt;

&lt;p&gt;Enough about trade-offs. What are the capabilities that no framework can replicate?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTML-to-runtime binding at the node level.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Frontend developers tend to forget that HTML is the foundation. A page starts as a text document, often assembled server-side, where no JS abstractions exist yet. Custom tags in that document are native markers for your JS integration. They provide natural mounting points and an XML-compatible structure that works with standard HTML -- no proprietary template syntax, no vendor-specific compilation step. A server written in any language can emit &lt;code&gt;&amp;lt;my-widget data-id="42"&amp;gt;&amp;lt;/my-widget&amp;gt;&lt;/code&gt; without knowing anything about your frontend stack. Try doing that with a React component.&lt;/p&gt;

&lt;p&gt;For example, this native binding radically simplifies SSR (Server-Side Rendering). Look at the architectural overhead required to hydrate a Next.js or Nuxt application: serialization boundaries, matching virtual DOM trees, and shipping a heavy hydration engine. Compare that to the &lt;a href="https://github.com/symbiotejs/symbiote.js/blob/main/docs/ssr.md" rel="noopener noreferrer"&gt;Symbiote.js SSR approach&lt;/a&gt;: the server simply produces standard HTML with custom tags and declarative attributes. When it reaches the browser, the Custom Element initializes, detects the pre-rendered children, and attaches its logic directly to the existing DOM nodes. There is no virtual tree to reconcile -- the HTML structure itself tells the runtime exactly what to do. No mismatch errors by design, no extra bundle size.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Atomic lifecycle management.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Custom Elements let you know, from inside the component, when it enters or leaves the DOM. Component appeared -- you know. Component removed -- you know. Regardless of what created it or what triggered a re-render.&lt;/p&gt;

&lt;p&gt;Without Custom Elements, lifecycle management is always external. You need a runtime to search the DOM for integration points, decide when to initialize, and decide when to clean up. That runtime has to be loaded, parsed, and executed before any component becomes alive. In React, this is ReactDOM scanning for a root element and owning the entire subtree. In Angular, it's the platform bootstrap and zone.js. These aren't free -- they add tens of kilobytes of code just to answer the question "when does this piece of UI exist?"&lt;/p&gt;

&lt;p&gt;Custom Elements answer that question natively. &lt;code&gt;connectedCallback&lt;/code&gt; fires when the element enters any document -- a React app, a static HTML page, a server-rendered document, an email template rendered in an iframe. No library runtime required. No teardown logic to wire up from outside. The browser tells the component directly, and the component handles itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A universal component model.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Custom Elements are the base layer that everything else can build on. Libraries like Lit or Symbiote.js add convenience (reactive state, templating, etc). UI kits build design systems. Complex widgets encapsulate business logic. Micro-frontends compose applications from independent deployables. All of these benefit from a standardized component API with built-in documentation on MDN, universal browser support, and zero lock-in. A Custom Element works inside React, Angular, Vue, Svelte, plain HTML pages -- or alongside other Custom Elements. No wrapping, no adapters, no compatibility layers for the most cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unmet Expectations
&lt;/h2&gt;

&lt;p&gt;A lot of frustration with Web Components comes from hoping they'd be a native, highly-optimized replacement for React or Angular. They're not meant to be. The browser gives you a low-level component primitive. What you build on top of it is up to you.&lt;/p&gt;

&lt;p&gt;That mismatch between expectation and reality doesn't make the standard bad. It means the standard solves a different problem than people assumed. And the problem it does solve -- a universal, framework-agnostic component model with native lifecycle tracking -- has no viable alternative in any library or framework today.&lt;/p&gt;




&lt;p&gt;If you want to see these ideas in practice, check out &lt;a href="https://github.com/symbiotejs/symbiote.js" rel="noopener noreferrer"&gt;Symbiote.js&lt;/a&gt; -- a lightweight library built on Custom Elements that adds very flexible reactive state and data binding while keeping you close to the platform.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>webcomponents</category>
      <category>javascript</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>Lit vs Symbiote.js</title>
      <dc:creator>Alex M</dc:creator>
      <pubDate>Mon, 23 Mar 2026 15:54:47 +0000</pubDate>
      <link>https://forem.com/foxeyes/lit-vs-symbiotejs-22gj</link>
      <guid>https://forem.com/foxeyes/lit-vs-symbiotejs-22gj</guid>
      <description>&lt;p&gt;Hi, DEV!&lt;/p&gt;

&lt;p&gt;If you work with Web Components, you've probably heard of &lt;a href="https://lit.dev/" rel="noopener noreferrer"&gt;Lit&lt;/a&gt;. It's the most popular library in this space, maintained by the Google team, and it does its job well. So why would I write an article about an alternative?&lt;/p&gt;

&lt;p&gt;Because &lt;strong&gt;Symbiote.js&lt;/strong&gt; takes some fundamentally different architectural decisions - and even if you personally never plan to use it, some of these approaches are genuinely interesting and might change how you think about component design. I'm Alex, the maintainer of Symbiote.js, and I want to walk you through the key differences, being as fair as possible to both sides.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Lit and Symbiote.js both produce standard Custom Elements. They can coexist on the same page - alongside raw native web components - without any conflicts. This isn't "pick one forever"; you could literally use both in the same project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  At a Glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Symbiote.js 3.x&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Lit 3.x&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Core size&lt;/strong&gt; (brotli)&lt;/td&gt;
&lt;td&gt;~5.9 kb&lt;/td&gt;
&lt;td&gt;~5.1 kb&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dependencies&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0 runtime&lt;/td&gt;
&lt;td&gt;0 runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Shadow DOM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Opt-in&lt;/td&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SSR&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;Experimental (&lt;code&gt;@lit-labs/ssr&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Routing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;Not included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;State management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;Separate (&lt;code&gt;@lit/context&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Build step&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not required&lt;/td&gt;
&lt;td&gt;Not required&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both are lightweight. Lit's core is ~1 kb smaller, but Symbiote's 5.9 kb already includes state management, list rendering (Itemize API), computed properties, and exit animations - things that in Lit require additional packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Templates: the Biggest Difference
&lt;/h2&gt;

&lt;p&gt;This is where the two libraries diverge most. And honestly, this is the part I find most interesting from an architectural standpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Lit Does
&lt;/h3&gt;

&lt;p&gt;In Lit, templates are JavaScript expressions bound to &lt;code&gt;this&lt;/code&gt;:&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="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
    &amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/p&amp;gt;
    &amp;lt;button @click=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;Click&amp;lt;/button&amp;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 &lt;code&gt;html&lt;/code&gt; tag returns a &lt;code&gt;TemplateResult&lt;/code&gt; - a special object processed by Lit's rendering pipeline. Templates live inside &lt;code&gt;render()&lt;/code&gt;, they reference &lt;code&gt;this&lt;/code&gt;, and they re-evaluate on every update cycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Symbiote Does Differently
&lt;/h3&gt;

&lt;p&gt;Symbiote templates are plain HTML strings. They don't reference &lt;code&gt;this&lt;/code&gt; at all:&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="nx"&gt;MyComponent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
  &amp;lt;p&amp;gt;{{message}}&amp;lt;/p&amp;gt;
  &amp;lt;button &lt;/span&gt;&lt;span class="p"&gt;${{&lt;/span&gt;&lt;span class="nl"&gt;onclick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;onClick&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}&amp;gt;Click&amp;lt;/button&amp;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 &lt;code&gt;html&lt;/code&gt; function produces an actual HTML string with &lt;code&gt;bind=&lt;/code&gt; attributes. It's standard template literal syntax - no special objects, no rendering pipeline.&lt;/p&gt;

&lt;p&gt;Why does this matter?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Templates become portable.&lt;/strong&gt; Since a template is just an HTML string, it can live in a separate file, arrive from an API, or sit in the HTML document itself. The same component can even swap templates at runtime via &lt;code&gt;use-template&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;my-component&lt;/span&gt; &lt;span class="na"&gt;use-template=&lt;/span&gt;&lt;span class="s"&gt;"#compact-view"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/my-component&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;my-component&lt;/span&gt; &lt;span class="na"&gt;use-template=&lt;/span&gt;&lt;span class="s"&gt;"#detailed-view"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/my-component&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Templates can be pure HTML with zero JavaScript:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;bind=&lt;/span&gt;&lt;span class="s"&gt;"textContent: myProp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;bind=&lt;/span&gt;&lt;span class="s"&gt;"onclick: handler; @hidden: !flag"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Click me&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;bind&lt;/code&gt; attribute is all you need. A CMS editor, a no-code tool, or a server-side template engine can generate this markup - the component will find the bindings and wire everything up. The template is not coupled to any execution context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Server-Side Rendering: One Module vs Three Packages
&lt;/h2&gt;

&lt;p&gt;This is another area where the approaches differ significantly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lit SSR
&lt;/h3&gt;

&lt;p&gt;Lit's SSR lives in &lt;code&gt;@lit-labs/ssr&lt;/code&gt; (experimental). To get it working, you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@lit-labs/ssr&lt;/code&gt; - the server renderer&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@lit-labs/ssr-client&lt;/code&gt; - client-side hydration support&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@lit-labs/ssr-dom-shim&lt;/code&gt; - DOM polyfills for Node.js&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And there are rules: &lt;code&gt;lit-element-hydrate-support.js&lt;/code&gt; must load &lt;em&gt;before&lt;/em&gt; the &lt;code&gt;lit&lt;/code&gt; module. Server and client renders must produce identical output - mismatches cause errors. Lit SSR doesn't handle async work natively. The output contains &lt;code&gt;&amp;lt;!--lit-part--&amp;gt;&lt;/code&gt; comment markers for template re-association.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symbiote SSR
&lt;/h3&gt;

&lt;p&gt;One module. No hydration mismatches. No comment markers:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SSR&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@symbiotejs/symbiote/node/SSR.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SSR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./my-app.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SSR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;processHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;my-app&amp;gt;&amp;lt;/my-app&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;SSR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trick is architectural: the server writes &lt;code&gt;bind=&lt;/code&gt; attributes into the HTML (clean, standard HTML - no framework markers). The client reads those attributes and attaches reactivity. There's no diffing step, so there's nothing to mismatch. It's impossible by design.&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;isoMode = true&lt;/code&gt; on a component and it figures out what to do: if server content exists, hydrate; if not, render from template. One flag, no "use client" directives, no conditional logic:&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;class&lt;/span&gt; &lt;span class="nc"&gt;MyComponent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Symbiote&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;isoMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&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;span class="nx"&gt;MyComponent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
  &amp;lt;h2 &lt;/span&gt;&lt;span class="p"&gt;${{&lt;/span&gt;&lt;span class="nl"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}&amp;gt;0&amp;lt;/h2&amp;gt;
  &amp;lt;button &lt;/span&gt;&lt;span class="p"&gt;${{&lt;/span&gt;&lt;span class="nl"&gt;onclick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;increment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}&amp;gt;Click me!&amp;lt;/button&amp;gt;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This exact code runs on the server and the client. Streaming is supported too via &lt;code&gt;SSR.renderToStream()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Management: Decorators vs Prefix Tokens
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Lit Way
&lt;/h3&gt;

&lt;p&gt;Lit uses decorators for reactive properties:&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;class&lt;/span&gt; &lt;span class="nc"&gt;MyEl&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;LitElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;property&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&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="nd"&gt;state&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;For data sharing across components, there's &lt;code&gt;@lit/context&lt;/code&gt; - a separate package implementing the W3C Context Community Protocol:&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="c1"&gt;// Provider&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;myContext&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;property&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

&lt;span class="c1"&gt;// Consumer  &lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;consume&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;myContext&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;property&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For anything global, you bring your own state management (Redux, MobX, signals, etc.).&lt;/p&gt;

&lt;h3&gt;
  
  
  The Symbiote Way
&lt;/h3&gt;

&lt;p&gt;Symbiote has a built-in layered data context system. Each layer is identified by a prefix token in the template:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Token&lt;/th&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;(none)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Local state&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{{count}}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;^&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Parent, pop-up (DOM tree walk)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{{^parentTitle}}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shared (by &lt;code&gt;ctx&lt;/code&gt; attr)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{{*sharedCount}}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;APP/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Named global&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{{APP/user}}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CSS custom property&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{{--label}}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;+&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Computed&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'+sum': () =&amp;gt; ...&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here's what I find genuinely elegant about this: &lt;strong&gt;the template &lt;em&gt;is&lt;/em&gt; the wiring.&lt;/strong&gt; A component can bind to an external data context without a single line of component logic - just a prefix in the template. No decorator setup, no provider/consumer classes, no subscription boilerplate. Write &lt;code&gt;{{APP/user}}&lt;/code&gt; and it's connected. Write &lt;code&gt;{{^parentAction}}&lt;/code&gt; and it walks up the DOM to find the handler.&lt;/p&gt;

&lt;p&gt;For example, shared context between components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;upload-btn&lt;/span&gt; &lt;span class="na"&gt;ctx=&lt;/span&gt;&lt;span class="s"&gt;"gallery"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/upload-btn&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;file-list&lt;/span&gt;  &lt;span class="na"&gt;ctx=&lt;/span&gt;&lt;span class="s"&gt;"gallery"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/file-list&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;status-bar&lt;/span&gt; &lt;span class="na"&gt;ctx=&lt;/span&gt;&lt;span class="s"&gt;"gallery"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/status-bar&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UploadBtn&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Symbiote&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;init$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*files&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;span class="nf"&gt;onUpload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newFile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*files&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*files&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;newFile&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;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FileList&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Symbiote&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;init$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*files&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three components, one shared &lt;code&gt;*files&lt;/code&gt; state. No parent orchestrator, no event bus, no prop drilling. Just &lt;code&gt;ctx="gallery"&lt;/code&gt; in the markup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shadow DOM: Default vs Opt-In
&lt;/h2&gt;

&lt;p&gt;Lit uses Shadow DOM by default. Every component gets style isolation. To opt out, you override &lt;code&gt;createRenderRoot() { return this; }&lt;/code&gt; - it works, but it's clearly an escape hatch.&lt;/p&gt;

&lt;p&gt;Symbiote flips this: &lt;strong&gt;Light DOM is the default.&lt;/strong&gt; Shadow DOM is opt-in, per component. Set &lt;code&gt;renderShadow = true&lt;/code&gt; or define &lt;code&gt;shadowStyles&lt;/code&gt; - shadow root is created. Don't - and your component renders in Light DOM.&lt;/p&gt;

&lt;p&gt;This is more than a preference. For widgets embedded in third-party pages, mandatory Shadow DOM means the host application can't restyle your component - even when they need to. An opt-in model lets you choose isolation where it's valuable and openness where it's not.&lt;/p&gt;

&lt;p&gt;Symbiote supports both simultaneously on the same component: &lt;code&gt;rootStyles&lt;/code&gt; for Light DOM and &lt;code&gt;shadowStyles&lt;/code&gt; for Shadow DOM.&lt;/p&gt;

&lt;h2&gt;
  
  
  CSS as a Data Source
&lt;/h2&gt;

&lt;p&gt;This is probably the most unusual Symbiote feature. Components can read CSS custom properties directly into their reactive state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;my-widget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Upload files'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;768px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;my-widget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Upload'&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;MyWidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
  &amp;lt;button&amp;gt;{{--label}}&amp;lt;/button&amp;gt;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CSS custom properties are read once on initialization - they set the starting state. Since browsers don't provide a universal signal for custom property changes, runtime updates require calling &lt;code&gt;this.updateCssData()&lt;/code&gt; explicitly (for example, from a &lt;code&gt;ResizeObserver&lt;/code&gt; or after toggling a class). It's not fully automatic, but the initialization alone is already powerful: different CSS classes, media queries, or host stylesheets can set different starting configurations for the same component - all without JavaScript.&lt;/p&gt;

&lt;p&gt;You can even assign shared context groups via CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.gallery-section&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;--ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;gallery&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;Lit supports CSS custom properties crossing shadow boundaries, but it doesn't have a mechanism to use CSS values as component &lt;em&gt;state&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build Tooling
&lt;/h2&gt;

&lt;p&gt;Both libraries technically work without a build step. But in practice, Lit's developer experience is built around TypeScript decorators (&lt;code&gt;@property()&lt;/code&gt;, &lt;code&gt;@state()&lt;/code&gt;, &lt;code&gt;@customElement()&lt;/code&gt;). The docs, the examples, most community code - all assume a TypeScript compiler.&lt;/p&gt;

&lt;p&gt;Symbiote uses standard JavaScript throughout - ESM, class fields, template literals. No decorators, no transpilation. It supports &lt;code&gt;importmap&lt;/code&gt;-based dependency sharing with CDN imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"importmap"&lt;/span&gt;&lt;span class="nt"&gt;&amp;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;imports&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@symbiotejs/symbiote&lt;/span&gt;&lt;span class="dl"&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;https://esm.run/@symbiotejs/symbiote&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./my-app.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No bundler, no &lt;code&gt;node_modules&lt;/code&gt;, works in the browser directly. When you need a bundler for production - esbuild, Rollup, or any standard tool will do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Lit Does Better
&lt;/h2&gt;

&lt;p&gt;I said I'd be fair, so:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Community and ecosystem.&lt;/strong&gt; Lit has a large, active community, extensive documentation, and wide adoption. Google uses it internally. If you need Stack Overflow answers and community plugins, Lit has far more momentum.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maturity.&lt;/strong&gt; Lit has been around longer (through Polymer → LitElement → Lit transitions) and has gone through more production battle-testing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slightly smaller core.&lt;/strong&gt; ~5.1 kb vs ~5.9 kb - though the practical difference evaporates once you add context, lists, and routing to a Lit project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slightly faster initial render.&lt;/strong&gt; Symbiote needs to look up the DOM environment after a component connects - reading context from its position in the tree, resolving pop-up bindings, checking CSS data. This makes its initial render a bit slower than Lit's. The gap is not dramatic, but it's noticeable in benchmarks. Once rendered, both libraries perform comparably for updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Developer Experience
&lt;/h2&gt;

&lt;p&gt;Both libraries use &lt;code&gt;html&lt;/code&gt; tagged template literals, so standard IDE syntax highlighting for HTML-in-JS works equally well in both. Lit has a dedicated VS Code plugin (&lt;code&gt;lit-plugin&lt;/code&gt;) that adds type checking and completion inside templates.&lt;/p&gt;

&lt;p&gt;Symbiote takes a different approach: instead of IDE tooling, it ships with a built-in &lt;code&gt;devMode&lt;/code&gt; runtime messaging system. Enable it and you get warnings about broken bindings, missing context properties, and type mismatches - directly in the console, at the moment they occur. It's not an IDE plugin, but it catches the same class of problems at runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should You Try It?
&lt;/h2&gt;

&lt;p&gt;If you're building widgets that embed into any environment, micro-frontends without framework coupling, or component libraries that need to work in React, Angular, Vue, and plain HTML - Symbiote's architectural choices solve real problems.&lt;/p&gt;

&lt;p&gt;If you're building a single-stack application where all components live in one codebase and you want maximum community support, Lit is a perfectly solid choice.&lt;/p&gt;

&lt;p&gt;And remember: they produce standard Custom Elements. You can mix Lit, Symbiote, and raw native web components on the same page without any conflicts. The choice isn't exclusive.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Even if you don't plan to use Symbiote.js right now, some of the ideas - templates as portable HTML strings, one-prefix data binding across contexts, CSS as a data source - are worth knowing about. Different approaches expand how we think about component architecture. And if any of this was interesting, a &lt;a href="https://github.com/symbiotejs/symbiote.js" rel="noopener noreferrer"&gt;⭐ on GitHub&lt;/a&gt; really helps us Open Source developers keep going.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/symbiotejs/symbiote.js" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/symbiotejs/symbiote.js/blob/main/docs/README.md" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/symbiotejs/symbiote.js/blob/main/docs/lit-vs-symbiote.md" rel="noopener noreferrer"&gt;Full comparison doc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rnd-pro.com/symbiote/3x/examples/" rel="noopener noreferrer"&gt;Live Examples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rnd-pro/jsda-kit" rel="noopener noreferrer"&gt;JSDA-Kit&lt;/a&gt; - companion all-in-one tool with SSR, SSG, bundling, live serving and import maps generation&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webcomponents</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Symbiote.js - Superpower For Web Components</title>
      <dc:creator>Alex M</dc:creator>
      <pubDate>Tue, 17 Mar 2026 13:55:24 +0000</pubDate>
      <link>https://forem.com/foxeyes/symbiotejs-v3-web-components-with-ssr-in-6kb-10n6</link>
      <guid>https://forem.com/foxeyes/symbiotejs-v3-web-components-with-ssr-in-6kb-10n6</guid>
      <description>&lt;p&gt;Hi, DEV!&lt;/p&gt;

&lt;p&gt;My name is Alex, and I'm the maintainer of &lt;a href="https://github.com/symbiotejs/symbiote.js" rel="noopener noreferrer"&gt;Symbiote.js&lt;/a&gt; - a library for building UI components and isomorphic applications using the latest web standards. &lt;/p&gt;

&lt;p&gt;Today I'm going to talk about our important major update - version 3.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea in Few Words
&lt;/h2&gt;

&lt;p&gt;Symbiote.js is a lightweight (~5.9 kb brotli) wrapper around Custom Elements that adds reactivity, templates, and data layer management. No Virtual DOM, no special compiler, no mandatory build step - you can plug components right from a CDN.&lt;/p&gt;

&lt;p&gt;But the most important thing is not the size or the absence of dependencies. The main thing is &lt;strong&gt;loose coupling&lt;/strong&gt;. The entire design of the library is built around a crucial idea: a component can beforehand &lt;em&gt;not know&lt;/em&gt; who configures it, what surrounds it, and in what context it is used. Configuration and data can come from HTML markup, from CSS, from a parent component in the DOM tree, or from a dedicated data context - and the component simply checks what it's ready to bind to, finding itself in a specific place at a specific time, and enters into a &lt;strong&gt;symbiosis&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Another important point: I know many developers are afraid to get involved with Shadow DOM. Well, in Symbiote.js, Shadow DOM is an &lt;em&gt;optional&lt;/em&gt; feature. You can freely apply the most conservative approaches to styling, use the isolation layer only where necessary, and implement any hybrid scheme with maximum flexibility and efficiency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Loose Coupling is Important
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Loose_coupling" rel="noopener noreferrer"&gt;Loose coupling&lt;/a&gt; is not just an abstract architectural principle. These are concrete scenarios where hard dependencies between components create real problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Embeddable widgets.&lt;/strong&gt; Your component has to work on someone else's site - and you don't control its stack, CSS, and build process. If a widget requires a specific framework, provider, or build pipeline, you will complicate your life and the lives of others. If it gets configured via HTML attributes or CSS, it will easily fit anywhere.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Micro-frontends.&lt;/strong&gt; Several teams are building different parts of the same application. This works much better when components communicate via declarative contracts (HTML attributes, CSS variables, named data contexts) rather than direct JS imports and shared memory objects.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;CMS and no-code platforms.&lt;/strong&gt; A content manager or designer configures a component via HTML markup or CSS without touching JavaScript. This is possible only if the component knows how to get configuration from these sources.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Multi-team development.&lt;/strong&gt; One team makes a design system, another - product features. The fewer explicit dependencies between modules, the fewer merge conflicts.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Gradual migration.&lt;/strong&gt; You can't rewrite everything at once. Symbiote allows you to embed new components into an existing React, Angular, Vue, Svelte or jQuery application - without wrappers, adapters, and double renders, organizing data exchange seamlessly.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Runtime-dedicated modules without conditional logic.&lt;/strong&gt; Since all binding in Symbiote.js is key-based (text strings like &lt;code&gt;'app/theme'&lt;/code&gt;, &lt;code&gt;'*files'&lt;/code&gt;, &lt;code&gt;'^onAction'&lt;/code&gt;), you can create modules that are specific to a runtime environment (server, browser, worker) without wrapping them in &lt;code&gt;if/else&lt;/code&gt; blocks. For example, the server imports &lt;code&gt;node-imports.js&lt;/code&gt; and the browser imports &lt;code&gt;browser-imports.js&lt;/code&gt; - both register components that bind to the same named context keys. Modules don't import each other, don't check &lt;code&gt;typeof window&lt;/code&gt;, and don't share objects in memory. They connect indirectly, through matching data keys. This is composition without coupling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Symbiote.js is designed so that all these scenarios work out of the box. Below are the specific mechanisms.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Everything described below can be seen in action in the &lt;a href="https://github.com/rnd-pro/symbiote-ref-simplified" rel="noopener noreferrer"&gt;reference application&lt;/a&gt; with a &lt;a href="https://rnd-pro.github.io/symbiote-ref-simplified/" rel="noopener noreferrer"&gt;live demo&lt;/a&gt; - a universal app with SSR streaming, SPA routing, declarative server-side Shadow DOM, localization, and basic patterns for connecting to reactive data. No bundlers, no build pipelines - pure ESM + import maps. &lt;/p&gt;

&lt;p&gt;You can also see examples and play with live code without installation here: &lt;a href="https://rnd-pro.com/symbiote/3x/examples/" rel="noopener noreferrer"&gt;Live Examples&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Configuration outside JavaScript
&lt;/h2&gt;

&lt;p&gt;Most UI libraries dictate a similar and familiar way of configuring components - via props or attributes passed by a parent JS component. Symbiote.js expands this model: components can be configured from multiple data sources. All of them work equally transparently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Describing bindings in HTML attributes
&lt;/h3&gt;

&lt;p&gt;Any Symbiote.js template can be written as regular HTML that knows absolutely nothing about the JavaScript context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;bind=&lt;/span&gt;&lt;span class="s"&gt;"textContent: myProp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;bind=&lt;/span&gt;&lt;span class="s"&gt;"onclick: handler; @hidden: !flag"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Click me&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;bind&lt;/code&gt; attribute is exactly the declarative binding of an element to the reactive state of the component. You can write it by hand in an HTML file, generate it on the server, or create it in any template engine. The component's JavaScript code doesn't care - it will see &lt;code&gt;bind&lt;/code&gt; in the DOM, substitute data, and connect handlers.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;html&lt;/code&gt; helper in JS files simply generates these attributes from a more convenient syntax:&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="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;button &lt;/span&gt;&lt;span class="p"&gt;${{&lt;/span&gt;&lt;span class="nl"&gt;onclick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}&amp;gt;Click me&amp;lt;/button&amp;gt;`&lt;/span&gt;
&lt;span class="c1"&gt;// → &amp;lt;button bind="onclick: handler"&amp;gt;Click me&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The template can be stored wherever and however you like: in a JS file, in an HTML document, on the server. It is not tied to the execution context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration from CSS
&lt;/h3&gt;

&lt;p&gt;This is perhaps the most unusual feature. Components can read CSS variables to initialize their state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;my-widget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Upload files'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;768px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;my-widget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Upload'&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyWidget&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Symbiote&lt;/span&gt; &lt;span class="p"&gt;{...}&lt;/span&gt;

&lt;span class="nx"&gt;MyWidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
  &amp;lt;button&amp;gt;{{--label}}&amp;lt;/button&amp;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 component uses &lt;code&gt;--label&lt;/code&gt; from CSS. Change the theme - parameters change. A media query triggers - an adaptive template is applied. Switch a class on a container - new configuration. &lt;/p&gt;

&lt;p&gt;Why do this? Advanced themes, responsiveness without additional JS window listeners, localization, or simply passing parameters to embedded widgets from a host app's stylesheet.&lt;/p&gt;

&lt;h3&gt;
  
  
  External templates
&lt;/h3&gt;

&lt;p&gt;A component can use a template defined anywhere in the HTML document:&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;class&lt;/span&gt; &lt;span class="nc"&gt;MyComponent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Symbiote&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;allowCustomTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"custom-view"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;{{title}}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{{description}}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;my-component&lt;/span&gt; &lt;span class="na"&gt;use-template=&lt;/span&gt;&lt;span class="s"&gt;"#custom-view"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/my-component&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful when the component provides only data and handlers, and different markup variants form different representations in the DOM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Component communication without prop drilling
&lt;/h2&gt;

&lt;p&gt;Symbiote.js has several mechanisms for component communication that do not require explicit passing of data from parent to child or between instances.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shared context (&lt;code&gt;ctx&lt;/code&gt; + &lt;code&gt;*&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Components can be grouped via an HTML attribute - same as the native HTML &lt;code&gt;name&lt;/code&gt; attribute groups radio buttons:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;upload-btn&lt;/span&gt; &lt;span class="na"&gt;ctx=&lt;/span&gt;&lt;span class="s"&gt;"gallery"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/upload-btn&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;file-list&lt;/span&gt;  &lt;span class="na"&gt;ctx=&lt;/span&gt;&lt;span class="s"&gt;"gallery"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/file-list&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;status-bar&lt;/span&gt; &lt;span class="na"&gt;ctx=&lt;/span&gt;&lt;span class="s"&gt;"gallery"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/status-bar&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UploadBtn&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Symbiote&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;init$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*files&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;span class="nf"&gt;onUpload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newFile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*files&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*files&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;newFile&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;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FileList&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Symbiote&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;init$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*files&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three components, one shared data context &lt;code&gt;gallery&lt;/code&gt; and a &lt;code&gt;*files&lt;/code&gt; field. Without a shared parent component, without prop drilling, without an event bus. Put &lt;code&gt;ctx="gallery"&lt;/code&gt; in the markup - components are linked, done.&lt;/p&gt;

&lt;p&gt;The group can also be assigned via CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.gallery-section&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;gallery&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"gallery-section"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;upload-btn&amp;gt;&amp;lt;/upload-btn&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;file-list&amp;gt;&amp;lt;/file-list&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is layout-driven grouping: the visual container defines the logical relationship between components.&lt;/p&gt;

&lt;p&gt;An obvious use case: a complex widget where, for example, the file upload interface and the upload progress bar are in different parts of the host application's DOM tree.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pop-up binding (&lt;code&gt;^&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;A component can access the properties of the nearest ancestor in the DOM tree - without imports, without knowing about the specific parent:&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;class&lt;/span&gt; &lt;span class="nc"&gt;ToolbarBtn&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Symbiote&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;ToolbarBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
  &amp;lt;button &lt;/span&gt;&lt;span class="p"&gt;${{&lt;/span&gt;&lt;span class="nl"&gt;onclick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;^onAction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}&amp;gt;{{^label}}&amp;lt;/button&amp;gt;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;^onAction&lt;/code&gt; - Symbiote will go up the DOM and find the first component that has &lt;code&gt;onAction&lt;/code&gt; registered in its state. Like a CSS cascade, only bottom-up, for data and handlers.&lt;/p&gt;

&lt;p&gt;This allows creating reusable "dumb" components that adapt to the context of use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Named data contexts
&lt;/h3&gt;

&lt;p&gt;For situations where global or feature-dedicated state is needed, 3 lines of code is all it takes:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PubSub&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@symbiotejs/symbiote&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// app/app.js - register once&lt;/span&gt;
&lt;span class="nx"&gt;PubSub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerCtx&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;darkTheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;toDoList&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;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In any component:&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="c1"&gt;// Access:&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app/darkTheme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// write&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app/darkTheme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// read&lt;/span&gt;

&lt;span class="c1"&gt;// Subscription:&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app/toDoList&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="nx"&gt;items&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tasks:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;items&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;Without a store, without a provider, without &lt;code&gt;useContext&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  SSR and Universal Components
&lt;/h2&gt;

&lt;p&gt;The killer feature of version 3 is server-side rendering. One flag, one code, one component, works everywhere, on the server and on the client:&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;class&lt;/span&gt; &lt;span class="nc"&gt;MyComponent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Symbiote&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;isoMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&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;span class="nx"&gt;MyComponent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
  &amp;lt;h2 &lt;/span&gt;&lt;span class="p"&gt;${{&lt;/span&gt;&lt;span class="nl"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}&amp;gt;&amp;lt;/h2&amp;gt;
  &amp;lt;button &lt;/span&gt;&lt;span class="p"&gt;${{&lt;/span&gt;&lt;span class="nl"&gt;onclick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;increment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}&amp;gt;Click me!&amp;lt;/button&amp;gt;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;MyComponent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;isoMode = true&lt;/code&gt; - if there is server content, the component hydrates it. If not, it renders the template from scratch. Without conditions, without &lt;code&gt;'use client'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On the server:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SSR&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@symbiotejs/symbiote/node/SSR.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SSR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./my-app.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SSR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;processHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;my-app&amp;gt;&amp;lt;/my-app&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;SSR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hydration mismatches are &lt;strong&gt;impossible in principle, by design&lt;/strong&gt; - there is no diffing. The server writes &lt;code&gt;bind=&lt;/code&gt; attributes into the markup, the client reads them and attaches reactivity. No kilometer-long JSONs for hydration.&lt;/p&gt;

&lt;p&gt;Components with Shadow DOM are also supported in SSR via the Declarative Shadow DOM (DSD) mechanism.&lt;/p&gt;

&lt;h3&gt;
  
  
  Static Site Generation (SSG)
&lt;/h3&gt;

&lt;p&gt;The same SSR mechanism works for static generation - &lt;code&gt;SSR.processHtml()&lt;/code&gt; returns a string that you simply write to a file:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SSR&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@symbiotejs/symbiote/node/SSR.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SSR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./my-app.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SSR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;processHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mainTemplate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dist/index.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;SSR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No separate SSG framework required. The &lt;a href="https://github.com/rnd-pro/symbiote-ref-simplified" rel="noopener noreferrer"&gt;reference app&lt;/a&gt; uses exactly this approach - &lt;code&gt;npm run ssr&lt;/code&gt; generates a fully rendered &lt;code&gt;dist/index.html&lt;/code&gt; that can be opened directly in a browser or deployed to any static hosting (e.g. GitHub Pages). Client-side JS takes over from there and the app becomes an SPA.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fun fact: recently I came across yet another heavily upvoted comment on Reddit claiming that Custom Elements are a purely browser API and that rendering web components on the server is impossible. So, friends, here we are easily doing the impossible. In general, web components as a group of standards are surrounded by many myths and misconceptions - and Symbiote helps fight those misconceptions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What else is in the new version?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Computed properties&lt;/strong&gt; - derived state properties with dependency tracking.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Exit animations&lt;/strong&gt; - CSS enter and exit animations using &lt;code&gt;@starting-style&lt;/code&gt; and &lt;code&gt;[leaving]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;SPA Router&lt;/strong&gt; - an optional module with path-based URLs, parameters, guards, and lazy loading.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Keyed itemize&lt;/strong&gt; - key-based reconciliation for lists, running multiple times faster for immutable data.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;CSP &amp;amp; Trusted Types&lt;/strong&gt; - out-of-the-box compatibility with strict CSP headers.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Dev mode&lt;/strong&gt; - verbose warnings about problems in bindings and hydration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bundle Size
&lt;/h2&gt;

&lt;p&gt;Size is not just a badge number. It is concrete loading time for your real users.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Minified&lt;/th&gt;
&lt;th&gt;Gzip&lt;/th&gt;
&lt;th&gt;Brotli&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Symbiote.js&lt;/strong&gt; (core)&lt;/td&gt;
&lt;td&gt;18.9 kb&lt;/td&gt;
&lt;td&gt;6.6 kb&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5.9 kb&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Symbiote.js&lt;/strong&gt; (full, with AppRouter)&lt;/td&gt;
&lt;td&gt;23.2 kb&lt;/td&gt;
&lt;td&gt;7.9 kb&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.2 kb&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lit 3.3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;15.5 kb&lt;/td&gt;
&lt;td&gt;6.0 kb&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~5.1 kb&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;React 19 + ReactDOM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~186 kb&lt;/td&gt;
&lt;td&gt;~59 kb&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~50 kb&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In the 5.9 kb base bundle, Symbiote gives you reactivity, data contexts, dynamic lists, animations, computed properties, hydration - all the most important stuff. For comparable functionality in Lit or React, you'll need additional packages. And I won't even mention SSR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Symbiote.js is a library that significantly expands the capabilities of web components while staying close to the platform and standards. Configuration from CSS, binding via HTML attributes, data contexts without direct links between components in JS, minimal boilerplate, optimal DX. Components don't know about each other, but work together - on the client and on the server.&lt;/p&gt;

&lt;p&gt;If you need widgets that embed into any environment, micro-frontends without extra hassle, complex hybrid framework-agnostic applications, or a reusable component library for different projects - take a look at &lt;a href="https://github.com/symbiotejs/symbiote.js" rel="noopener noreferrer"&gt;Symbiote.js&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Even if you don't plan to use the library itself right away, but saw some interesting approaches - give the project a star, it really helps us Open Source developers not to lose heart.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webdev</category>
      <category>webcomponents</category>
      <category>ssr</category>
      <category>javascript</category>
    </item>
    <item>
      <title>JavaScript Distributed Assets - simple but powerful</title>
      <dc:creator>Alex M</dc:creator>
      <pubDate>Thu, 11 Dec 2025 17:10:46 +0000</pubDate>
      <link>https://forem.com/foxeyes/jsda-is-very-simple-1cfk</link>
      <guid>https://forem.com/foxeyes/jsda-is-very-simple-1cfk</guid>
      <description>&lt;p&gt;&lt;strong&gt;Here's the idea:&lt;/strong&gt; take standard JavaScript modules (ESM) and turn them into direct endpoints for generating any text-based web assets — HTML files, CSS, SVG, or even JSON and Markdown — using a simple file naming convention with a default export as a string (JavaScript Template Literal). Sounds super simple and kinda like PHP, right? But what does this actually give us?&lt;/p&gt;

&lt;p&gt;Let's dive into why &lt;strong&gt;JSDA&lt;/strong&gt; (JavaScript Distributed Assets) might just be the thing that makes web development "great again" after a thousand wrong turns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create a file called index.html.js&lt;/span&gt;

&lt;span class="c1"&gt;// Import a method to access data (optional):&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getPageData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./project-data.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Fetch the necessary data (optional):&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pageData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPageData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Export the final HTML document:&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="cm"&gt;/*html*/&lt;/span&gt; &lt;span class="s2"&gt;`
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pageData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h1&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pageData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/h1&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will automatically transform into plain HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Page Title&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Page Heading&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conventions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;JSDA&lt;/strong&gt; is all about following these simple conventions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;To define the output file type, use the pattern &lt;code&gt;*.&amp;lt;type&amp;gt;.js&lt;/code&gt;, for example &lt;code&gt;my-page.html.js&lt;/code&gt;, &lt;code&gt;styles.css.js&lt;/code&gt;, &lt;code&gt;image.svg.js&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;To define static generation entry points, use the pattern &lt;code&gt;index.&amp;lt;type&amp;gt;.js&lt;/code&gt;, for example &lt;code&gt;index.html.js&lt;/code&gt;, &lt;code&gt;index.css.js&lt;/code&gt;, &lt;code&gt;index.svg.js&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;A JSDA file must be a standard ESM module containing a default export as a string (&lt;code&gt;export default '...'&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The directory structure of output files mirrors the source structure (we get file-system-based routing out of the box):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── index.html.js          → dist/index.html
├── styles/
│   └── index.css.js       → dist/styles/index.css
└── assets/
    └── logo/
        └── index.svg.js   → dist/assets/logo/index.svg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  JSDA as an Evolution of JAMStack
&lt;/h2&gt;

&lt;p&gt;Simplifying things is actually pretty hard. When we strip away the excess, it feels like we might be missing something important. But in practice, it's often the opposite — extra elements in a system make it less stable and more vulnerable. Simplification is an art that might seem trivial in hindsight. But it really isn't.&lt;/p&gt;

&lt;p&gt;When Netlify CEO Matt Biilmann proposed the &lt;a href="https://jamstack.org/what-is-jamstack/" rel="noopener noreferrer"&gt;JAMStack&lt;/a&gt; architectural concept back in 2015, that's exactly what he did — he simplified. Think about it: why do we need a CMS and a database, with all their vulnerabilities and server resource consumption, if the server ultimately just needs to serve static files? Why does a server need complex logic if we can generate the necessary assets at build time? Why not serve all static files as fast, efficiently, and securely as possible through a CDN, minimizing load and dramatically improving scalability? And most importantly—why overcomplicate things when we can achieve better results by simplifying? A pretty counterintuitive way of thinking at the time, but absolutely right.&lt;/p&gt;

&lt;p&gt;However, in my opinion, JAMStack is too general a concept — a set of high-level recommendations that barely touch on implementation details and don't propose specific solutions to tasks that can have their own complexity. Many people still believe JAMStack has significant limitations, but that's not true — we can combine it with any other practices in any arbitrary combinations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSDA&lt;/strong&gt; follows the same philosophy but with more technical substance. We take the native capabilities of the web platform (Node.js + browser) and existing standards, and without adding any new redundant entities, we solve problems that traditionally require much bulkier solutions — all while not limiting ourselves in terms of possibilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hybrid Approach
&lt;/h2&gt;

&lt;p&gt;If JAMStack is mostly about SSG (Static Site Generators), then JSDA plays on both fields — as an architecture applicable for generating both static and dynamic content. On top of that, JSDA doesn't restrict you from using any auxiliary technologies if needed.&lt;/p&gt;

&lt;p&gt;Traditionally, we distinguish the following web application modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SPA&lt;/strong&gt; (Single Page Application) – everything is controlled by client-side JavaScript, the DOM tree is fully created dynamically on the client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSR&lt;/strong&gt; (Server Side Rendering) – the server pre-renders the page, which is then "hydrated" on the client with JavaScript.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSG&lt;/strong&gt; (Static Site Generation) – generating HTML files at build time, which are then served as static content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic page generation&lt;/strong&gt; – the server generates HTML files on request, and the result can be cached.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These approaches don't exclude each other. Each has its strengths and weaknesses, but they can be combined effectively. In complex scenarios, for instance, documentation pages or promo materials can be fully static, product pages can be partially pre-rendered on the server and partially contain dynamic widgets (f.e. shopping cart), while a user's dashboard can be fully implemented as an SPA.&lt;/p&gt;

&lt;p&gt;And the JSDA stack is exactly suited for such complex scenarios, while remaining simple and minimalistic itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Async Operations (Top Level Await)
&lt;/h2&gt;

&lt;p&gt;According to the &lt;strong&gt;ESM&lt;/strong&gt; specifications, modules are asynchronous and support top-level async calls — Top level &lt;code&gt;await&lt;/code&gt;. This means that when generating assets, we can make requests and fetch data across the entire chain of imports and dependencies. We can access databases, external APIs, the file system, and so on. And here's the cool part: we don't have to deal with async complexity ourselves — it's "masked" behind standard ESM mechanisms. By simply importing a dependency, we can be sure that all its data is fetched during module resolution. In my view, this is a very powerful yet underrated platform capability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caching
&lt;/h2&gt;

&lt;p&gt;According to the standard, ESM modules are automatically cached in runtime memory upon resolution. This makes the generation process more efficient by avoiding redundant operations when reusing a module. If, on the other hand, you want to control caching and get fresh data from the import chain, you can use unique module identifiers (addresses) during import, like this:&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`./data.js?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also use query parameters accessible inside the module via &lt;code&gt;import.meta&lt;/code&gt;, for example:&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;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`./user-data.js?id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  String Interpolation
&lt;/h2&gt;

&lt;p&gt;Another &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals" rel="noopener noreferrer"&gt;cool thing&lt;/a&gt; we get "for free" is the ability to compose complex result strings. This native "templating engine" lets you build any complex markup or structure from components, reuse them, and inject any logic during generation. Against this backdrop, the trendy server components from the React and Next.js ecosystem look like overcomplicated nonsense.&lt;/p&gt;

&lt;h2&gt;
  
  
  SSR and Web Components
&lt;/h2&gt;

&lt;p&gt;But how do we bring markup to life on the client? How do we bind DOM elements to data and handlers? We've got everything we need for this too — no need to invent anything extra. The solution is a standard group of specifications known as Web Components. Let me show you a simplified example of how this works.&lt;/p&gt;

&lt;p&gt;On the server, we create the following markup:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;getUserData&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./getUserData.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;?user-id=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="cm"&gt;/*html*/&lt;/span&gt; &lt;span class="s2"&gt;`
&amp;lt;my-component user-id="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;
  &amp;lt;div id="name"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/div&amp;gt;
  &amp;lt;div id="age"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/div&amp;gt;
&amp;lt;/my-component&amp;gt;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the client, we register a CustomElement:&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="c1"&gt;// getUserData function can be isomorphic&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;getUserData&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./getUserData.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyComponent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;connectedCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userNameEl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userAgeEl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#age&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Refine and bind data if needed&lt;/span&gt;
    &lt;span class="nf"&gt;getUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userNameEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userAgeEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&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;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MyComponent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! And no horrible &lt;code&gt;__NEXT_DATA__&lt;/code&gt; like in Next.js. The identifier for the "hydrated" node is our custom tag, which easily takes control of its DOM section. The browser handles everything through the CustomElements lifecycle. Remember — Web Components don't necessarily have to include Shadow DOM.&lt;/p&gt;

&lt;p&gt;But what if there are lots of components with their own nested hierarchies? That's also simple—here's another example.&lt;/p&gt;

&lt;p&gt;This time, a simple recursive function will draw us structured HTML:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * 
 * @param {String} html source HTML 
 * @param {String} tplPathSchema component template path schema (e.g., './ref/wc/{tag-name}/tpl.html')
 * @param {Object&amp;lt;string, string&amp;gt;} [data] rendering data
 * @returns {Promise&amp;lt;String&amp;gt;} rendered HTML
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;wcSsr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tplPathSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;key&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="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replaceAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`{[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;([&lt;/span&gt;&lt;span class="sr"&gt;a-z&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;[\w&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)(?:\s&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)?&lt;/span&gt;&lt;span class="sr"&gt;&amp;gt;/g&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;fullMatch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tagName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;tplPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tplPathSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{tag-name}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tagName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;tpl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Support component templates in different formats:&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tplPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.html&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="nx"&gt;tpl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tplPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tplPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&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="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tplPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;tplPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.mjs&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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;tpl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tplPath&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Template not found for &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tagName&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;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tpl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;tpl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;tpl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;wcSsr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tpl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tplPathSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;tpl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Endless loop detected for component &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fullMatch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fullMatch&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;tpl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&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;For more fully-fledged work with Web Components, you can use &lt;a href="https://open-wc.org/guides/community/base-libraries/" rel="noopener noreferrer"&gt;any popular library&lt;/a&gt;. Personally, I use &lt;a href="https://www.npmjs.com/package/@symbiotejs/symbiote" rel="noopener noreferrer"&gt;Symbiote.js&lt;/a&gt;, as it's adapted for working with execution-context-independent HTML templates (and more).&lt;/p&gt;

&lt;h2&gt;
  
  
  File Transformations
&lt;/h2&gt;

&lt;p&gt;Converting JSDA files to text-based web assets is clear enough, but here's the next question: how do we represent regular text files in JSDA format?&lt;/p&gt;

&lt;p&gt;Super easy:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./index.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before exporting the file content, you can perform any intermediate transformations, for example, adding the right color palette to SVG or even doing automatic document translation through an LLM call.&lt;/p&gt;

&lt;h2&gt;
  
  
  CDN, HTTPS Imports, and npm
&lt;/h2&gt;

&lt;p&gt;Let's get back to the ESM standard and its wonderful capabilities—specifically, the ability to load modules directly from a CDN via HTTPS. This is fully supported in both Node.js and the browser. At the CDN level (or even your own endpoint), automatic intermediate bundling and minification of modules can happen. That's how many popular specialized code delivery CDNs work — jsDelivr, esm.run, esm.sh, cdnjs, and many others. This lets you efficiently manage external dependencies and separate deployment cycles for complex system components.&lt;/p&gt;

&lt;p&gt;For managing such dependencies, a super useful tool is the native browser technology &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script/type/importmap" rel="noopener noreferrer"&gt;importmap&lt;/a&gt;. Forget about all those clunky things like Module Federation — the platform already gives us everything we need.&lt;/p&gt;

&lt;p&gt;Also, for working with JSDA dependencies (especially in a server context), good old &lt;code&gt;npm&lt;/code&gt; works perfectly, giving us version control out of the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distributed Composition
&lt;/h2&gt;

&lt;p&gt;Everything mentioned in the previous section explains the "Distributed" part in the &lt;strong&gt;JSDA&lt;/strong&gt; acronym. Simple and clear composition models are super important at the ecosystem level, where solution providers may be loosely connected to each other.&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;JSDA&lt;/strong&gt; ecosystem, creating integrable solutions is really easy because you can always rely on the most basic standards and specifications without reinventing the wheel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security
&lt;/h2&gt;

&lt;p&gt;One of the key security mechanisms in the JSDA stack is &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Subresource_Integrity" rel="noopener noreferrer"&gt;SRI&lt;/a&gt; (Subresource Integrity) — integrity verification of JSDA dependencies through hashing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Isomorphism
&lt;/h2&gt;

&lt;p&gt;At the very beginning, I mentioned PHP, and you might be wondering: if &lt;strong&gt;JSDA&lt;/strong&gt; works almost like PHP, why not just use PHP?&lt;/p&gt;

&lt;p&gt;First off, PHP is a hypertext preprocessor. Working with output formats other than HTML (XML) isn't always as painless as you'd like. PHP simply doesn't have a full equivalent to template literals like JS does.&lt;/p&gt;

&lt;p&gt;Second, JavaScript is — whether you like it or not — the only programming language on the web that's fully and natively supported on both server and client.  &lt;/p&gt;

&lt;p&gt;Third, and most importantly, by using one language you can reuse the same entities in server and client code, write so-called "isomorphic" code, and SIGNIFICANTLY save on all development-related processes, including the mental ones in your head. You don't have to switch between languages, you don't have to write tons of separate configs, and your project is easier to test and maintain with fewer resources.&lt;/p&gt;

&lt;p&gt;Thanks to isomorphism and a unified language, &lt;strong&gt;JSDA&lt;/strong&gt;, while primarily a server technology, can easily and almost seamlessly be applied on the client when needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript
&lt;/h2&gt;

&lt;p&gt;You can't get by without TS in modern development. However, TypeScript itself, being an important ecosystem tool for static analysis, contains a bunch of complexities and controversial aspects that surface as soon as you try to do something more intricate. If you've ever developed your own libraries — you'll immediately know what I'm talking about.&lt;/p&gt;

&lt;p&gt;The author of these lines has come to use type declarations directly in &lt;a href="https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html" rel="noopener noreferrer"&gt;JS code in JSDoc format&lt;/a&gt;, along with additional &lt;code&gt;*.d.ts&lt;/code&gt; files, as the most balanced TypeScript practice. And I'm not alone in this — I'm seeing more and more experienced developers doing the same.&lt;/p&gt;

&lt;p&gt;Applied to &lt;strong&gt;JSDA&lt;/strong&gt;, this approach gives the following benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No additional build steps: each module can be a standalone endpoint&lt;/li&gt;
&lt;li&gt;The code that runs is exactly the code you wrote — no messing with sourceMap bugs during debugging&lt;/li&gt;
&lt;li&gt;No issues with following standards (for example, ESM identifiers include file extensions — in TS this isn't mandatory)&lt;/li&gt;
&lt;li&gt;JSDoc is more convenient for commenting + allows automated documentation generation, which is what it was designed for anyway&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But if that's not your thing — you can totally use TypeScript syntax, it's perfectly compatible with &lt;strong&gt;JSDA&lt;/strong&gt; principles.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI
&lt;/h2&gt;

&lt;p&gt;When working with AI assistants in development, we face one problem that for some reason isn't talked about much yet. AI tends to reproduce popular approaches and patterns without deep analysis of their efficiency and applicability in a specific case. AI is still pretty bad at that simplification thing I mentioned earlier. React became too bloated and slow? Doesn't matter — AI will suggest using it simply because there are more code examples online. In this regard, AI behaves like a junior developer, unable to take responsibility for architectural decisions that require making choices. Meanwhile, AI itself globally needs token consumption optimization, and this could well be reflected in the technologies we use. The simplicity of our solutions, including reducing the entities we operate with, should positively influence this situation. That's why it's important to create new minimalistic patterns like JSDA, which should overall have a positive impact on the industry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;p&gt;Until now, I've been talking about JSDA as a set of conventions and an architectural principle, without tying it to specific tools and not forcing anyone to use anything particular. Of course, for anyone to actually use all this, there needs to be an ecosystem of solutions that let you quickly spin up a project and get first results as fast as possible. That's exactly what I've been working on lately. I'd love to get the community involved and move forward — together.&lt;/p&gt;

&lt;p&gt;Here's what we have at the moment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/rnd-pro/jsda" rel="noopener noreferrer"&gt;JSDA Manifest&lt;/a&gt; – concept description&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/jsda-kit" rel="noopener noreferrer"&gt;JSDA-Kit&lt;/a&gt; – isomorphic toolkit for working with the JSDA stack&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/@symbiotejs/symbiote" rel="noopener noreferrer"&gt;Symbiote.js&lt;/a&gt; – library for efficient work with Web Components, enabling very flexible HTML handling&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rnd-pro/jsda-template" rel="noopener noreferrer"&gt;JSDA repository template for quick start&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of everything listed, Symbiote.js is the most "mature" project—the rest is in active development, but you can try it out right now.&lt;/p&gt;

&lt;p&gt;Also, we are working on a specialized CDN that, in addition to preliminary bundling and minification of modules, will automatically issue ready files in the final format (HTML, CSS, SVG, Markdown, JSON and so on).&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>jamstack</category>
      <category>javascript</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>Zen of Image Publishing</title>
      <dc:creator>Alex M</dc:creator>
      <pubDate>Tue, 18 Mar 2025 14:23:05 +0000</pubDate>
      <link>https://forem.com/foxeyes/zen-of-image-publishing-kb</link>
      <guid>https://forem.com/foxeyes/zen-of-image-publishing-kb</guid>
      <description>&lt;p&gt;Hello! Today I'd like to talk about the problem of publishing images. Why talk about this at all? Every day, millions of people publish millions, or even billions of images - what's there to discuss? Surely for a web developer, especially an experienced one, this isn't a problem at all. Well, not quite.&lt;/p&gt;

&lt;p&gt;I'll start by describing the perspective from which I personally look at this issue. Perhaps this will add some color to the technical narrative.&lt;/p&gt;

&lt;p&gt;I'm a full-stack developer who specializes in projects with a significant R&amp;amp;D focus. I often need to publish side artifacts of my work, such as web pages with documentation, technical articles, interactive demos, prototypes and experiments, promotional pages, and so on. Naturally, all of this needs to be illustrated with interface screenshots, diagrams, various render examples, and other cat photos.&lt;/p&gt;

&lt;p&gt;At the same time, I prefer approaches and practices that minimize context switching and the proliferation of entities I have to interact with. This allows me to be more effective. I write docs and articles directly in my IDE, in markdown format, and store them in git. Building and publishing to the web happens automatically through git hooks and actions. This makes collaboration with technical colleagues comfortable, and our common business processes more lightweight. Ideally, I want images to be part of this zen too. But...&lt;/p&gt;

&lt;p&gt;The first "chicken and egg" question: to place an image on a page, you first need to get its URL. The simplest way, at first glance, is a relative path. You simply save the image somewhere in your project structure and publish it along with other files.&lt;/p&gt;

&lt;h2&gt;
  
  
  CDN
&lt;/h2&gt;

&lt;p&gt;We don't want to store images in git. I hope you don't either. Especially if there are many of them. Because it increases repository size, slows down cloning and synchronization, increases build time, and so on. We want the images to be separate, somewhere in the cloud. Preferably in a special CDN, to avoid burning extra traffic from our main server and to speed up page loading for our respected readers and users. Using such CDNs has long been an established best practice.&lt;/p&gt;

&lt;p&gt;We face a new question: how do we reconcile our lightweight workflow with the need to interact with a separate external service? After all, we still need convenient collaboration and automation, and that feeling of superiority over those who do everything manually through various inconvenient third-party UIs...&lt;/p&gt;

&lt;p&gt;What else needs to be considered: CDN services typically create a flat storage structure where there's only a unique identifier for the uploaded asset and nothing more. Yes, often you can get additional metadata through a separate request using your API access key, but this isn't always convenient or applicable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adaptivity
&lt;/h2&gt;

&lt;p&gt;In today's world, it's not enough to just insert an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag with a &lt;code&gt;src="..."&lt;/code&gt; attribute. We need to take care of users with different screens having different pixel densities. Otherwise, some will see "blur" while others will download more useless megabytes (which we'll pay for). We also need to take care of the optimal format (webp, avif, etc.). And on top of everything else, we need different image sizes simultaneously for different possible layouts (mobile, desktop, etc.).&lt;/p&gt;

&lt;p&gt;It's good that CDN services handle the preparation of multiple variants, and we don't need to worry about their generation. But the HTML insertion code that takes everything into account becomes quite cumbersome, and we don't want to write it manually each time. Instead, we want to have a convenient tool that will do this automatically, in accordance with our settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dream Web Media Asset Management
&lt;/h2&gt;

&lt;p&gt;So, let me describe my ideal solution within the described goals. Here's an approximate list of what I want:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Collaborative work with the project's media collection through Git (but without uploading original images to the repository)&lt;/li&gt;
&lt;li&gt;Automatic synchronization of local media directory with CDN (Like in Dropbox, roughly: drop files in a folder, and they automatically fly to the cloud)&lt;/li&gt;
&lt;li&gt;Mandatory support for local directory structure (mapping nested folder addresses to file ID in CDN)&lt;/li&gt;
&lt;li&gt;Generation of HTML insertion code with support for adaptiveness, lazy loading, and other modern features (srcset, sizes, loading="lazy")&lt;/li&gt;
&lt;li&gt;Ability to sort and filter images by metadata&lt;/li&gt;
&lt;li&gt;Ability to view and edit cloud collections without the need for all process participants to have local file copies&lt;/li&gt;
&lt;li&gt;Ability to download and save local copies when needed&lt;/li&gt;
&lt;li&gt;Storage of upload data in a format convenient for use in CI/CD pipelines and any other automatic processing (JSON)&lt;/li&gt;
&lt;li&gt;Locally available web management interface without the need to create an additional account and authorize in a separate service&lt;/li&gt;
&lt;li&gt;Binding cloud collections to projects, as well as the ability to use shared resources across different projects&lt;/li&gt;
&lt;li&gt;Solution lightness. Absence of endless dependency lists. Simplicity for auditing and modifications&lt;/li&gt;
&lt;li&gt;Independence from CDN service provider&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A Bit More Personal History
&lt;/h2&gt;

&lt;p&gt;I've been dealing with the technical side of this image kitchen for a long time. Once, I led a startup: we were making a service for publishing 360-photos. 360-photos is an interactive display method that allows you to view an object from all sides using a sequence of photos taken from different angles. You've probably seen this on some online store pages where products can be rotated with the mouse. That startup didn't survive, but it left behind rich experience applicable in many aspects. A peculiarity of that project was uploading a VERY large number of images by users: more than a hundred source photos might be needed for just one interactive publication.&lt;/p&gt;

&lt;p&gt;Some time later, I was responsible for developing a set of widgets for uploading and displaying files at a company - a specialized CDN provider, one of those I mentioned above.&lt;/p&gt;

&lt;p&gt;Besides this, I constantly experiment and publish something, both for my current projects and for fun. And despite all my experience, I haven't found a ready-made solution that would fully satisfy both myself and my team. At one perfect moment, we finally got fed up with this, and we decided to make our own.&lt;/p&gt;

&lt;h2&gt;
  
  
  CIT - Cloud Images Toolkit
&lt;/h2&gt;

&lt;p&gt;I don't want to be accused of self-promotion, so I'll immediately clarify that we're talking about a free Open Source tool that isn't monetized in any way. My main goal is to share with the audience the story of a specific solution, as well as the approaches that we, as a team, developed in the process. Goal number two is to try to get feedback and motivation for further development of a useful tool.&lt;/p&gt;

&lt;p&gt;So, CIT (Cloud Images Toolkit) is a web developer's workspace tool that allows automating the process of working with media content in web projects. It includes a utility for synchronizing a local directory with CDN, as well as a web interface for viewing and managing image collections.&lt;/p&gt;

&lt;p&gt;Project GitHub link: &lt;a href="https://github.com/rnd-pro/cloud-images-toolkit" rel="noopener noreferrer"&gt;https://github.com/rnd-pro/cloud-images-toolkit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the current stage, CIT is adapted to work with the Cloudflare Images CDN service but is, in principle, independent of it. If there's interest, we can add built-in support for any other providers. But adapting it for your service is not a complicated matter, and you can add it yourself if needed. Later I plan to write a simple instruction on how to do this in Node.js.&lt;/p&gt;

&lt;p&gt;Installation:&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; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; cloud-images-toolkit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configuration (cit-config.json) in the project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"syncDataPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./cit-sync-data.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"imsDataPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./ims-data.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"imgSrcFolder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./cit-store/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"apiKeyPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./CIT_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"projectId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;YOUR_PROJECT_ID&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"imgUrlTemplate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://&amp;lt;YOUR_DOMAIN&amp;gt;/images/{UID}/{VARIANT}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"previewUrlTemplate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://&amp;lt;YOUR_DOMAIN&amp;gt;/images/{UID}/{VARIANT}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"uploadUrlTemplate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.cloudflare.com/client/v4/accounts/{PROJECT}/images/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fetchUrlTemplate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.cloudflare.com/client/v4/accounts/{PROJECT}/images/v1/{UID}/blob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"removeUrlTemplate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.cloudflare.com/client/v4/accounts/{PROJECT}/images/v1/{UID}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"variants"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"120"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"320"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"640"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"860"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1024"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1200"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2048"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"imgTypes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jpeg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gif"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"svg"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"wsPort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"httpPort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8081&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What to pay attention to here:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;imgSrcFolder&lt;/code&gt; - path to the local directory with images, which should be added to &lt;code&gt;.gitignore&lt;/code&gt;, along with the file specified in &lt;code&gt;apiKeyPath&lt;/code&gt;;&lt;/p&gt;

&lt;p&gt;Image size variants and their formats are set in the config. During synchronization, CIT automatically generates all variants for each image using URL templates. It's important that your CDN supports automatic resizing and format conversion. In the case of Cloudflare Images, this is done in the service settings. Variant names should contain the image size in pixels, or the keyword &lt;code&gt;max&lt;/code&gt; for the original. For example, if your template uses an image with a width of &lt;code&gt;320px&lt;/code&gt;, then you need to add variant &lt;code&gt;320&lt;/code&gt; and variant &lt;code&gt;640&lt;/code&gt; for screens with double pixel density.&lt;/p&gt;

&lt;p&gt;Mapping of the local directory structure is implemented through a string containing the local path to the image. For filtering images in the UI, its substring is used, so it's desirable to ensure that folder names are unique where possible and the overall collection structure is meaningful.&lt;/p&gt;

&lt;h2&gt;
  
  
  IMS - Interactive Media Spots
&lt;/h2&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.amazonaws.com%2Fuploads%2Farticles%2F15h4uoqf1gk74hxbtfh5.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.amazonaws.com%2Fuploads%2Farticles%2F15h4uoqf1gk74hxbtfh5.png" alt="IMS Interactive Media Spots" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CIT supports generating interactive widgets using your cloud images. For this, the OpenSource (MIT) widget library - IMS (&lt;a href="https://github.com/rnd-pro/interactive-media-spots" rel="noopener noreferrer"&gt;https://github.com/rnd-pro/interactive-media-spots&lt;/a&gt;) is used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If you have questions about implementation and usage - write, I'll be happy to answer them in the comments. The project is very young and is currently actively used as our internal working tool, so, of course, I'll be glad for any feedback, stars on GitHub, and suggestions for improvement or collaboration.&lt;/p&gt;

&lt;p&gt;And of course, we have plenty of our own plans for developing the tool. For example, built-in image generation using AI, prompt management, auto-generation of descriptions (alt) for better SEO, support for video collections (streaming video), a dashboard section for working with IMS object collections, splitting the media collection description file into parts and managing different collections... And so on.&lt;/p&gt;

&lt;p&gt;Thank you for your attention.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Symbiote.js 2.0</title>
      <dc:creator>Alex M</dc:creator>
      <pubDate>Wed, 17 Jan 2024 15:37:41 +0000</pubDate>
      <link>https://forem.com/symbiotejs/symbiotejs-20-2mj8</link>
      <guid>https://forem.com/symbiotejs/symbiotejs-20-2mj8</guid>
      <description>&lt;p&gt;I'm happy to announce the release of a new version of the wonderful front-end library &lt;a href="https://symbiotejs.org/" rel="noopener noreferrer"&gt;Symbiote.js&lt;/a&gt;! Never heard of it? It's time to get acquainted.&lt;/p&gt;

&lt;p&gt;Symbiote is a compact but very powerful library for creating web components and applications based on them. Yes, I know, we already have React, Vue, Svelte, LitElement and more. And maybe it’s not very clear why delve into anything else... But don’t rush to conclusions, the Symbiote has something to offer you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concept
&lt;/h2&gt;

&lt;p&gt;Unlike many popular solutions, Symbiote.js takes the path of extending standard browser APIs. It is NOT trying to replace the web platform and become a new meta-platform, to create a new syntax, or reinvent the wheel in any other way. It does not require special compilers, parsers or special environment tools. The symbiote only needs a browser and JavaScript, but otherwise it is absolutely agnostic and can work both on its own and in combination with many popular and familiar technologies.&lt;/p&gt;

&lt;p&gt;Also, a feature of Symbiote.js is that components created with it, use the DOM as the basis of their runtime. They are capable of analyzing the actual context of their use and can determine for themselves how to behave in each specific case. This makes Symbiote.js an excellent choice for creating widgets, organizing micro-frontends, creating meta applications and UI component libraries.&lt;/p&gt;

&lt;p&gt;It is also worth noting that Symbiote.js is very flexible and extensible. It does not hide entities behind a thick layer of opaque abstractions, but, on the contrary, makes it possible to conveniently interact with them. If you are familiar with the DOM API, you already know almost everything you need and can easily create your own base class for application components with the functionality you need.&lt;/p&gt;

&lt;p&gt;Symbiote.js is the answer to the question: - what can be squeezed out of standard HTML, JavaScript and CSS? And this answer is - You can squeeze out a lot. More than bare React and its closest, in terms of popularity, competitors can do.&lt;/p&gt;

&lt;p&gt;In order not to be unfounded, I will give an example with such a difficult topic as SSR (Server Side Rendering). Since Symbiote templates are just HTML, which can be directly parsed by the browser without any additional processing, you can generate the base document markup for your Symbiote application with almost anything: from various CMS and JAMStack generators, to using simple template literals in Node.js or even static files directly. All you need to further “hydrate” (revitalize) the result is one single flag in the code of your wrapper component: &lt;code&gt;ssrMode = true&lt;/code&gt;. That's all! There is no need to pre-generate HTML from JSX templates, no special placeholders and no magical “server components”. Here you can see how it works live: &lt;a href="https://symbiotejs.org/2x/playground/ssr-hydration/" rel="noopener noreferrer"&gt;https://symbiotejs.org/2x/playground/ssr-hydration/&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  A simple example
&lt;/h2&gt;

&lt;p&gt;Here I will give a basic example from the official documentation:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Symbiote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://esm.run/@symbiotejs/symbiote&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyComponent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Symbiote&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nx"&gt;init$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;:&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&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;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;MyComponent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
  &amp;lt;h2&amp;gt;{{count}}&amp;lt;/h2&amp;gt;
  &amp;lt;button &lt;/span&gt;&lt;span class="p"&gt;${{&lt;/span&gt;&lt;span class="nl"&gt;onclick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;increment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}&amp;gt;Click me!&amp;lt;/button&amp;gt;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;MyComponent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rootStyles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt;&lt;span class="s2"&gt;`
  my-component {
    color: green;
  }
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;MyComponent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, everything is quite simple and familiar: state initialization, component template, styles, event handlers... No conceptual complexity. You've most likely already encountered all of this, in one form or another.&lt;/p&gt;

&lt;h2&gt;
  
  
  DX
&lt;/h2&gt;

&lt;p&gt;At the heart of Symbiote's DX strategy (Developer Experience) is the idea that the vulgar minimization of characters printed by the developer does not equal convenience in itself. Yes, compact code and minimal boilerplate are very good (and the code of Symbiote applications does not violate general trends), but what is much more important is an understanding of what the developer sees in front of him and a clear mental model behind the development process. This is why, personally, I really don’t like fashionable “black boxes” that hide details beyond their most banal and superficial use. There are ALWAYS problems lurking in these “dark waters”. And the more “magical” your framework is, the more difficult it is to solve these problems. And sometimes, almost all development in such environments comes down to solving problems created in previous iterations.&lt;/p&gt;

&lt;p&gt;In Symbiote.js, almost everything you see should already be familiar to you, directly or indirectly. Unless you're new to frontend. And if you are a beginner, then you can learn the necessary basics on popular sites with documentation on modern specifications, for example &lt;a href="https://developer.mozilla.org" rel="noopener noreferrer"&gt;MDN&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Patterns
&lt;/h2&gt;

&lt;p&gt;This section is directly related to the previous one. To implement reactivity and work with data, Symbiote uses an extremely simple and understandable pub/sub pattern (Publish–subscribe). Most changes happen synchronously, and surprise surprise, this works, in most cases, faster than the approach of accumulating changes and then rendering asynchronously. Much faster, judging by the benchmarks.&lt;/p&gt;

&lt;p&gt;In many ways, Symbiote follows principles that favor the creation of a Loosely Coupled Architecture, which is hardwired into the DNA of the web platform. The Symbiote encourages you to create business abstractions (independent components) rather than abstractions above the platform and alternatives to native APIs that further connect your entities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ecosystem
&lt;/h2&gt;

&lt;p&gt;For any core library, an ecosystem of ready-made solutions is important. And often, this is an argument against using something new. And here the Symbiote also has a strong position. The fact is that due to its proximity to the platform, it can easily work with ready-made solutions for this platform and not require any special treatment. I have never had any problems with integrations, and I have already done a lot of them, from working with WebGL, to online code editors, complex UIs for working with graph data, and more.&lt;/p&gt;

&lt;p&gt;Speaking about development environment tools, we have the same picture. An approximate and sufficient setup is described on the project website: &lt;a href="https://symbiotejs.org/#biome" rel="noopener noreferrer"&gt;https://symbiotejs.org/#biome&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;And the Symbiote does a lot of things perfectly on its own and does not require any additional dependencies at all. For example, you don’t need to look for libraries for rendering complex dynamic tables and lists (itemize API does a great job with this), you don’t need external solutions for application localization (there are several very simple and compact ways to do this), you don’t need external state managers (you you can connect them, but Symbiote has everything you need without them, and even more). And so on. Examples can also be seen on the website: &lt;a href="https://symbiotejs.org/#playground" rel="noopener noreferrer"&gt;https://symbiotejs.org/#playground&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;I understand perfectly well that new front-end libs are now of little interest to anyone, in general. But I think Symbiote.js can be seen as an alternative to the black box trend, as a dedicated solution for widgets, and as an example of how the use of modern standards is changing approaches. And therefore, it is worthy of your attention, at least in general terms, for the self development and outlook.&lt;/p&gt;

&lt;p&gt;I often communicate with other developers, conduct interviews and read posts with comments on specialized resources. And I often come across the opinion that there are libraries “for large projects” and “for the soul.” Some use it for their main work, others for personal projects. Many people like Symbiote.js, but most often it is automatically classified as the second category of solutions for “small” personal projects. It's hard for me to agree with this. Recently, my team completed another, very large project using Symbiote.js and we did not regret our choice for a second.&lt;/p&gt;

&lt;p&gt;Thanks for your attention, and don't be sorry for the stars on &lt;a href="https://github.com/symbiotejs/symbiote.js" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, developers will be very pleased to know that they are not “screaming into the void.”&lt;/p&gt;

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