UNPKG

@symbiotejs/symbiote

Version:

Symbiote.js - zero-dependency close-to-platform frontend library to build super-powered web components

344 lines (233 loc) 17.1 kB
# Changelog ## 3.5.4 ### Fixed - **SSR: `bindAttributes()` crash in linkedom.** `observedAttributes` is a getter-only property in linkedom — direct assignment threw. Now uses `Object.defineProperty` with a configurable getter. - **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. ## 3.5.2 ### Fixed - **`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`). ### Improved - **`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. - **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. - **Doc references in all warnings.** Every message now includes a `→` link to the relevant GitHub docs page. - **`devMessages.js` auto-enables `devMode`.** Importing the module sets `globalThis.__SYMBIOTE_DEV_MODE = true` — one import for the full dev experience. ## 3.5.0 ### Added - **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. - **`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. - **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: ```js AppRouter.setDefaultTitle(() => i18n('app.title')); AppRouter.initRoutingCtx('R', { home: { pattern: '/', title: () => i18n('home.title'), default: true }, }); ``` ## 3.4.7 ### Fixed - **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. - **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). - **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. ## 3.4.4 ### Fixed - **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. - **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. ## 3.4.3 ### Fixed - **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. ### Added - **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. ## 3.4.2 ### Fixed - **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. ## 3.4.1 ### Fixed - **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. ## 3.4.0 ### Added - **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. ## 3.3.9 ### Improved - **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. - **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`. ### Fixed - **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. ## 3.3.8 ### Fixed - **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. ## 3.3.7 ### Fixed - **SSR hydration: Itemize list duplication.** 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. ## 3.3.6 ### Improved - **SSR hydration: generic property preservation.** 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. - **SSR hydration: Itemize API support.** 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. ## 3.3.5 ### Fixed - **AppRouter: SSR context populated with default route.** `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. ## 3.3.4 ### Fixed - **PubSub context isolation with importmaps.** `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. ### Added - **`@symbiotejs/symbiote/full` entry point.** Re-exports everything from the main entry point plus `AppRouter`, guaranteeing a single PubSub module: ```js import Symbiote, { html, css, AppRouter } from '@symbiotejs/symbiote/full'; ``` - **AppRouter SSR support.** `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. ## 3.3.0 ### Added - **`isoMode` flag for isomorphic rendering.** `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. ## 3.2.0 ### Added - **Itemize class property fallback.** The `itemize` data source property now supports class property fallback, consistent with `domBindProcessor` and `txtNodesProcessor`. ### Changed - **Utils moved to separate entry point.** `UID`, `setNestedProp`, `applyStyles`, `applyAttributes`, `create`, `kebabToCamel`, `reassignDictionary` are no longer exported from the main `@symbiotejs/symbiote` entry point. Import from `@symbiotejs/symbiote/utils` instead: ```js import { UID, create } from '@symbiotejs/symbiote/utils'; ``` Individual deep imports (`@symbiotejs/symbiote/utils/UID.js`, etc.) continue to work. - **`initPropFallback` extracted to shared module.** Duplicated fallback initialization logic across template processors consolidated into `core/initPropFallback.js`. ## 3.1.0 ### Changed - **Class property fallback (generalized).** 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. ```js class MyComp extends Symbiote { label = 'Click me'; onSubmit() { console.log('submitted'); } } ``` Previously only `on*` handlers supported this fallback. ## 3.0.0 ### ⚠️ Breaking Changes - **`tplProcessors` → `templateProcessors`.** The `addTemplateProcessor()` method is removed — use native `Set` methods: ```js // 2.x: this.addTemplateProcessor(myProcessor); // 3.x: this.templateProcessors.add(myProcessor); ``` - **`AppRouter.applyRoute()` → `AppRouter.navigate()`.** - **`AppRouter` removed from main entry point.** ```js // 2.x: import { AppRouter } from '@symbiotejs/symbiote'; // 3.x: import { AppRouter } from '@symbiotejs/symbiote/core/AppRouter.js'; ``` - **Computed properties: cross-context requires explicit deps.** Local computeds (same-context) keep working unchanged. Cross-context now uses object syntax: ```js // 2.x — implicit via global scan: init$ = { '+total': () => this.$['APP/score'] + this.$.local, }; // 3.x — explicit deps: init$ = { '+total': { deps: ['APP/score'], fn: () => this.$['APP/score'] + this.$.local, }, }; ``` - **Shared context (`*prop`) simplified.** Removed `ctxOwner` / `ctx-owner`. First-registered value always wins. Dev-mode warnings when `*prop` used without `ctx` attribute. ### Performance - **Computed properties: per-instance dependency tracking.** Replaced global scan with per-instance auto-tracking. Up to **676× faster** for local computeds, **14×** for sparse scenarios. - **Microtask batching.** All computed recalculation and internal scheduling uses `queueMicrotask` instead of `setTimeout`. - **`#parseProp` fast path.** `charCodeAt` checks skip full string parsing for common local properties — the most frequent case. - **`$` proxy inlined fast path.** Both `get` and `set` traps bypass `#parseProp` entirely for local props. - **`PubSub.pub()` direct value pass.** Eliminates redundant `read()` on every state update. - **Dev warnings gated by `PubSub.devMode`.** Type-mismatch checks have zero overhead in production. - **`txtNodesProcessor` early exit.** Skips text-node scanning when template contains no `{{` tokens. - **`localCtx` direct construction.** Uses `new PubSub({})` instead of `PubSub.registerCtx({})`, bypassing global store for component-scoped state. - **`hasOwnProperty` → `in` operator** across `PubSub` internals. - **`itemizeProcessor-keyed.js` — optional optimized itemize processor.** 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: ```js import { itemizeProcessor } from '@symbiotejs/symbiote/core/itemizeProcessor-keyed.js'; import { itemizeProcessor as defaultProcessor } from '@symbiotejs/symbiote/core/itemizeProcessor.js'; class BigList extends Symbiote { constructor() { super(); this.templateProcessors.delete(defaultProcessor); this.templateProcessors = new Set([itemizeProcessor, ...this.templateProcessors]); } } ``` ### Added - **Server-side rendering (`node/SSR.js`).** `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. ```js import { SSR } from '@symbiotejs/symbiote/node/SSR.js'; await SSR.init(); await import('./my-app.js'); let html = await SSR.processHtml('<my-app>content</my-app>'); SSR.destroy(); ``` - **Declarative Shadow DOM hydration (`ssrMode`).** `ssrMode = true` hydrates pre-rendered content (light DOM + `<template shadowrootmode>`). Template injection skipped; bindings attach to existing DOM. Shadow styles applied via `adoptedStyleSheets`. - **Exit animation hook (`animateOut`).** Sets `[leaving]` attribute, waits for CSS `transitionend`, removes element. Integrated into itemize processors — items with transitions animate out automatically. Enter animations use `@starting-style`. - **`AppRouter`: path-based routing.** Routes with `pattern` key use path-based URLs with `:param` extraction: ```js AppRouter.initRoutingCtx('R', { home: { pattern: '/', title: 'Home', default: true }, user: { pattern: '/users/:id', title: 'User' }, }); // /users/42 → { route: 'user', options: { id: '42' } } ``` Query-string routes remain fully backward compatible. - **Route guards — `AppRouter.beforeRoute(fn)`.** Return `false` to cancel, a route string to redirect: ```js let unsub = AppRouter.beforeRoute((to, from) => { if (!isAuth && to.route === 'settings') return 'login'; }); ``` - **Lazy loaded routes.** `load` in route descriptors for dynamic imports, cached automatically: ```js { pattern: '/settings', load: () => import('./pages/settings.js') } ``` - **Class property fallback.** Bindings not in `init$` fall back to own class properties/methods: ```js label = 'Click me'; onSubmit() { console.log('submitted'); } ``` - **`Symbiote.devMode` flag.** Enables verbose warnings (unresolved bindings, tag names, available contexts). Also wires `PubSub.devMode`. - **`reg()` returns the class itself.** Enables `export default MyComponent.reg('my-component')`. - **`destructionDelay` instance property.** Configurable delay (default `100`ms) before cleanup in `disconnectedCallback`. - **Trusted Types support.** Template writes use `'symbiote'` Trusted Types policy when available. Zero overhead otherwise. - **`this` in template detection.** `html` fires `console.error` when `${this.…}` used in template (templates are context-free). - **AI_REFERENCE.md** — comprehensive context file for code assistants. ### Fixed - `css()` trailing `undefined` when no interpolations exist. - `new DocumentFragment()` → `document.createDocumentFragment()` for linkedom compatibility. - `txtNodesProcessor` null check for `fr.textContent` in SSR environments.