<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alex M</title>
    <description>The latest articles on DEV Community by Alex M (@foxeyes).</description>
    <link>https://dev.to/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>DEV Community: Alex M</title>
      <link>https://dev.to/foxeyes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/foxeyes"/>
    <language>en</language>
    <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://dev.to/foxeyes/lit-vs-symbiotejs-22gj</link>
      <guid>https://dev.to/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://dev.to/foxeyes/symbiotejs-v3-web-components-with-ssr-in-6kb-10n6</link>
      <guid>https://dev.to/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://dev.to/foxeyes/jsda-is-very-simple-1cfk</link>
      <guid>https://dev.to/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://dev.to/foxeyes/zen-of-image-publishing-kb</link>
      <guid>https://dev.to/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://dev.to/symbiotejs/symbiotejs-20-2mj8</link>
      <guid>https://dev.to/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>
