@symbiotejs/symbiote 3.2.1 → 3.3.1
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/AI_REFERENCE.md +4 -2
- package/CHANGELOG.md +7 -0
- package/README.md +23 -12
- package/core/Symbiote.js +6 -0
- package/node/index.js +1 -0
- package/package.json +8 -1
- package/scripts/update-exports.js +5 -2
- package/types/core/Symbiote.d.ts +2 -1
- package/types/core/Symbiote.d.ts.map +1 -1
- package/types/node/index.d.ts +2 -0
- package/types/node/index.d.ts.map +1 -0
package/AI_REFERENCE.md
CHANGED
|
@@ -153,7 +153,7 @@ Prefixes control which data context a binding resolves to:
|
|
|
153
153
|
| Prefix | Meaning | Example | Description |
|
|
154
154
|
|--------|---------|---------|-------------|
|
|
155
155
|
| _(none)_ | Local state | `{{count}}` | Current component's local context |
|
|
156
|
-
| `^` | Parent inherited | `{{^parentProp}}` | Walk up DOM ancestry to find nearest component that has this prop |
|
|
156
|
+
| `^` | Parent inherited | `{{^parentProp}}` | Walk up DOM ancestry to find nearest component that has this prop in its data context (`init$` / `add$()`) |
|
|
157
157
|
| `*` | Shared context | `{{*sharedProp}}` | Shared context scoped by `ctx` attribute or CSS `--ctx` |
|
|
158
158
|
| `/` | Named context | `{{APP/myProp}}` | Global named context identified by key before `/` |
|
|
159
159
|
| `--` | CSS Data | `{{--my-css-var}}` | Read value from CSS custom property |
|
|
@@ -318,6 +318,7 @@ Both components access the same `*files` state — no parent component, no prop
|
|
|
318
318
|
| `readyToDestroy` | `true` | Allow cleanup on disconnect |
|
|
319
319
|
| `processInnerHtml` | `false` | Process existing inner HTML with template processors |
|
|
320
320
|
| `ssrMode` | `false` | **Client-only.** Hydrate server-rendered HTML: skips template injection, attaches bindings to existing DOM. Supports both light DOM and Declarative Shadow DOM. Ignored when `__SYMBIOTE_SSR` is active (server side) |
|
|
321
|
+
| `isoMode` | `false` | **Client-only.** Isomorphic mode: if component has children at connect time, behaves as `ssrMode = true` (hydrate). If no children, renders template normally. Same component works for both SSR and client-only scenarios |
|
|
321
322
|
| `allowCustomTemplate` | `false` | Allow `use-template="#selector"` attribute |
|
|
322
323
|
| `isVirtual` | `false` | Replace element with its template fragment |
|
|
323
324
|
| `allowTemplateInits` | `true` | Auto-add props found in template but not in init$ |
|
|
@@ -454,7 +455,7 @@ MyList.template = html`
|
|
|
454
455
|
> **CRITICAL**: Inside itemize templates, items are full Symbiote components with their own state scope.
|
|
455
456
|
> - `{{name}}` — item's own property
|
|
456
457
|
> - `${{onclick: 'handler'}}` — binds to the item component's own method/property
|
|
457
|
-
> - `${{onclick: '^handler'}}` — use `^` prefix to reach the **parent** component's property
|
|
458
|
+
> - `${{onclick: '^handler'}}` — use `^` prefix to reach the **parent** component's property (must be in parent's `init$`)
|
|
458
459
|
> - Failure to use `^` for parent handlers will result in broken event bindings
|
|
459
460
|
|
|
460
461
|
### Custom item component
|
|
@@ -798,3 +799,4 @@ Symbiote.devMode = true;
|
|
|
798
799
|
9. **DON'T** use CSS frameworks (Tailwind, etc.) — use native CSS with custom properties
|
|
799
800
|
10. **DON'T** use `require()` — ESM only (import/export)
|
|
800
801
|
11. **DON'T** use `*prop` without `ctx` attribute or `--ctx` CSS variable — shared context won't be created
|
|
802
|
+
12. **DON'T** rely on class property fallbacks for `^`-targeted properties — the `^` walk only checks the parent's data context (`init$` / `add$()`), not own class properties
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.3.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`isoMode` flag for isomorphic rendering.**
|
|
8
|
+
`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.
|
|
9
|
+
|
|
3
10
|
## 3.2.0
|
|
4
11
|
|
|
5
12
|
### Added
|
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
[](https://github.com/symbiotejs/symbiote.js/actions/workflows/tests.yml)
|
|
1
2
|
[](https://www.npmjs.com/package/@symbiotejs/symbiote)
|
|
2
|
-
](https://www.npmjs.com/package/@symbiotejs/symbiote)
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
3
7
|
|
|
4
8
|
# Symbiote.js
|
|
5
9
|
|
|
@@ -12,6 +16,7 @@ Symbiote.js gives you the convenience of a modern framework while staying close
|
|
|
12
16
|
## What's new in 3.x
|
|
13
17
|
|
|
14
18
|
- **Server-Side Rendering** — render components to HTML with `SSR.processHtml()` or stream chunks with `SSR.renderToStream()`. Client-side hydration via `ssrMode` attaches bindings to existing DOM without re-rendering.
|
|
19
|
+
- **Isomorphic components** — `isoMode` flag makes components work in both SSR and client-only scenarios automatically. If server-rendered content exists, it hydrates; otherwise it renders the template from scratch. One component, zero conditional logic.
|
|
15
20
|
- **Computed properties** — reactive derived state with microtask batching.
|
|
16
21
|
- **Path-based router** — optional `AppRouter` module with `:param` extraction, route guards, and lazy loading.
|
|
17
22
|
- **Exit animations** — `animateOut(el)` for CSS-driven exit transitions, integrated into itemize API.
|
|
@@ -70,7 +75,7 @@ let html = await SSR.processHtml('<my-app>slot content</my-app>');
|
|
|
70
75
|
SSR.destroy(); // cleanup
|
|
71
76
|
```
|
|
72
77
|
|
|
73
|
-
On the client, components with `ssrMode = true` skip template injection and attach bindings to the existing DOM. State mutations work immediately — no hydration step, no reconciliation, no diffing.
|
|
78
|
+
On the client, components with `ssrMode = true` skip template injection and attach bindings to the existing DOM. State mutations work immediately — no hydration step, no reconciliation, no diffing. For isomorphic components that may or may not be server-rendered, use `isoMode = true` — it detects children automatically.
|
|
74
79
|
|
|
75
80
|
### Streaming
|
|
76
81
|
|
|
@@ -158,15 +163,15 @@ Render lists from data arrays with efficient diffing:
|
|
|
158
163
|
|
|
159
164
|
```js
|
|
160
165
|
class TaskList extends Symbiote {
|
|
166
|
+
tasks = [
|
|
167
|
+
{ name: 'Buy groceries' },
|
|
168
|
+
{ name: 'Write docs' },
|
|
169
|
+
];
|
|
161
170
|
init$ = {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
onItemClick() {
|
|
169
|
-
console.log('clicked!');
|
|
171
|
+
// Needs to be defined in init$ for bubbling binding to work
|
|
172
|
+
onItemClick: () => {
|
|
173
|
+
console.log('clicked!');
|
|
174
|
+
},
|
|
170
175
|
}
|
|
171
176
|
}
|
|
172
177
|
|
|
@@ -179,11 +184,11 @@ TaskList.template = html`
|
|
|
179
184
|
`;
|
|
180
185
|
```
|
|
181
186
|
|
|
182
|
-
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.
|
|
187
|
+
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$`.
|
|
183
188
|
|
|
184
189
|
### Bubbling binding (`^`)
|
|
185
190
|
|
|
186
|
-
The `^` prefix works in any nested component template — it walks up the DOM tree to find the nearest ancestor
|
|
191
|
+
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$()`):
|
|
187
192
|
|
|
188
193
|
```html
|
|
189
194
|
<!-- Text binding to parent property: -->
|
|
@@ -193,6 +198,8 @@ The `^` prefix works in any nested component template — it walks up the DOM tr
|
|
|
193
198
|
<button ${{onclick: '^parentHandler'}}>Click</button>
|
|
194
199
|
```
|
|
195
200
|
|
|
201
|
+
> **Note:** Class property fallbacks are not checked by the `^` walk — the parent must define the property in `init$`.
|
|
202
|
+
|
|
196
203
|
### Named data contexts
|
|
197
204
|
|
|
198
205
|
Share state across components without prop drilling:
|
|
@@ -355,3 +362,7 @@ All modern browsers: Chrome, Firefox, Safari, Edge, Opera.
|
|
|
355
362
|
- [Changelog](https://github.com/symbiotejs/symbiote.js/blob/main/CHANGELOG.md)
|
|
356
363
|
|
|
357
364
|
**Questions or proposals? Welcome to [Symbiote Discussions](https://github.com/symbiotejs/symbiote.js/discussions)!** ❤️
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
© [rnd-pro.com](https://rnd-pro.com) — MIT License
|
package/core/Symbiote.js
CHANGED
|
@@ -98,6 +98,10 @@ export class Symbiote extends HTMLElement {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
+
// Resolve isoMode: hydrate if children exist, render template otherwise
|
|
102
|
+
if (this.isoMode && !globalThis.__SYMBIOTE_SSR) {
|
|
103
|
+
this.ssrMode = this.childNodes.length > 0;
|
|
104
|
+
}
|
|
101
105
|
let clientSSR = this.ssrMode && !globalThis.__SYMBIOTE_SSR;
|
|
102
106
|
if (this.processInnerHtml || clientSSR) {
|
|
103
107
|
for (let fn of this.templateProcessors) {
|
|
@@ -169,6 +173,8 @@ export class Symbiote extends HTMLElement {
|
|
|
169
173
|
/** @type {Boolean} */
|
|
170
174
|
this.ssrMode = false;
|
|
171
175
|
/** @type {Boolean} */
|
|
176
|
+
this.isoMode = false;
|
|
177
|
+
/** @type {Boolean} */
|
|
172
178
|
this.allowCustomTemplate = false;
|
|
173
179
|
/** @type {Boolean} */
|
|
174
180
|
this.isVirtual = false;
|
package/node/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SSR } from './SSR.js';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@symbiotejs/symbiote",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.3.1",
|
|
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",
|
|
@@ -36,6 +36,9 @@
|
|
|
36
36
|
"./utils": {
|
|
37
37
|
"default": "./utils/index.js"
|
|
38
38
|
},
|
|
39
|
+
"./node": {
|
|
40
|
+
"default": "./node/index.js"
|
|
41
|
+
},
|
|
39
42
|
"./core/AppRouter.js": {
|
|
40
43
|
"types": "./types/core/AppRouter.d.ts",
|
|
41
44
|
"default": "./core/AppRouter.js"
|
|
@@ -115,6 +118,10 @@
|
|
|
115
118
|
"./utils/setNestedProp.js": {
|
|
116
119
|
"types": "./types/utils/setNestedProp.d.ts",
|
|
117
120
|
"default": "./utils/setNestedProp.js"
|
|
121
|
+
},
|
|
122
|
+
"./node/SSR.js": {
|
|
123
|
+
"types": "./types/node/SSR.d.ts",
|
|
124
|
+
"default": "./node/SSR.js"
|
|
118
125
|
}
|
|
119
126
|
},
|
|
120
127
|
"publishConfig": {
|
|
@@ -10,7 +10,7 @@ import { existsSync } from 'fs';
|
|
|
10
10
|
import { join, extname, basename } from 'path';
|
|
11
11
|
|
|
12
12
|
const ROOT = new URL('..', import.meta.url).pathname;
|
|
13
|
-
const DIRS = ['core', 'utils'];
|
|
13
|
+
const DIRS = ['core', 'utils', 'node'];
|
|
14
14
|
|
|
15
15
|
async function getModules(dir) {
|
|
16
16
|
let fullDir = join(ROOT, dir);
|
|
@@ -29,12 +29,15 @@ async function run() {
|
|
|
29
29
|
'./utils': {
|
|
30
30
|
default: './utils/index.js',
|
|
31
31
|
},
|
|
32
|
+
'./node': {
|
|
33
|
+
default: './node/index.js',
|
|
34
|
+
},
|
|
32
35
|
};
|
|
33
36
|
|
|
34
37
|
for (let dir of DIRS) {
|
|
35
38
|
let modules = await getModules(dir);
|
|
36
39
|
for (let mod of modules) {
|
|
37
|
-
if (mod === './core/index.js' || mod === './utils/index.js') continue;
|
|
40
|
+
if (mod === './core/index.js' || mod === './utils/index.js' || mod === './node/index.js') continue;
|
|
38
41
|
let dtsPath = `./types/${dir}/${basename(mod, '.js')}.d.ts`;
|
|
39
42
|
if (!existsSync(join(ROOT, dtsPath))) continue;
|
|
40
43
|
exports[mod] = {
|
package/types/core/Symbiote.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export class Symbiote<S> extends HTMLElement {
|
|
|
20
20
|
initCallback(): void;
|
|
21
21
|
renderCallback(): void;
|
|
22
22
|
render(template?: string | DocumentFragment, shadow?: boolean): void;
|
|
23
|
+
ssrMode: boolean;
|
|
23
24
|
init$: S;
|
|
24
25
|
cssInit$: {
|
|
25
26
|
[x: string]: any;
|
|
@@ -33,7 +34,7 @@ export class Symbiote<S> extends HTMLElement {
|
|
|
33
34
|
renderShadow: boolean;
|
|
34
35
|
readyToDestroy: boolean;
|
|
35
36
|
processInnerHtml: boolean;
|
|
36
|
-
|
|
37
|
+
isoMode: boolean;
|
|
37
38
|
allowCustomTemplate: boolean;
|
|
38
39
|
isVirtual: boolean;
|
|
39
40
|
allowTemplateInits: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Symbiote.d.ts","sourceRoot":"","sources":["../../core/Symbiote.js"],"names":[],"mappings":";;AAmBA,sBADc,CAAC;IAuBb,cADW,mBAAmB,CACjB;IAGb,sCAAwB;IAExB,iCAGC;IAED,8BAEC;IAkBD,wBAAgB;
|
|
1
|
+
{"version":3,"file":"Symbiote.d.ts","sourceRoot":"","sources":["../../core/Symbiote.js"],"names":[],"mappings":";;AAmBA,sBADc,CAAC;IAuBb,cADW,mBAAmB,CACjB;IAGb,sCAAwB;IAExB,iCAGC;IAED,8BAEC;IAkBD,wBAAgB;IAoJhB,+BAJwB,CAAC,SAAZ,aAAU,uBAEZ,CAAC;;;MAuCX;IA+OD,qCAA+B;IAoC/B,iDAFa,OAAO,QAAQ,CAqB3B;IAED,wBAKC;IAGD;;aAOC;IA6GD,6BADY,SAAS,aAAa,QAOjC;IAGD,+BADY,SAAS,aAAa,QAOjC;IAGD,8BADY,SAAS,aAAa,EAIjC;IAGD,gCADY,SAAS,aAAa,EAIjC;IAtiBD,cA6BC;IA/HD,gCAEC;IAED,qBAAiB;IACjB,uBAAmB;IAiBnB,kBAHW,SAAS,gBAAgB,0BA6EnC;IAjDG,iBAAyC;IAsD3C,OADW,CAAC,CACoB;IAEhC;;MAAmC;IAEnC,oBADW,GAAG,CAAC,CAAC,EAAE,EAAE,gBAAgB,gBAAW,EAAE,KAAK,eAAU,KAAK,IAAI,CAAC,CACvC;IAEnC;;MAA8B;IAC9B,kBAAwB;IAExB,qBAAwB;IAExB,sBAAyB;IAEzB,wBAA0B;IAE1B,0BAA6B;IAI7B,iBAAoB;IAEpB,6BAAgC;IAEhC,mBAAsB;IAEtB,4BAA8B;IAIhC,yBAEC;IAGD,sBASC;IAGD,4BAKC;IAGD,6BAEC;IAoDD,IALuB,CAAC,SAAX,MAAO,CAAE,QACX,CAAC,WACD,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,wBAmB/B;IAGD,2BAGC;IAGD,uBAGC;IAQD,IALuB,CAAC,SAAX,MAAO,CAAE,qBAEX,CAAC,CAAC,CAAC,CAAC,2BAMd;IAMD,UAHW,OAAO,CAAC,CAAC,CAAC,2BAOpB;IAGD,SADc,CAAC,CA2Bd;IAMD,YAHW,OAAO,CAAC,CAAC,CAAC,mCAcpB;IAED,8BAgBC;IAdG,4CASE;IAwFF,0BAAwC;IAoB1C,uBAAyB;IAG3B,0BAEC;IAED,wBAAoB;IAUpB,yBAAuB;IACvB,6BA0BC;IA6CD,oEAeC;IAMD,yDAiBC;IAYD,0BAME;IAMF,0CAFW,GAAG,QAab;IAED,yBAGC;IAOD,8EAmBC;;CA+BF;;mBAhsBkB,aAAa;qBACX,iBAAiB;2BACX,iBAAiB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../node/index.js"],"names":[],"mappings":""}
|