@symbiotejs/symbiote 3.8.0 → 3.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/llms.txt CHANGED
@@ -8,7 +8,6 @@ Symbiote.js extends HTMLElement natively. State changes update the DOM synchrono
8
8
 
9
9
  - [Full documentation (single file)](https://rnd-pro.com/symbiote/llms-full.txt): Complete merged reference — paste this into any AI tool for full context
10
10
  - [README](https://github.com/symbiotejs/symbiote.js/blob/main/README.md): Overview, quick start, and feature summary
11
- - [Changelog](https://github.com/symbiotejs/symbiote.js/blob/main/CHANGELOG.md): Version history
12
11
 
13
12
  ## Core concepts
14
13
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@symbiotejs/symbiote",
4
- "version": "3.8.0",
4
+ "version": "3.8.2",
5
5
  "description": "Symbiote.js - zero-dependency close-to-platform frontend library to build super-powered web components",
6
6
  "author": "team@rnd-pro.com",
7
7
  "license": "MIT",
@@ -24,7 +24,6 @@
24
24
  "scripts/*",
25
25
  "docs/*",
26
26
  "README.md",
27
- "CHANGELOG.md",
28
27
  "LICENSE",
29
28
  "llms.txt",
30
29
  "llms-full.txt"
@@ -11,8 +11,6 @@ const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
11
11
  const EXCLUDE_DOCS = new Set([
12
12
  'README.md', // index only, no content
13
13
  'ecosystem.md', // IDE/tooling setup, not API
14
- 'lit-vs-symbiote.md', // comparison article
15
- 'migration-2x-to-3x.md', // migration guide
16
14
  'llms-index.md', // llms.txt template — not merged into llms-full.txt
17
15
  ]);
18
16
 
@@ -12,12 +12,12 @@ export class ToolDescriptor {
12
12
  name: string;
13
13
  description: string | ((owner?: any) => string);
14
14
  inputSchema: any;
15
- fn: (args?: any, owner?: any, event?: Event) => any;
16
- when: () => boolean;
15
+ when: (owner?: any) => boolean;
17
16
  deps: string[];
18
17
  exposedTo: string[];
19
18
  annotations: any;
20
19
  execute(args?: any, owner?: any, event?: Event): any;
20
+ #private;
21
21
  }
22
22
  export const webMCPRegistry: PubSub<any>;
23
23
  declare namespace _default {
@@ -43,7 +43,7 @@ export type ToolDescriptorOptions = {
43
43
  description?: string | ((owner?: any) => string);
44
44
  inputSchema?: any | ((owner?: any) => any);
45
45
  execute?: (args?: any, owner?: any, event?: Event) => any;
46
- when?: () => boolean;
46
+ when?: (owner?: any) => boolean;
47
47
  deps?: string[];
48
48
  exposedTo?: string[];
49
49
  annotations?: any;
@@ -1 +1 @@
1
- {"version":3,"file":"webmcp.d.ts","sourceRoot":"","sources":["../../core/webmcp.js"],"names":[],"mappings":"AA8nBA,0CALW,GAAG,OACH,MAAM,cACN,cAAc,GACZ,CAAC,mBAAmB,GAAG;IAAC,UAAU,EAAE,eAAe,GAAG,IAAI,CAAA;CAAC,CAAC,GAAG,OAAO,CAAC,mBAAmB,GAAG;IAAC,UAAU,EAAE,eAAe,GAAG,IAAI,CAAA;CAAC,CAAC,CAgC9I;AAGD,uCADY,GAAG,QA0Dd;AAGD,6CADY,GAAG,QAyBd;AAGD,wCADc,mBAAmB,EAAE,CAIlC;AAGD,8CADY,OAAO,QAAQ,QA+B1B;AA1MD;IAEE,sBADY,qBAAqB,EAWhC;IARC,aAAwB;IACxB,gCAhkB6B,GAAG,KAAK,MAAM,EAgkBsB;IACjE,iBAAuD;IACvD,yBAhkBkC,GAAG,UAAU,KAAK,KAAK,GAAG,CAgkBnC;IACzB,YAhkBgB,OAAO,CAgkBC;IACxB,eAA8B;IAC9B,oBAAkC;IAClC,iBAAsC;IASxC,4BAJW,GAAG,UACH,KAAK,GACH,GAAG,CAOf;CACF;AAED,yCAUK;;;;;;;;;;;;UA9mBS,MAAM;UACN,MAAM;;;cAKN,oBAAoB,EAAE;cACtB,OAAO;;;WAKP,MAAM;kBACN,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,MAAM,CAAC;kBAClC,MAAS,CAAC,CAAC,KAAK,CAAC,EAAE,GAAG,QAAW,CAAC;cAClC,CAAC,IAAI,CAAC,KAAQ,EAAE,KAAK,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,KAAK,KAAK,GAAG;WAClD,MAAM,OAAO;WACb,MAAM,EAAE;gBACR,MAAM,EAAE;;;;UAMR,MAAM;SACN,MAAM;aACN,MAAM;eACN,WAAW,GAAG,SAAS;eACvB,MAAM;0BACN,MAAM;iBACN,MAAM;;YAEN,OAAO;kBACP,OAAO;;qBAxCA,eAAe;mBADjB,aAAa"}
1
+ {"version":3,"file":"webmcp.d.ts","sourceRoot":"","sources":["../../core/webmcp.js"],"names":[],"mappings":"AAqoBA,0CALW,GAAG,OACH,MAAM,cACN,cAAc,GACZ,CAAC,mBAAmB,GAAG;IAAC,UAAU,EAAE,eAAe,GAAG,IAAI,CAAA;CAAC,CAAC,GAAG,OAAO,CAAC,mBAAmB,GAAG;IAAC,UAAU,EAAE,eAAe,GAAG,IAAI,CAAA;CAAC,CAAC,CAmC9I;AA2CD,uCADY,GAAG,QA6Bd;AAGD,6CADY,GAAG,QAyBd;AAGD,wCADc,mBAAmB,EAAE,CAIlC;AAGD,8CADY,OAAO,QAAQ,QA+B1B;AA3ND;IAKE,sBADY,qBAAqB,EAWhC;IARC,aAAwB;IACxB,gCAvkB6B,GAAG,KAAK,MAAM,EAukBsB;IACjE,iBAAuD;IAEvD,eAvkBmB,GAAG,KAAK,OAAO,CAukBV;IACxB,eAA8B;IAC9B,oBAAkC;IAClC,iBAAsC;IASxC,4BAJW,GAAG,UACH,KAAK,GACH,GAAG,CAOf;;CACF;AAED,yCAUK;;;;;;;;;;;;UArnBS,MAAM;UACN,MAAM;;;cAKN,oBAAoB,EAAE;cACtB,OAAO;;;WAKP,MAAM;kBACN,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,MAAM,CAAC;kBAClC,MAAS,CAAC,CAAC,KAAK,CAAC,EAAE,GAAG,QAAW,CAAC;cAClC,CAAC,IAAI,CAAC,KAAQ,EAAE,KAAK,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,KAAK,KAAK,GAAG;WAClD,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,OAAO;WACxB,MAAM,EAAE;gBACR,MAAM,EAAE;;;;UAMR,MAAM;SACN,MAAM;aACN,MAAM;eACN,WAAW,GAAG,SAAS;eACvB,MAAM;0BACN,MAAM;iBACN,MAAM;;YAEN,OAAO;kBACP,OAAO;;qBAxCA,eAAe;mBADjB,aAAa"}
package/CHANGELOG.md DELETED
@@ -1,372 +0,0 @@
1
- # Changelog
2
-
3
- ## 3.8.0
4
-
5
- ### Added
6
-
7
- - **Experimental WebMCP support.** New optional `@symbiotejs/symbiote/webmcp` entry point exposes live Symbiote UI actions as native browser WebMCP tools. Import it before rendering participating components, then use `mcpToolMode` for automatic event-handler tools or `ToolDescriptor` for custom descriptions, input schemas, execution, and `when()` visibility.
8
-
9
- - **Component context for agents.** Components can define `componentDescription` as a string or async function. The resolved text is appended to component-owned tool descriptions.
10
-
11
- - **WebMCP lifecycle registration.** Component tools register on render and unregister on DOM removal. Itemized components inherit global `mcpToolMode`, keyed item tools include `_KEY_` context, and popup `^` event bindings can expose ancestor-owned tools with source item context.
12
-
13
- ### Notes
14
-
15
- - This is an experimental release intended for browser builds with native WebMCP support, such as Chrome Canary 150. Publish with the `webmcp` npm tag while the API stabilizes.
16
-
17
- ## 3.7.0
18
-
19
- ### Added
20
-
21
- - Self-Closing Custom Elements
22
-
23
- Now this compact format is supported for tagged templates:
24
- ```js
25
- html`<my-component />`;
26
- ```
27
-
28
- ### Improved
29
-
30
- - Documentation update
31
-
32
- ## 3.5.4
33
-
34
- ### Fixed
35
-
36
- - **SSR: `bindAttributes()` crash in linkedom.** `observedAttributes` is a getter-only property in linkedom — direct assignment threw. Now uses `Object.defineProperty` with a configurable getter.
37
-
38
- - **SSR: `renderCallback()` crash from browser-only APIs.** Components using `IntersectionObserver`, `window.location`, etc. in `renderCallback` crashed the entire SSR process. Now wrapped in try-catch during SSR (`__SYMBIOTE_SSR`); browser-side errors still re-throw normally.
39
-
40
- ## 3.5.2
41
-
42
- ### Fixed
43
-
44
- - **`devMode` flag is now cross-module safe.** Moved to `globalThis.__SYMBIOTE_DEV_MODE` so it works correctly across CDN/importmap module scopes (same pattern as `PubSub.globalStore`).
45
-
46
- ### Improved
47
-
48
- - **`warn.js` — minimal core dispatcher.** Stripped to just `devState`, `warnMsg`, and `errMsg`. All formatting, messages, and styling logic moved to `devMessages.js` via `globalThis.__SYMBIOTE_DEV_LOG` handler. No messages map or `Array.isArray` in core.
49
-
50
- - **Colored console output.** `devMessages.js` now renders styled badges in browser consoles: purple `Symbiote` badge, amber/red code badge, violet `<component-name>`, and dimmed doc links. Falls back to plain text in Node.js.
51
-
52
- - **Doc references in all warnings.** Every message now includes a `→` link to the relevant GitHub docs page.
53
-
54
- - **`devMessages.js` auto-enables `devMode`.** Importing the module sets `globalThis.__SYMBIOTE_DEV_MODE = true` — one import for the full dev experience.
55
-
56
- ## 3.5.0
57
-
58
- ### Added
59
-
60
- - **External dev messages module (`core/devMessages.js`).** All warning and error message strings have been extracted from core files into an optional module. Core files now emit short numeric codes (e.g. `[Symbiote W5]`). Import `@symbiotejs/symbiote/core/devMessages.js` once to get full human-readable messages. This reduces the core bundle size by removing all formatting strings from `PubSub.js`, `Symbiote.js`, `tpl-processors.js`, `AppRouter.js`, `html.js`, and both itemize processors.
61
-
62
- - **`core/warn.js` — lightweight message dispatcher.** Exports `warnMsg(code, ...args)` and `errMsg(code, ...args)`. All core files now use this dispatcher instead of inline `console.warn`/`console.error` calls.
63
-
64
- - **AppRouter: `title` accepts functions.** Route descriptors and `setDefaultTitle()` now accept `() => String` in addition to plain strings. The function is called at navigation time, enabling dynamic or localized page titles:
65
- ```js
66
- AppRouter.setDefaultTitle(() => i18n('app.title'));
67
- AppRouter.initRoutingCtx('R', {
68
- home: { pattern: '/', title: () => i18n('home.title'), default: true },
69
- });
70
- ```
71
-
72
- ## 3.4.7
73
-
74
- ### Fixed
75
-
76
- - **PubSub: computed properties with cross-context deps no longer depend on import order.** When a computed property declared `deps: ['CTX/prop']` and the target context wasn't registered yet, the subscription was silently skipped. Now deferred deps are stored in `PubSub.pendingDeps` and automatically resolved when `registerCtx()` is called.
77
- - **Symbiote: named context access no longer crashes when context is not yet registered.** The `$` proxy, `sub()`, `notify()`, `has()`, and `add()` now handle missing named contexts gracefully (return `undefined`/no-op instead of throwing).
78
- - **Symbiote: template bindings for named contexts are deferred when context is not yet registered.** Previously, `sub()` silently dropped the subscription; now it queues it via `PubSub.pendingDeps` and resolves automatically when `registerCtx()` is called.
79
-
80
- ## 3.4.4
81
-
82
- ### Fixed
83
-
84
- - **SSR: `isVirtual` components.** `renderToString` and `renderToStream` now detect `isVirtual` elements and serialize the replacement template nodes instead of the detached custom element wrapper. Previously, virtual components produced an empty `<tag-name></tag-name>` in SSR output.
85
-
86
- - **SSR: `allowCustomTemplate` with `use-template` attribute.** `DocumentFragment` detection in `render()` used `constructor === DocumentFragment`, which fails in linkedom where `template.content.cloneNode()` returns a fragment with a different internal constructor. Changed to `nodeType === 11` — spec-correct and works in both browser and linkedom. Previously, custom templates were silently ignored during SSR.
87
-
88
- ## 3.4.3
89
-
90
- ### Fixed
91
-
92
- - **SSR: shared context props (`*prop`) with `ctx` attribute.** `getCssData()` attempted `window.getComputedStyle()` during server-side rendering, which is unavailable in linkedom. Now returns `null` immediately when `globalThis.__SYMBIOTE_SSR` is set, skipping all computed CSS reads on the server.
93
-
94
- ### Added
95
-
96
- - **DevMode warning for CSS data bindings in SSR/ISO mode.** When `devMode = true` and the component has `ssrMode` or `isoMode`, a `console.warn` fires for each `bindCssData` call — computed styles are unavailable during SSR, so the init value is used instead.
97
-
98
- ## 3.4.2
99
-
100
- ### Fixed
101
-
102
- - **isoMode: Shadow DOM components with Light DOM slot children.** `isoMode` checked `this.childNodes` to detect SSR content, but for Shadow DOM components Light DOM children are slot content — not server-rendered shadow tree. This caused template rendering to be skipped. Now checks `this.shadowRoot.childNodes` for Shadow DOM components and `this.childNodes` for Light DOM ones.
103
-
104
- ## 3.4.1
105
-
106
- ### Fixed
107
-
108
- - **SSR: `{{prop}}` text-node bindings with class property fallback.** `resolveTextTokens` now checks own class properties and prototype methods when the prop is not in `init$`. Previously, linkedom's `DocumentFragment.textContent` returning `null` caused `txtNodesProcessor` to skip processing, and the serializer fallback only resolved `init$` props — leaving raw `{{prop}}` tokens in SSR output.
109
-
110
- ## 3.4.0
111
-
112
- ### Added
113
-
114
- - **DevMode warning for `{{prop}}` in SSR/ISO mode.** Text-node bindings produce no `bind=` attribute in SSR output, so they render correctly on the server but cannot be hydrated on the client. When `devMode = true` and the component has `ssrMode` or `isoMode` enabled, a `console.warn` now suggests using `${{textContent: 'prop'}}` for hydratable text.
115
-
116
- ## 3.3.9
117
-
118
- ### Improved
119
-
120
- - **DRY itemize processors.** Extracted shared setup logic (element discovery, SSR hydration, class creation, template derivation, attribute cleanup) into `itemizeSetup.js`. Both `itemizeProcessor.js` and `itemizeProcessor-keyed.js` now use the same setup, eliminating code duplication.
121
-
122
- - **Keyed processor SSR support.** `itemizeProcessor-keyed.js` now inherits all SSR hydration fixes: `clientSSR` detection, SSR tag adoption, `isoMode` on items, static template derivation, conditional child clearing, and `initPropFallback`.
123
-
124
- ### Fixed
125
-
126
- - **Keyed processor: live HTMLCollection bug.** Fixed `animateOut` failing when removing multiple items by key — the live `HTMLCollection` shifted indices during removal. Elements are now snapshotted before removal.
127
-
128
- ## 3.3.8
129
-
130
- ### Fixed
131
-
132
- - **SSR hydration: Itemize template derivation.** Dynamically added itemize items now use the original template (parsed from `fnCtx.constructor.template`) instead of SSR-expanded `innerHTML`. This preserves nested custom elements (e.g. icons) in new items added after hydration.
133
-
134
- ## 3.3.7
135
-
136
- ### Fixed
137
-
138
- - **SSR hydration: Itemize list duplication.**
139
- Fixed itemize SSR hydration creating duplicate items. The processor now adopts the existing SSR item tag name and element class, skips the initial subscription fire, and sets `isoMode` on the item class so upgraded elements hydrate their existing content instead of re-rendering.
140
-
141
- ## 3.3.6
142
-
143
- ### Improved
144
-
145
- - **SSR hydration: generic property preservation.**
146
- During SSR/ISO hydration, all primitive-valued bindings (`textContent`, `innerHTML`, `style.*`, `value`, etc.) now skip the initial write, preserving server-rendered DOM. Function bindings (event handlers) and non-null object bindings (component state) still fire immediately. Previously only `textContent` and attribute bindings were preserved.
147
-
148
- - **SSR hydration: Itemize API support.**
149
- Auto-generated itemize item components now inherit `ssrMode`/`isoMode` from the parent. Server-rendered list items are preserved during hydration instead of being cleared and re-created.
150
-
151
- ## 3.3.5
152
-
153
- ### Fixed
154
-
155
- - **AppRouter: SSR context populated with default route.**
156
- `initRoutingCtx()` in SSR now populates the PubSub context with the default route's `route`, `title`, and `options` instead of leaving them as `null`. Components can access `this.$['R/route']` during server rendering.
157
-
158
- ## 3.3.4
159
-
160
- ### Fixed
161
-
162
- - **PubSub context isolation with importmaps.**
163
- `PubSub.globalStore` now uses `globalThis.__SYMBIOTE_PUBSUB_STORE`, so multiple copies of PubSub loaded from different URLs (e.g. via importmap) share the same context registry. Fixes `Router/title` and similar named context bindings not working when AppRouter is resolved separately.
164
-
165
- ### Added
166
-
167
- - **`@symbiotejs/symbiote/full` entry point.**
168
- Re-exports everything from the main entry point plus `AppRouter`, guaranteeing a single PubSub module:
169
- ```js
170
- import Symbiote, { html, css, AppRouter } from '@symbiotejs/symbiote/full';
171
- ```
172
-
173
- - **AppRouter SSR support.**
174
- `AppRouter.initRoutingCtx()` now works in Node.js and linkedom SSR environments — creates the PubSub context without errors, skipping browser-only APIs (`window`, `history`, events). Enables isomorphic code that uses AppRouter on both server and client.
175
-
176
- ## 3.3.0
177
-
178
- ### Added
179
-
180
- - **`isoMode` flag for isomorphic rendering.**
181
- `isoMode = true` enables automatic detection: if the component has children at connect time (server-rendered content), it hydrates existing DOM like `ssrMode`. If no children exist, it renders the template normally. Same component code works for both SSR and client-only scenarios.
182
-
183
- ## 3.2.0
184
-
185
- ### Added
186
-
187
- - **Itemize class property fallback.**
188
- The `itemize` data source property now supports class property fallback, consistent with `domBindProcessor` and `txtNodesProcessor`.
189
-
190
- ### Changed
191
-
192
- - **Utils moved to separate entry point.**
193
- `UID`, `setNestedProp`, `applyStyles`, `applyAttributes`, `create`, `kebabToCamel`, `reassignDictionary` are no longer exported from the main `@symbiotejs/symbiote` entry point. Import from `@symbiotejs/symbiote/utils` instead:
194
- ```js
195
- import { UID, create } from '@symbiotejs/symbiote/utils';
196
- ```
197
- Individual deep imports (`@symbiotejs/symbiote/utils/UID.js`, etc.) continue to work.
198
-
199
- - **`initPropFallback` extracted to shared module.**
200
- Duplicated fallback initialization logic across template processors consolidated into `core/initPropFallback.js`.
201
-
202
- ## 3.1.0
203
-
204
- ### Changed
205
-
206
- - **Class property fallback (generalized).**
207
- Bindings not found in `init$` now fall back to own class properties (checked via `Object.hasOwn`), not just `on*` event handlers. Functions are auto-bound to the component instance. Inherited `HTMLElement` properties are never picked up.
208
- ```js
209
- class MyComp extends Symbiote {
210
- label = 'Click me';
211
- onSubmit() { console.log('submitted'); }
212
- }
213
- ```
214
- Previously only `on*` handlers supported this fallback.
215
-
216
- ## 3.0.0
217
-
218
- ### ⚠️ Breaking Changes
219
-
220
- - **`tplProcessors` → `templateProcessors`.**
221
- The `addTemplateProcessor()` method is removed — use native `Set` methods:
222
- ```js
223
- // 2.x:
224
- this.addTemplateProcessor(myProcessor);
225
- // 3.x:
226
- this.templateProcessors.add(myProcessor);
227
- ```
228
-
229
- - **`AppRouter.applyRoute()` → `AppRouter.navigate()`.**
230
-
231
- - **`AppRouter` removed from main entry point.**
232
- ```js
233
- // 2.x:
234
- import { AppRouter } from '@symbiotejs/symbiote';
235
- // 3.x:
236
- import { AppRouter } from '@symbiotejs/symbiote/core/AppRouter.js';
237
- ```
238
-
239
- - **Computed properties: cross-context requires explicit deps.**
240
- Local computeds (same-context) keep working unchanged. Cross-context now uses object syntax:
241
- ```js
242
- // 2.x — implicit via global scan:
243
- init$ = {
244
- '+total': () => this.$['APP/score'] + this.$.local,
245
- };
246
- // 3.x — explicit deps:
247
- init$ = {
248
- '+total': {
249
- deps: ['APP/score'],
250
- fn: () => this.$['APP/score'] + this.$.local,
251
- },
252
- };
253
- ```
254
-
255
- - **Shared context (`*prop`) simplified.**
256
- Removed `ctxOwner` / `ctx-owner`. First-registered value always wins. Dev-mode warnings when `*prop` used without `ctx` attribute.
257
-
258
- ### Performance
259
-
260
- - **Computed properties: per-instance dependency tracking.**
261
- Replaced global scan with per-instance auto-tracking. Up to **676× faster** for local computeds, **14×** for sparse scenarios.
262
-
263
- - **Microtask batching.**
264
- All computed recalculation and internal scheduling uses `queueMicrotask` instead of `setTimeout`.
265
-
266
- - **`#parseProp` fast path.**
267
- `charCodeAt` checks skip full string parsing for common local properties — the most frequent case.
268
-
269
- - **`$` proxy inlined fast path.**
270
- Both `get` and `set` traps bypass `#parseProp` entirely for local props.
271
-
272
- - **`PubSub.pub()` direct value pass.**
273
- Eliminates redundant `read()` on every state update.
274
-
275
- - **Dev warnings gated by `PubSub.devMode`.**
276
- Type-mismatch checks have zero overhead in production.
277
-
278
- - **`txtNodesProcessor` early exit.**
279
- Skips text-node scanning when template contains no `{{` tokens.
280
-
281
- - **`localCtx` direct construction.**
282
- Uses `new PubSub({})` instead of `PubSub.registerCtx({})`, bypassing global store for component-scoped state.
283
-
284
- - **`hasOwnProperty` → `in` operator** across `PubSub` internals.
285
-
286
- - **`itemizeProcessor-keyed.js` — optional optimized itemize processor.**
287
- Drop-in replacement with reference-equality fast paths and key-based reconciliation. Up to **3× faster** for appends, **2×** for in-place updates, **32×** for no-ops:
288
- ```js
289
- import { itemizeProcessor } from '@symbiotejs/symbiote/core/itemizeProcessor-keyed.js';
290
- import { itemizeProcessor as defaultProcessor } from '@symbiotejs/symbiote/core/itemizeProcessor.js';
291
-
292
- class BigList extends Symbiote {
293
- constructor() {
294
- super();
295
- this.templateProcessors.delete(defaultProcessor);
296
- this.templateProcessors = new Set([itemizeProcessor, ...this.templateProcessors]);
297
- }
298
- }
299
- ```
300
-
301
- ### Added
302
-
303
- - **Server-side rendering (`node/SSR.js`).**
304
- `SSR` class with static methods for server-side rendering. `SSR.processHtml(html)` renders any HTML string with embedded components. `SSR.renderToString(tagName, attrs?)` renders a single component. `SSR.renderToStream(tagName, attrs?)` async generator yields HTML chunks. Declarative Shadow DOM with inlined styles. rootStyles emitted as `<style>` tags in light DOM. Light DOM content preserved. Binding attributes preserved for client hydration. `linkedom` is an optional peer dependency.
305
- ```js
306
- import { SSR } from '@symbiotejs/symbiote/node/SSR.js';
307
- await SSR.init();
308
- await import('./my-app.js');
309
- let html = await SSR.processHtml('<my-app>content</my-app>');
310
- SSR.destroy();
311
- ```
312
-
313
- - **Declarative Shadow DOM hydration (`ssrMode`).**
314
- `ssrMode = true` hydrates pre-rendered content (light DOM + `<template shadowrootmode>`). Template injection skipped; bindings attach to existing DOM. Shadow styles applied via `adoptedStyleSheets`.
315
-
316
- - **Exit animation hook (`animateOut`).**
317
- Sets `[leaving]` attribute, waits for CSS `transitionend`, removes element. Integrated into itemize processors — items with transitions animate out automatically. Enter animations use `@starting-style`.
318
-
319
- - **`AppRouter`: path-based routing.**
320
- Routes with `pattern` key use path-based URLs with `:param` extraction:
321
- ```js
322
- AppRouter.initRoutingCtx('R', {
323
- home: { pattern: '/', title: 'Home', default: true },
324
- user: { pattern: '/users/:id', title: 'User' },
325
- });
326
- // /users/42 → { route: 'user', options: { id: '42' } }
327
- ```
328
- Query-string routes remain fully backward compatible.
329
-
330
- - **Route guards — `AppRouter.beforeRoute(fn)`.**
331
- Return `false` to cancel, a route string to redirect:
332
- ```js
333
- let unsub = AppRouter.beforeRoute((to, from) => {
334
- if (!isAuth && to.route === 'settings') return 'login';
335
- });
336
- ```
337
-
338
- - **Lazy loaded routes.**
339
- `load` in route descriptors for dynamic imports, cached automatically:
340
- ```js
341
- { pattern: '/settings', load: () => import('./pages/settings.js') }
342
- ```
343
-
344
- - **Class property fallback.**
345
- Bindings not in `init$` fall back to own class properties/methods:
346
- ```js
347
- label = 'Click me';
348
- onSubmit() { console.log('submitted'); }
349
- ```
350
-
351
- - **`Symbiote.devMode` flag.**
352
- Enables verbose warnings (unresolved bindings, tag names, available contexts). Also wires `PubSub.devMode`.
353
-
354
- - **`reg()` returns the class itself.**
355
- Enables `export default MyComponent.reg('my-component')`.
356
-
357
- - **`destructionDelay` instance property.**
358
- Configurable delay (default `100`ms) before cleanup in `disconnectedCallback`.
359
-
360
- - **Trusted Types support.**
361
- Template writes use `'symbiote'` Trusted Types policy when available. Zero overhead otherwise.
362
-
363
- - **`this` in template detection.**
364
- `html` fires `console.error` when `${this.…}` used in template (templates are context-free).
365
-
366
- - **AI_REFERENCE.md** — comprehensive context file for code assistants.
367
-
368
- ### Fixed
369
-
370
- - `css()` trailing `undefined` when no interpolations exist.
371
- - `new DocumentFragment()` → `document.createDocumentFragment()` for linkedom compatibility.
372
- - `txtNodesProcessor` null check for `fr.textContent` in SSR environments.
@@ -1,200 +0,0 @@
1
- # Lit vs Symbiote.js
2
-
3
- > [!NOTE]
4
- > Both libraries build on Web Components. Lit is the most popular choice backed by Google. So why consider Symbiote.js?
5
-
6
- This comparison is written from the Symbiote.js perspective but aims to be technically fair. Where Lit does something well, we say so.
7
-
8
- ## At a Glance
9
-
10
- | | **Symbiote.js 3.x** | **Lit 3.x** |
11
- |--|--|--|
12
- | **Core size** (brotli) | ~7.3 kb | ~5.1 kb |
13
- | **Full bundle** (brotli) | ~11.0 kb (core + router + WebMCP export) | ~5.1 kb + addons |
14
- | **Dependencies** | 0 runtime | 0 runtime |
15
- | **Shadow DOM** | Opt-in per component | On by default |
16
- | **SSR** | Built-in (`node/SSR.js`) | Experimental (`@lit-labs/ssr`) |
17
- | **Routing** | Built-in (optional import) | Not included |
18
- | **State management** | Built-in (PubSub, contexts) | Separate package (`@lit/context`) |
19
- | **Build step** | Not required | Not required |
20
- | **TypeScript** | JSDoc + `.d.ts` | Decorators-first API |
21
-
22
- > Lit's base size is ~2 kb smaller, but Symbiote's core already includes state management, list rendering (Itemize API), computed properties, and exit animations — features that in the Lit ecosystem require additional packages.
23
-
24
- ## Templates
25
-
26
- This is the biggest architectural difference.
27
-
28
- ### Lit
29
-
30
- Templates use the `html` tagged template literal with JavaScript expressions bound to the current component's `this`:
31
-
32
- ```js
33
- html`<p>${this.message}</p>`
34
- html`<button @click=${this.onClick}>Click</button>`
35
- ```
36
-
37
- - Expressions are tied to the component's render method and its `this` scope.
38
- - Templates are re-evaluated on every render cycle.
39
- - The `html` function does not produce a plain HTML string — it returns a `TemplateResult` object processed by Lit's internal rendering pipeline.
40
- - Templates cannot be separated from the JavaScript execution context.
41
-
42
- ### Symbiote.js
43
-
44
- Templates are plain HTML strings with declarative binding attributes. They are context-free — they do not reference `this`:
45
-
46
- ```js
47
- html`<p>{{message}}</p>`
48
- html`<button ${{onclick: 'onClick'}}>Click</button>`
49
- ```
50
-
51
- Key advantages:
52
-
53
- 1. **Runtime-independent** — templates are standard HTML strings. They can live in separate files, arrive from an API response, or be embedded directly in the page markup.
54
- 2. **Decoupled from `this`** — bindings use property name strings, not direct references. A component provides data and handlers; a template defines presentation independently.
55
- 3. **Multiple template sources** — the same component can use different templates from the document via `use-template` attribute, enabling complete separation of data logic and representation.
56
- 4. **Dual-mode interpolation** — `html` supports both reactive bindings (objects) and standard string interpolation in a single template.
57
- 5. **Loose-coupling alternative** — templates can be written as pure HTML without any JavaScript context at all: `<div bind="textContent: myProp"></div>`.
58
- 6. **SSR-transparent** — the `html` function produces clean HTML with `bind=` attributes that can be rendered server-side by any template engine, or by Symbiote's own SSR module, and hydrated on the client. No special markers, no `<!--lit-part-->` comments.
59
-
60
- ## Server-Side Rendering
61
-
62
- ### Lit
63
-
64
- SSR is experimental and lives in `@lit-labs/ssr`. A working setup requires:
65
-
66
- - `@lit-labs/ssr` — server-side renderer
67
- - `@lit-labs/ssr-client` — client-side hydration support (including `lit-element-hydrate-support.js`)
68
- - `@lit-labs/ssr-dom-shim` — DOM polyfills for the server
69
-
70
- Important constraints:
71
- - **Load order matters** — `lit-element-hydrate-support.js` must be loaded _before_ the `lit` module and all components.
72
- - **Hydration mismatches** — server and client renders must produce identical output, or errors occur.
73
- - **No async support** — Lit SSR has no built-in mechanism to wait for async results before serializing.
74
- - **Comment-based markers** — the output contains `<!--lit-part-->` and `<!--lit-node-->` comments for template re-association.
75
-
76
- ### Symbiote.js
77
-
78
- SSR is built in (one module, `node/SSR.js`) with `linkedom` as an optional peer dependency:
79
-
80
- ```js
81
- import { SSR } from '@symbiotejs/symbiote/node/SSR.js';
82
-
83
- await SSR.init();
84
- await import('./my-app.js');
85
-
86
- let result = await SSR.processHtml('<my-app></my-app>');
87
- SSR.destroy();
88
- ```
89
-
90
- Key differences:
91
- - **1 module** — no separate client/server packages.
92
- - **No hydration mismatches** — the server produces HTML with `bind=` attributes. The client reads those attributes and attaches reactivity. There's no diffing, so there's nothing to mismatch.
93
- - **Streaming** — `SSR.renderToStream()` yields HTML chunks for lower TTFB.
94
- - **`isoMode`** — one flag makes a component isomorphic: hydrates if server content exists, renders from template otherwise. No conditional logic.
95
- - **Clean output** — no framework markers in the HTML.
96
- - **CSP nonce support** — pass `{ nonce }` to add nonce attributes to generated `<style>` tags.
97
-
98
- ## Data Flow and State Management
99
-
100
- ### Lit
101
-
102
- - **Reactive properties** — declared with `@property()` decorator. Changes trigger re-render cycle.
103
- - **Internal state** — `@state()` decorator for private reactive properties.
104
- - **Data sharing** — `@lit/context` package implements the W3C Context Community Protocol. Uses `@provide`/`@consume` decorators and DOM events under the hood. This is a separate install.
105
- - **No global state** — Lit does not include global state management; you bring your own (Redux, MobX, signals).
106
-
107
- ### Symbiote.js
108
-
109
- Symbiote provides a layered data context system built in — no extra packages:
110
-
111
- | Context | Token | Description |
112
- |---------|-------|-------------|
113
- | **Local** | `myProp` | Component's own reactive state |
114
- | **Pop-up (`^`)** | `^parentProp` | Walks up the DOM tree to find the nearest ancestor with the property. Works like CSS cascade |
115
- | **Shared (`*`)** | `*sharedProp` | Components with the same `ctx` attribute share state. No parent component or prop drilling needed |
116
- | **Named** | `APP/myProp` | Global named data contexts accessible by key from anywhere |
117
- | **CSS Data** | `--my-var` | Initialize component state from CSS custom property values |
118
- | **Computed** | `+sum` | Reactive derived state with microtask batching |
119
-
120
- The outstanding simplicity here is that a component can bind directly to any external data context without adding a single line of component logic. Just use the prefix in the template — `{{APP/user}}`, `{{^parentTitle}}`, `{{*sharedCount}}` — and the binding is established. No decorator setup, no provider/consumer wiring, no subscription boilerplate. The template _is_ the wiring.
121
-
122
- In Lit, achieving the same patterns requires a combination of `@property()` declarations, `@provide`/`@consume` decorators from a separate `@lit/context` package, custom events, and often an external state management library.
123
-
124
- ## CSS and Styling
125
-
126
- ### Lit
127
-
128
- - Shadow DOM is the default — styles are scoped and isolated.
129
- - Styles are defined in the component class via `static styles = css`...``.
130
- - CSS custom properties cross shadow boundaries.
131
- - Global/document-level styles do not reach shadow DOM internals.
132
- - Opting out of Shadow DOM requires `createRenderRoot() { return this; }` — an escape hatch, not a first-class feature.
133
-
134
- ### Symbiote.js
135
-
136
- - **Shadow DOM is opt-in** — Light DOM is the default. Use shadow only where isolation is needed.
137
- - Two style interfaces: `rootStyles` (Light DOM, adopted stylesheets) and `shadowStyles` (Shadow DOM). Combinable on the same component.
138
- - **CSS Data Binding** — component state can be initialized directly from CSS custom properties. This enables CSS-driven configuration: themes, layout parameters, localization strings — all without touching JavaScript.
139
- - **Context from CSS** — the `--ctx` custom property can assign shared context names via the CSS cascade, enabling layout-driven component grouping.
140
- - The `css` helper function returns `CSSStyleSheet` instances with optional processing pipeline (`css.useProcessor()`).
141
-
142
- The opt-in Shadow DOM approach is important for widgets and micro-frontends: it lets embedding applications style components normally when isolation is not desired, while still supporting full encapsulation when it is.
143
-
144
- ## Loose Coupling and Micro-Frontends
145
-
146
- This is where Symbiote.js was specifically designed to excel.
147
-
148
- Lit components are standard custom elements and work in any framework, but the library itself doesn't provide special architecture for loose coupling. Template `this`-binding, Shadow DOM by default, and decorator-heavy API create a tightly coupled component model.
149
-
150
- Symbiote.js was built for agnostic, loosely coupled systems:
151
-
152
- - **Templates decouple data from presentation** — the same component can render entirely different UIs via `use-template`.
153
- - **Pop-up binding** — child components access parent data through the DOM hierarchy, like CSS cascading. No import dependencies, no prop drilling.
154
- - **Shared context** — unrelated components cooperate by sharing the same `ctx` name. No wiring, no parent orchestrator.
155
- - **CSS-driven configuration** — components can be configured purely through CSS, without JavaScript.
156
- - **Hybrid rendering** — some components can be server-only, some client-only, some isomorphic — in the same application. Data contexts can mirror this: same API shape on server (database-backed) and client (API-backed).
157
- - **No-JS / Low-code** — components can be fully configured via HTML attributes and CSS, enabling systems where non-developers assemble UIs without writing JavaScript.
158
-
159
- ## Build Tooling
160
-
161
- Both libraries work without a build step and with any standard bundler.
162
-
163
- Lit's developer experience leans on TypeScript decorators (`@property()`, `@state()`, `@customElement()`), which benefit from a TypeScript build step. The library itself runs without one, but the idiomatic style assumes a compiler.
164
-
165
- Symbiote.js relies on standard JavaScript — ESM imports, class fields, template literals. No decorators, no compiler magic. It supports modern `importmap`-based dependency sharing with CDN imports and works with any common-purpose bundler (esbuild, Rollup, etc.) when needed.
166
-
167
- ## Built-in Routing
168
-
169
- Lit does not include a router. Third-party solutions or custom implementations are needed.
170
-
171
- Symbiote.js ships with `AppRouter` — a built-in SPA router (optional import) with:
172
- - Path-based routes with `:param` extraction
173
- - Route guards (`beforeRoute()`)
174
- - Lazy loading (`load: () => import(...)`)
175
- - SSR-safe (creates PubSub context on server, browser APIs skipped automatically)
176
- - Dynamic i18n-ready titles
177
-
178
- ## Summary
179
-
180
- | Aspect | Symbiote.js | Lit |
181
- |--------|-------------|-----|
182
- | **Templates** | Context-free HTML strings, multiple sources, loose coupling | JS-bound tagged templates, tied to `this` |
183
- | **SSR** | Built-in, zero mismatches, streaming, isomorphic mode | Experimental, 3 packages, load-order constraints, mismatch risk |
184
- | **State** | Built-in layered contexts (local, pop-up, shared, named, CSS, computed) | Reactive properties + `@lit/context` (separate package) + external libs |
185
- | **Shadow DOM** | Opt-in | Default |
186
- | **Styling** | `rootStyles` + `shadowStyles` + CSS Data Binding | Shadow-scoped `static styles` |
187
- | **Routing** | Built-in `AppRouter` | None |
188
- | **Bundle** | ~7.3 kb core, ~11.0 kb full | ~5.1 kb core + addons |
189
- | **Architecture** | Loose coupling, micro-frontends, no-JS config | Component-first, decorator-driven |
190
- | **Ecosystem** | Smaller community, focused tooling (JSDA-Kit) | Large community, Google-backed, mature ecosystem |
191
-
192
- Lit is a mature, well-supported library with a large community and strong ecosystem. It's a solid choice for teams that want a lightweight framework experience with custom elements.
193
-
194
- Symbiote.js is designed for a different set of priorities: maximally loose coupling, template flexibility, built-in state management, and seamless isomorphic rendering — all without external dependencies. If you're building widgets, micro-frontends, or framework-agnostic component libraries, these architectural choices make a practical difference.
195
-
196
- And since both Lit and Symbiote.js produce standard custom elements, they can coexist on the same page — alongside raw native web components — without conflicts. You don't have to choose one exclusively; mix them freely within a single project, using each where it fits best.
197
-
198
- ---
199
-
200
- See also: [Symbiote.js documentation](./README.md) · [SSR guide](./ssr.md) · [Context system](./context.md)