@symbiotejs/symbiote 3.8.1 → 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/README.md +19 -106
- package/docs/README.md +0 -6
- package/docs/animations.md +1 -1
- package/docs/attributes.md +2 -2
- package/docs/common-mistakes.md +63 -1
- package/docs/context.md +212 -73
- package/docs/ecosystem.md +0 -3
- package/docs/llms-index.md +0 -1
- package/llms-full.txt +297 -183
- package/llms.txt +0 -1
- package/package.json +1 -2
- package/scripts/build-llms.js +0 -2
- package/CHANGELOG.md +0 -372
- package/docs/lit-vs-symbiote.md +0 -200
- package/docs/migration-2x-to-3x.md +0 -171
package/README.md
CHANGED
|
@@ -9,12 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
A lightweight, standards-first UI library built on Web Components. No virtual DOM, no compiler, no black boxes, no excess repaints. No build step required - works directly in the browser. A bundler is recommended for production performance, but entirely optional.
|
|
11
11
|
|
|
12
|
-
Symbiote.js gives you the convenience of a modern framework while staying close to the native platform - HTML, CSS, and DOM APIs. Components are custom elements that work everywhere: in any framework, in plain HTML, in a micro-frontend architecture. And with **isomorphic mode**, the same component code works on the server and the client - server-rendered pages hydrate automatically, no diffing, no mismatch errors.
|
|
13
|
-
|
|
14
12
|
Here are the three most important differences between Symbiote.js and other frameworks:
|
|
15
|
-
1. Natural DOM Extension Philosophy - designed to extend platform, not to replace
|
|
16
|
-
2. Runtime-Agnostic HTML Templates - outstanding flexibility for rendering strategies and further customization
|
|
17
|
-
3. Powerful App-wide State Management - combine data contexts without bloated boilerplate or external tools
|
|
13
|
+
1. **Natural DOM Extension Philosophy** - designed to extend platform APIs, not to replace them
|
|
14
|
+
2. **Runtime-Agnostic HTML Templates** - outstanding flexibility for rendering strategies and further customization
|
|
15
|
+
3. **Powerful App-wide State Management** - combine data contexts without bloated boilerplate or external tools
|
|
18
16
|
|
|
19
17
|
## What's new in v3.x?
|
|
20
18
|
|
|
@@ -28,7 +26,6 @@ Here are the three most important differences between Symbiote.js and other fram
|
|
|
28
26
|
- **DSD hydration** - `ssrMode` supports both light DOM and Declarative Shadow DOM.
|
|
29
27
|
- **Class property fallback** - binding keys not in `init$` fall back to own class properties/methods.
|
|
30
28
|
- **Lazy mode** - `lazyMode` flag defers component initialization and rendering based on viewport visibility. Can also be enabled via the `lazy` attribute on `itemize` containers to efficiently handle massive data sets.
|
|
31
|
-
- And [more](https://github.com/symbiotejs/symbiote.js/blob/webmcp/CHANGELOG.md).
|
|
32
29
|
|
|
33
30
|
## Quick start
|
|
34
31
|
|
|
@@ -66,57 +63,6 @@ npm i @symbiotejs/symbiote
|
|
|
66
63
|
import Symbiote, { html, css } from '@symbiotejs/symbiote';
|
|
67
64
|
```
|
|
68
65
|
|
|
69
|
-
## Isomorphic Web Components
|
|
70
|
-
|
|
71
|
-
One component. Server-rendered or client-rendered - automatically. Set `isoMode = true` and the component figures it out: if server-rendered content exists, it hydrates; otherwise it renders from template. No conditional logic, no separate server/client versions:
|
|
72
|
-
```js
|
|
73
|
-
class MyComponent extends Symbiote {
|
|
74
|
-
isoMode = true;
|
|
75
|
-
count = 0;
|
|
76
|
-
increment() {
|
|
77
|
-
this.$.count++;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
MyComponent.template = html`
|
|
82
|
-
<h2 ${{textContent: 'count'}}></h2>
|
|
83
|
-
<button ${{onclick: 'increment'}}>Click me!</button>
|
|
84
|
-
`;
|
|
85
|
-
MyComponent.reg('my-component');
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
This exact code runs **everywhere** - SSR on the server, hydration on the client, or pure client rendering. No framework split, no `'use client'` directives, no hydration mismatch errors.
|
|
89
|
-
|
|
90
|
-
### SSR - one class, zero config
|
|
91
|
-
|
|
92
|
-
Server rendering doesn't need a virtual DOM, a reconciler, or framework-specific packages:
|
|
93
|
-
|
|
94
|
-
```js
|
|
95
|
-
import { SSR } from '@symbiotejs/symbiote/node/SSR.js';
|
|
96
|
-
|
|
97
|
-
await SSR.init(); // patches globals with linkedom
|
|
98
|
-
await import('./my-app.js'); // components register normally
|
|
99
|
-
|
|
100
|
-
let html = await SSR.processHtml('<my-app></my-app>');
|
|
101
|
-
SSR.destroy();
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
For large pages, stream HTML chunks with `SSR.renderToStream()` for faster TTFB. See [SSR docs](./docs/ssr.md) and [server setup recipes](./docs/ssr-server.md).
|
|
105
|
-
|
|
106
|
-
### How it compares
|
|
107
|
-
|
|
108
|
-
| | **Symbiote.js** | **Next.js (React)** | **Lit** (`@lit-labs/ssr`) |
|
|
109
|
-
|--|----------------|---------------------|----|
|
|
110
|
-
| **Isomorphic code** | Same code, `isoMode` auto-detects | Server Components vs Client Components split | Same code, but load-order constraints |
|
|
111
|
-
| **Hydration** | Binding-based - attaches to existing DOM, no diffing | `hydrateRoot()` - must produce identical output or errors | Requires `ssr-client` + hydrate support module |
|
|
112
|
-
| **Packages** | 1 module + `linkedom` peer dep | Full framework buy-in | 3 packages: `ssr`, `ssr-client`, `ssr-dom-shim` |
|
|
113
|
-
| **Streaming** | `renderToStream()` async generator | `renderToPipeableStream()` | Iterable `RenderResult` |
|
|
114
|
-
| **Mismatch handling** | Not needed - bindings attach to whatever DOM exists | Hard errors if server/client output differs | N/A |
|
|
115
|
-
| **Template output** | Clean HTML with `bind=` attributes | HTML with framework markers | HTML with `<!--lit-part-->` comment markers |
|
|
116
|
-
| **Lock-in** | None - standard Web Components | Full framework commitment | Lit-specific, but Web Components |
|
|
117
|
-
|
|
118
|
-
**Key insight:** There are no hydration mismatches because there's no diffing. The server produces HTML with binding attributes. The client reads those attributes and adds reactivity. That's it.
|
|
119
|
-
|
|
120
66
|
## Core concepts
|
|
121
67
|
|
|
122
68
|
### Reactive state
|
|
@@ -145,7 +91,7 @@ This makes it easy to control Symbiote-based widgets and microfrontends from any
|
|
|
145
91
|
|
|
146
92
|
### Templates
|
|
147
93
|
|
|
148
|
-
Templates are plain HTML strings -
|
|
94
|
+
Templates are plain HTML strings - runtime-agnostic, easy to test, easy to move between files:
|
|
149
95
|
|
|
150
96
|
```js
|
|
151
97
|
// Separate file: my-component.template.js
|
|
@@ -163,34 +109,24 @@ The `html` function supports two interpolation modes:
|
|
|
163
109
|
|
|
164
110
|
### Itemize (dynamic reactive lists)
|
|
165
111
|
|
|
166
|
-
Render lists from data arrays with efficient updates:
|
|
112
|
+
Render lists from data arrays or objects with efficient updates:
|
|
167
113
|
```js
|
|
168
114
|
class TaskList extends Symbiote {
|
|
169
115
|
tasks = [
|
|
170
116
|
{ name: 'Buy groceries' },
|
|
171
117
|
{ name: 'Write docs' },
|
|
172
118
|
];
|
|
173
|
-
init$ = {
|
|
174
|
-
// Needs to be defined in init$ for pop-up binding to work
|
|
175
|
-
onItemClick: () => {
|
|
176
|
-
console.log('clicked!');
|
|
177
|
-
},
|
|
178
|
-
}
|
|
179
119
|
}
|
|
180
120
|
|
|
181
121
|
TaskList.template = html`
|
|
182
|
-
<
|
|
122
|
+
<ul itemize="tasks">
|
|
183
123
|
<template>
|
|
184
|
-
<
|
|
124
|
+
<li>{{name}}</li>
|
|
185
125
|
</template>
|
|
186
|
-
</
|
|
126
|
+
</ul>
|
|
187
127
|
`;
|
|
188
128
|
```
|
|
189
129
|
|
|
190
|
-
Items have their own state scope. Use the **`^` prefix** to reach higher-level component properties and handlers - `'^onItemClick'` binds to the parent's `onItemClick`, not the item's. Properties referenced via `^` must be defined in the parent's `init$`.
|
|
191
|
-
|
|
192
|
-
> **Performance Tip:** For massive lists, add the `lazy` attribute to the container (`<div itemize="tasks" lazy>`). It defers component initialization until they enter the viewport and cleans them up when they leave, heavily optimizing memory and rendering performance.
|
|
193
|
-
|
|
194
130
|
### Pop-up binding (`^`)
|
|
195
131
|
|
|
196
132
|
The `^` prefix works in any nested component template - it walks up the DOM tree to find the nearest ancestor that has the property registered in its data context (`init$` or `add$()`):
|
|
@@ -203,14 +139,11 @@ The `^` prefix works in any nested component template - it walks up the DOM tree
|
|
|
203
139
|
<button ${{onclick: '^parentHandler'}}>Click</button>
|
|
204
140
|
```
|
|
205
141
|
|
|
206
|
-
> **Note:** Class property fallbacks are not checked by the `^` walk - the parent must define the property in `init$`.
|
|
207
|
-
|
|
208
142
|
### Named data contexts
|
|
209
143
|
|
|
210
144
|
Share state across components without prop drilling:
|
|
211
|
-
|
|
212
145
|
```js
|
|
213
|
-
import { PubSub } from '@symbiotejs/symbiote';
|
|
146
|
+
import { PubSub, html } from '@symbiotejs/symbiote';
|
|
214
147
|
|
|
215
148
|
PubSub.registerCtx({
|
|
216
149
|
user: 'Alex',
|
|
@@ -219,6 +152,9 @@ PubSub.registerCtx({
|
|
|
219
152
|
|
|
220
153
|
// Any component can read/write:
|
|
221
154
|
this.$['APP/user'] = 'New name';
|
|
155
|
+
|
|
156
|
+
// Any template can use property directly:
|
|
157
|
+
let template = html`<h2>{{APP/user}}</h2>`;
|
|
222
158
|
```
|
|
223
159
|
|
|
224
160
|
### Shared context (`*`)
|
|
@@ -251,11 +187,10 @@ class StatusBar extends Symbiote {
|
|
|
251
187
|
|
|
252
188
|
All three components access the same `*files` state - no parent component, no prop drilling, no global store boilerplate. Just set `ctx="gallery"` in HTML and use `*`-prefixed properties. This makes it trivial to build complex component relationships purely in markup, with ready-made components that don't need to know about each other.
|
|
253
189
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
### Routing (optional module)
|
|
190
|
+
### Application routing
|
|
257
191
|
|
|
258
192
|
```js
|
|
193
|
+
// Import optional module:
|
|
259
194
|
import { AppRouter } from '@symbiotejs/symbiote/core/AppRouter.js';
|
|
260
195
|
|
|
261
196
|
AppRouter.initRoutingCtx('R', {
|
|
@@ -265,23 +200,7 @@ AppRouter.initRoutingCtx('R', {
|
|
|
265
200
|
});
|
|
266
201
|
```
|
|
267
202
|
|
|
268
|
-
###
|
|
269
|
-
|
|
270
|
-
CSS-driven transitions with zero JS animation code:
|
|
271
|
-
|
|
272
|
-
```css
|
|
273
|
-
task-item {
|
|
274
|
-
opacity: 1;
|
|
275
|
-
transition: opacity 0.3s;
|
|
276
|
-
|
|
277
|
-
@starting-style { opacity: 0; } /* enter */
|
|
278
|
-
&[leaving] { opacity: 0; } /* exit */
|
|
279
|
-
}
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
`animateOut(el)` sets `[leaving]`, waits for `transitionend`, then removes. Itemize uses this automatically.
|
|
283
|
-
|
|
284
|
-
### Styling
|
|
203
|
+
### CSS Styling
|
|
285
204
|
|
|
286
205
|
Shadow DOM is **optional** in Symbiote - use it when you need isolation, skip it when you don't. This gives full flexibility:
|
|
287
206
|
|
|
@@ -298,6 +217,8 @@ MyComponent.rootStyles = css`
|
|
|
298
217
|
`;
|
|
299
218
|
```
|
|
300
219
|
|
|
220
|
+
This style will be applied to nearest upper shadow root, if exists and to common document if not.
|
|
221
|
+
|
|
301
222
|
**Shadow DOM** - opt-in isolation when needed:
|
|
302
223
|
|
|
303
224
|
```js
|
|
@@ -311,9 +232,9 @@ Isolated.shadowStyles = css`
|
|
|
311
232
|
|
|
312
233
|
All native CSS features work as expected: CSS variables flow through shadow boundaries, `::part()` exposes internals, modern nesting, `@layer`, `@container` - no framework abstractions in the way. Mix light DOM and shadow DOM components freely in the same app.
|
|
313
234
|
|
|
314
|
-
### CSS Data
|
|
235
|
+
### CSS Data
|
|
315
236
|
|
|
316
|
-
Components can read CSS custom
|
|
237
|
+
Components can read CSS custom property values to initiate reactive state:
|
|
317
238
|
|
|
318
239
|
```css
|
|
319
240
|
my-widget {
|
|
@@ -329,8 +250,6 @@ MyWidget.template = html`
|
|
|
329
250
|
`;
|
|
330
251
|
```
|
|
331
252
|
|
|
332
|
-
CSS values are parsed automatically - quoted strings become strings, numbers become numbers. Call `this.updateCssData()` to re-read after runtime CSS changes. This enables CSS-driven configuration: theme values, layout parameters, or localized strings - all settable from CSS without touching JS.
|
|
333
|
-
|
|
334
253
|
## Best for
|
|
335
254
|
|
|
336
255
|
- **Complex widgets** embedded in any host application
|
|
@@ -341,19 +260,13 @@ CSS values are parsed automatically - quoted strings become strings, numbers bec
|
|
|
341
260
|
- **Framework-agnostic solutions** - one codebase, any context
|
|
342
261
|
- **Modern AI-first web** - expose the application state to WebMCP tools automatically
|
|
343
262
|
|
|
344
|
-
## Browser support
|
|
345
|
-
|
|
346
|
-
All modern browsers: Chrome, Firefox, Safari, Edge, Opera.
|
|
347
|
-
|
|
348
263
|
## Docs & Examples
|
|
349
264
|
|
|
350
265
|
- [Documentation](https://github.com/symbiotejs/symbiote.js/blob/main/docs/README.md)
|
|
351
|
-
- [Lit vs Symbiote.js](https://github.com/symbiotejs/symbiote.js/blob/main/docs/lit-vs-symbiote.md) - Side-by-side comparison
|
|
352
266
|
- [Live Examples](https://rnd-pro.com/symbiote/3x/examples/) - Interactive Code Playground
|
|
353
267
|
- [JSDA-Kit](https://github.com/rnd-pro/jsda-kit) - All-in-one companion tool: server, SSG, bundling, import maps, and native Symbiote.js SSR integration
|
|
354
268
|
- [AI / llms.txt](https://rnd-pro.com/symbiote/llms.txt) — index for AI tools
|
|
355
269
|
- [Full docs (single file)](https://rnd-pro.com/symbiote/llms-full.txt) — complete merged reference for AI context
|
|
356
|
-
- [Changelog](https://github.com/symbiotejs/symbiote.js/blob/main/CHANGELOG.md)
|
|
357
270
|
|
|
358
271
|
## Related articles
|
|
359
272
|
|
package/docs/README.md
CHANGED
|
@@ -41,19 +41,13 @@
|
|
|
41
41
|
- [**TypeScript**](./typescript.md) — JSDoc types, TypeScript usage, and hybrid template checks
|
|
42
42
|
- [**Security**](./security.md) — Trusted Types and CSP compliance
|
|
43
43
|
|
|
44
|
-
## Migration
|
|
45
|
-
|
|
46
|
-
- [**2.x → 3.x Migration Guide**](./migration-2x-to-3x.md) — Breaking changes and upgrade path
|
|
47
|
-
|
|
48
44
|
## Additional Resources
|
|
49
45
|
|
|
50
|
-
- [Lit vs Symbiote.js](./lit-vs-symbiote.md) — Side-by-side comparison
|
|
51
46
|
- [Live Examples](https://rnd-pro.com/symbiote/3x/examples/) - Interactive Code Playground
|
|
52
47
|
- [Common Mistakes](./common-mistakes.md) — Patterns that frequently cause bugs
|
|
53
48
|
- [Examples](./examples.md) — 26 complete working examples
|
|
54
49
|
- [llms.txt](https://rnd-pro.com/symbiote/llms.txt) — index for AI tools
|
|
55
50
|
- [llms-full.txt](https://rnd-pro.com/symbiote/llms-full.txt) — complete merged reference for AI context
|
|
56
|
-
- [Changelog](../CHANGELOG.md)
|
|
57
51
|
- [npm](https://www.npmjs.com/package/@symbiotejs/symbiote)
|
|
58
52
|
- [GitHub Discussions](https://github.com/symbiotejs/symbiote.js/discussions)
|
|
59
53
|
|
package/docs/animations.md
CHANGED
|
@@ -38,7 +38,7 @@ my-item {
|
|
|
38
38
|
|
|
39
39
|
## Itemize integration
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
Itemize processors use `animateOut` automatically for item removal. Items with CSS `transition` + `[leaving]` styles will animate out before being removed from the DOM:
|
|
42
42
|
```css
|
|
43
43
|
user-card {
|
|
44
44
|
opacity: 1;
|
package/docs/attributes.md
CHANGED
|
@@ -22,7 +22,7 @@ class MyComponent extends Symbiote {}
|
|
|
22
22
|
MyComponent.template = html`<h1>{{@attribute-name}}</h1>`;
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
Then use it as an attribute in markup:
|
|
25
|
+
Then use it as an component's attribute in markup:
|
|
26
26
|
```html
|
|
27
27
|
<my-component attribute-name="attribute value"></my-component>
|
|
28
28
|
```
|
|
@@ -46,7 +46,7 @@ MyComponent.observedAttributes = [
|
|
|
46
46
|
|
|
47
47
|
## `bindAttributes()` static method
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Reflect attribute values to property values:
|
|
50
50
|
```js
|
|
51
51
|
class MyComponent extends Symbiote {
|
|
52
52
|
|
package/docs/common-mistakes.md
CHANGED
|
@@ -243,7 +243,69 @@ Named external contexts (`CTX/prop`) are different: they are registered globally
|
|
|
243
243
|
|
|
244
244
|
---
|
|
245
245
|
|
|
246
|
-
## 10.
|
|
246
|
+
## 10. Using `{{prop}}` binding syntax inside tag definitions
|
|
247
|
+
|
|
248
|
+
`{{prop}}` is a **text node** binding — it only works inside element content. Any binding that targets a tag itself — attributes, DOM properties, event handlers, or CSS custom properties — must use `${{}}` or `bind=""` syntax inside the opening tag.
|
|
249
|
+
|
|
250
|
+
```html
|
|
251
|
+
<!-- WRONG — {{}} syntax does not work inside tag definitions -->
|
|
252
|
+
<div class="{{APP/theme}}">...</div>
|
|
253
|
+
<img src="{{imageUrl}}">
|
|
254
|
+
<button onclick="{{onAction}}">Go</button>
|
|
255
|
+
|
|
256
|
+
<!-- CORRECT — use ${{}} binding object inside the tag -->
|
|
257
|
+
<div ${{'@class': 'APP/theme'}}>...</div>
|
|
258
|
+
<img ${{'@src': 'imageUrl'}}>
|
|
259
|
+
<button ${{onclick: 'onAction'}}>Go</button>
|
|
260
|
+
<div ${{'style.color': 'colorProp'}}>...</div>
|
|
261
|
+
|
|
262
|
+
<!-- CORRECT — or use the bind attribute string syntax -->
|
|
263
|
+
<div bind="@class: APP/theme">...</div>
|
|
264
|
+
<img bind="@src: imageUrl">
|
|
265
|
+
<button bind="onclick: onAction">Go</button>
|
|
266
|
+
|
|
267
|
+
<!-- CORRECT — {{}} works fine in text node content -->
|
|
268
|
+
<h1>Hello, {{userName}}!</h1>
|
|
269
|
+
<p>Theme: {{APP/theme}}</p>
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## 11. Expecting two-way data binding
|
|
275
|
+
|
|
276
|
+
Symbiote.js bindings are **one-way by design** — from state to DOM. There is no `v-model`, `[(ngModel)]`, or `bind:value` equivalent. To react to user input, wire the event handler explicitly and write back to state yourself.
|
|
277
|
+
|
|
278
|
+
```js
|
|
279
|
+
// WRONG — expecting the binding to also update state on user input
|
|
280
|
+
MyComponent.template = html`
|
|
281
|
+
<input ${{value: 'query'}}>
|
|
282
|
+
`;
|
|
283
|
+
|
|
284
|
+
// CORRECT — handle the input event and write back to state
|
|
285
|
+
class MyComponent extends Symbiote {
|
|
286
|
+
init$ = { query: '' }
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
MyComponent.template = html`
|
|
290
|
+
<input
|
|
291
|
+
${{value: 'query', oninput: 'onInput'}}
|
|
292
|
+
>
|
|
293
|
+
`;
|
|
294
|
+
```
|
|
295
|
+
```js
|
|
296
|
+
class MyComponent extends Symbiote {
|
|
297
|
+
init$ = {
|
|
298
|
+
query: '',
|
|
299
|
+
onInput: (e) => { this.$.query = e.target.value; },
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
This is intentional — explicit event handling keeps data flow predictable and avoids the hidden side effects that two-way binding can introduce.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## 12. Treating `init$` as a plain object
|
|
247
309
|
|
|
248
310
|
`init$` is processed once at connection time to populate the component's reactive context. Mutating it after the fact has no effect. Use `this.$` or `add$()` for runtime changes.
|
|
249
311
|
|