npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@wesib/wesib

v2.0.2

Published

Web components building blocks

Downloads

10

Readme

Wesib: Web Components Building Blocks

NPM Build Status Code Quality Coverage GitHub Project API Documentation

Wesib is a base for web components definition.

It provides a way to define custom elements. But instead of extending HTMLElement, it supports arbitrary component classes, and defines custom elements for them programmatically.

Wesib provides an IoC container, a component definition and lifecycle callbacks, and an infrastructure for opt-in features that can involve in component definition process and thus alter the resulting components in very flexible way.

This package provides a core API.

The @wesib/generic package provides generic web components and features.

The examples can be found in @wesib/examples.

Components

Wesib allows defining custom element by decorating a component class with @Component decorator:

import { Component } from '@wesib/wesib';

@Component('my-component') // Custom element name
export class MyComponent {
  // ...component definition
}

No need to extend HTMLElement or any other class. Instead, Wesib creates a custom element accordingly to its definition built either programmatically or using component decorators.

To register custom component(s) call bootstrapComponents() function like this:

import { bootstrapComponents } from '@wesib/wesib';

bootstrapComponents(MyComponent);

After that the custom element can be used anywhere in the document:

<my-component></my-component>

The component instance created along with a custom element and bound to it. All the logic of custom element delegated to the bound component instance.

Element Attributes

To define custom element attributes use @Attribute or @AttributeChanged component property decorators, or @Attributes component class decorator.

import { Attribute, AttributeChanged, Attributes, Component } from '@wesib/wesib';

@Component('my-component') // Custom element name
@Attributes('attribute-one', 'another-attribute')
export class MyComponent {
  @Attribute('attribute-two') // Attribute name. When omitted the property name is used
  attribute2!: string | null; // Attribute value is accessed instead.

  @AttributeChanged('attribute-three') // Attribute name. When omitted the method name is used
  setAttribute3(newValue: string, oldValue: string | null) {
    // This is called on attribute value modification with new and old values
  }
}
<my-component
    attribute-one="1"  <!-- Can be accessed with element's `element.getAttribute("attribute-one")` -->
    attribute-two="2"  <!-- Can be accessed as `attribute2` property of `MyComponent` -->
    attribute-three"3" <!-- Triggers `setAttribute3()` method call -->
></my-component>

Element Properties

To define the properties of custom element use a @DomProperty component property decorator.

import { Component, DomProperty } from '@wesib/wesib';

@Component('my-component') // Custom element name
export class MyComponent {
  @DomProperty('elementProperty') // Element property name. The decorated property name is used if omitted.
  customProperty = 12; // Element's `elementProperty` is backed by this one.
}

The same can be done for element methods with @DomMethod decorator, which is just a convenient alias for @DomProperty.

IoC Container

Wesib provides contexts for each component and feature (see below). This context can be used to access provided values.

For example, each component class constructor accepts a ComponentContext instance as its only argument.

import { Component, ComponentContext } from '@wesib/wesib';

@Component('my-component') // Custom element name
export class MyComponent {
  private readonly _service: MyService;

  constructor(context: ComponentContext) {
    this._service = context.get(MyService); // Obtain a `MyService` instance provided by some feature elsewhere.
  }
}

IoC container implementation is based on @proc7ts/context-values.

Features

Apart from custom elements definition and IoC container, everything in Wesib is an opt-in feature.

It is possible to define custom features to extend Wesib. E.g. to define or augment existing components, extend custom elements (like @Attribute or @DomProperty decorators do), or provide some context values.

The feature is a class decorated with @Feature decorator:

import { cxBuildAsset } from '@proc7ts/context-builder';
import { ComponentContext, DefinitionContext, Feature, FeatureContext, FeatureSetup } from '@wesib/wesib';

@Feature({
  needs: [
    OtherFeature1, // Requires other features to be enabled.
    MyComponent, // The required component will be defined too.
  ],
  setup(setup: FeatureSetup) {
    setup.provide(
      cxBuildAsset(
        GlobalService, // Provide a `GlobalService` available globally
        () => new GlobalService(), // in all IoC contexts
      ),
    );
    setup.perDefinition(
      cxBuildAsset(DefinitionService, ({ context: definitionContext }) => {
        // Provide a `DefinitionService` available during component definition.
        // Such service will be provided per component class
        // and will be available during custom element construction,
        // e.g. to `onDefinition()` listeners.
        return new DefinitionService(definitionContext);
      }),
    );
    setup.perComponent(
      cxBuildAsset(MyService, ({ context: componentContext }) => {
        // Provide a `MyService` available to components.
        // Such service will be provided per component instance
        // and will be available to component instance and `onComponent()` listeners.
        return new MyService(componentContext.component);
      }),
    );
  },
  init(context: FeatureContext) {
    // Bootstrap the feature by calling methods of provided context.

    context.onDefinition((definitionContext: DefinitionContext) => {
      // Notified on each component definition.

      // The service provided with `perDefinition()` method above is available here
      const definitionService = definitionContext.get(DefinitionService);

      definitionContext.whenReady(() => {
        // This is called when element class is defined.
        console.log(
          `Define element class ${definitionContext.elementType.name}` +
            ` for component of ${definitionContext.componentType.name} type`,
        );
      });
    });
    context.onComponent((componentContext: ComponentContext) => {
      // Notified on each component instantiation.

      // The service provided with `perComponent()` method above is available here
      const myService = componentContext.get(MyService);

      componentContext.whenReady(() => {
        // This is called when component is instantiated,
        // which happens right after custom element instantiation.
        console.log(componentContext.element, ` is instantiated for`, componentContext.component);
      });
    });
  },
})
export class MyFeature {}

To enable a custom feature just pass it to bootstrapComponents() like this:

import { bootstrapComponents } from '@wesib/wesib';

bootstrapComponents(MyFeature);

Note that components are kind of features that, when passed to this function (or enabled with needs option), register themselves as components.

Component State

Whenever a component state changes, e.g. when element attribute or property value changes, a state update notification issued.

A state update notification can also be issued by calling a ComponentContext.updateState() method:

import { Component, ComponentContext } from '@wesib/wesib';

@Component('my-component') // Custom element name
export class MyComponent {
  data: any;

  constructor(private readonly _context: ComponentContext) {}

  async loadData() {
    const newData = await fetch('/api/data').then(response => response.json());
    const oldData = this.data;

    this.data = newData;
    this._context.updateState('data', newData, oldData); // Update the state
  }
}

A ComponentState instance available in component context allows to track the component state updates.

Shadow DOM

It is possible to attach shadow root to custom element by decorating the component with @AttachShadow decorator.

If shadow DOM is supported, then a shadow root will be attached to element. Otherwise, an element itself will be used as shadow root. In both cases the shadow root will be available in component context under [ShadowContentRoot] key.

A ComponentContext.contentRoot property is always available. It either contains a shadow root, or element itself. This is a root DOM node component element contents.

Rendering

Wesib core does not provide any mechanics for component rendering. It is completely up to the developer which rendering mechanics to use: direct DOM manipulations, template processing, virtual DOM, etc.

However, Wesib is able to notify the renderer on component state updates and trigger its rendering. For that a @Render decorator can be applied to component renderer method:

import { Attribute, Component, ComponentContext, Render } from '@wesib/wesib';

@Component('greet-text')
export class GreetTextComponent {
  @Attribute()
  name: string | null;

  constructor(private readonly _context: ComponentContext) {}

  @Render()
  render() {
    this._context.contentRoot.innerText = `Hello, ${this.name}!`;
  }
}

The @Render-decorated method will be called from requestAnimationFrame() callback by default. So, it won't be called too frequently.