Skip to content

releases/0.1.7

Pre-release
Pre-release

Choose a tag to compare

@dgp1130 dgp1130 released this 15 Dec 00:52

BREAKING 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 - hydrate and Dehydrated.prototype.hydrate now 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 global HTMLElementHydrationParamsMap interface (typically via the Properties type) which hydrate uses 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.effect now supports a custom scheduler.

  • e80c0c9 - Implements the initial draft of the On-Demand Definitions community protocol. Components now automatically create a static define method which calls customElements.define rather than doing so immediately. This allows components to be tree shaken when unused. Dehydrated.prototype.access and Dehydrated.prototype.hydrate both now call the static define property 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 defineSignalComponent and defineBaseComponent no 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 new define helper, 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 .define in the top-level scope, the general recommendation is that any custom element should either:

    1. Be rendered with defer-hydration - defer-hydration implies that whatever code hydrates that element is responsible for calling .define on the appropriate custom element class (which automatically happens when calling .hydrate).
    2. Be defined in the top-level scope - An element without defer-hydration is 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.
  • 9d9219f - Renames defineSignalComponent and defineBaseComponent to just component and baseComponent. This reflects the change in e80c0c9 whereby these functions no longer actually define the component they are creating as they no longer call customElements.define.

  • 283fee9 - Fix: Signals no longer notify consumers which are added during a consumer's notification from a signal change.

  • ef6033f - Fix: Exports HydroActiveComponent type.