releases/0.1.7
Pre-releaseBREAKING CHANGES: The defineSignalComponent and defineBaseComponent functions have been renamed to component and baseComponent respectively. The new versions do not immediately call customElements.define. Users must either manually define the component (preferably via MyComponent.define()) immediately after calling component / baseComponent or rely on a consumer of the component calling MyComponent.define() prior to using it (which Dehydrated.prototype.access and Dehydrated.prototype.hydrate will both do automatically). See the below changelog for more details.
-
769332b - Applies component definition returned from
defineSignalComponent/defineBaseComponent. Returned values are applied to the component instance based on their property names and create a public API for the component.export const MyComponent = defineSignalComponent('my-component', (host) => { return { log(): void { console.log('Hello, World!'); }, }; }); const instance = document.querySelector('my-component')!; instance.log(); // Logs 'Hello, World!'
-
a772872 -
hydrateandDehydrated.prototype.hydratenow accept hydration "parameters" which are applied to the component object prior to triggering hydration. This allows a parent component to required data to a child component during hydration. Types are defined on the globalHTMLElementHydrationParamsMapinterface (typically via thePropertiestype) whichhydrateuses to type check parameters correctly.import { Properties, defineSignalComponent } from 'hydroactive'; export class MyElement extends HTMLElement { declare tagName: 'MY-ELEMENT'; // Necessary. public foo!: string; public bar?: number; public baz?: boolean; } customElements.define('my-element', MyElement); declare global { interface HTMLElementTagNameMap { 'my-element': MyElement; } interface HTMLElementHydrationParamsMap { 'my-element': Properties<MyElement, { required: 'foo', optional: 'bar' | 'baz', }>; } } export const MyComponent = defineSignalComponent('my-component', (host) => { host.query('my-element').hydrate(MyElement, { foo: 'test', // Required bar: 1234, // Optional baz: true, // Optional }); });
Currently it is not straightforward to define and access component parameters in a the functional authoring format of HydroActive components (hence why the above example uses a native custom element), this will be added later.
-
8b93477 - Adds
untracked. This allows reading signal values without adding them as a dependency of any currently active consumer.const foo = signal('foo'); const bar = signal('bar'); // Re-runs when `foo` changes, but not when `bar` changes because it is untracked. host.effect(() => { const f = foo(); const b = untracked(() => bar()); console.log(`${f} - ${b}`); });
-
ab672f4 -
ReactiveRoot.prototype.effectnow supports a custom scheduler. -
e80c0c9 - Implements the initial draft of the On-Demand Definitions community protocol. Components now automatically create a static
definemethod which callscustomElements.definerather than doing so immediately. This allows components to be tree shaken when unused.Dehydrated.prototype.accessandDehydrated.prototype.hydrateboth now call the staticdefineproperty of the input class if it exists. This allows a HydroActive component to automatically define any custom elements it depends upon.BREAKING CHANGE: Note that this change implicitly means that
defineSignalComponentanddefineBaseComponentno longer actually define their custom elements immediately (name change in a follow up commit). Components must be manually defined after class creation. This can either be done in the top-level scope, preferrable with the newdefinehelper, or implicitly when those components are used in.access/.hydrate.For example, given the pre-rendered HTML:
<my-other-component> <my-component defer-hydration></my-component> </my-other-component>
We can implement the two components like so:
import { component } from 'hydroactive'; export const MyComponent = component('my-component', () => { return { doSomething(): void { /* ... */ } }; }); // No need to call `MyComponent.define()` here as it is implicitly defined below when used. export const MyOtherComponent = component('my-other-component', (host) => { // `.access` Implicitly calls `MyComponent.define()`. host.query('my-component').access(MyComponent).element.doSomething(); }); MyOtherComponent.define(); // Should define in top-level scope so the pre-rendered HTML upgrades.
Regarding the question of whether or not to call
.definein the top-level scope, the general recommendation is that any custom element should either:- Be rendered with
defer-hydration-defer-hydrationimplies that whatever code hydrates that element is responsible for calling.defineon the appropriate custom element class (which automatically happens when calling.hydrate). - Be defined in the top-level scope - An element without
defer-hydrationis expected to become active on page load, therefore it needs to define itself in the top-level scope and cannot rely on any consumers defining it automatically.
- Be rendered with
-
9d9219f - Renames
defineSignalComponentanddefineBaseComponentto justcomponentandbaseComponent. This reflects the change in e80c0c9 whereby these functions no longer actually define the component they are creating as they no longer callcustomElements.define. -
283fee9 - Fix: Signals no longer notify consumers which are added during a consumer's notification from a signal change.
-
ef6033f - Fix: Exports
HydroActiveComponenttype.