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

component-register

v0.8.7

Published

Basic Wrapper around Web Components

Downloads

139,548

Readme

Component Register

Build Status Coverage Status NPM Version

This library provides a framework agnostic base for webcomponents by the use of simple composition to wrap components from any existing library.

Don't know anything about webcomponents? Doesn't matter! This library is designed to transparently wrap the existing Components you have. None of this psuedo API imposing. Want React WebComponents?Write natural React Components.

Unlike Polymer or Stencil you don't need to learn a new Framework. Unlike SkateJS you don't need to learn new ways to interact with your old Framework. Webcomponents without the cognitive overhead.

This library is designed to work in environments that already support custom elements, templates, and shadow dom. If those are not present your target browser can include the component-register-platform shim which includes some ES2015 and webcomponents.js polyfills to give support back to IE11. Alternatively for more modern browsers without full support the webcomponents.js polyfills will do the trick.

Plugins

  • component-register-hooks A React Hooks API inspired state management extension to integrate with your favourite micro-renderer like LitHTML, HyperHTML, or IncrementalDOM.

  • solid-element Custom Element implementation for Solid that showcases some more powerful usage of the library with Fine Grained change managment.

  • component-register-preact This implementation to demonstrate using Preact Components as is as Custom Elements.

  • component-register-react This implementation to demonstrate using React Components as is as Custom Elements. Does event retargetting to support Reacts event system in the Shadow DOM.

  • component-register-ko The project where I started experimenting with generalizing webcomponents. It has a lot of extras but is good example of a template based rendering library with fine grained change management and support for 2 way binding.

Getting Started

The simplest use would be:

import { register } from 'component-register';

register('my-element')((props, { element }) =>
  element.renderRoot.innerHTML = 'Hello World'
)

This creates a custom element with the tag 'my-element' and the text 'Hello World' in its shadow root.

You can also define props by giving a name and a default value:

import { register } from 'component-register';

register('my-greeting', {name: 'World'})((props, { element }) =>
  element.renderRoot.innerHTML = `Hello ${props.name}`
)

These props map to both element[propName] and an attribute prop-name. Alternatively you can initialize props with an object which has properties value (default value), notify (fire an event on change, for 2 way binding libraries), and attribute (if you want to name the attribute for the prop differently than the default).

You can register a callback handler via:

element.addPropertyChangedCallback(fn)

The function handler is passed (name, value, prevValue).

You can also register a release callback via:

element.addReleaseCallback(fn)

Functions registered this way will be called when the component has been removed from the DOM for full Macrotask cycle.

With these 2 methods its very easy to write mixins that can react to changes and cleanup after themselves. The library includes createMixin as an easy way to add behavior to your elements. For example making a mixin to make a Component draggable with optional opacity on drag:

import { createMixin } from 'component-register';

export default function withDraggable({opacity} = {}) {
  return createMixin((options) => {
    let { element } = options,
      pos1, pos2, pos3, pos4;
    pos1 = pos2 = pos3 = pos4 = null;
    element.style.position = 'absolute';
    element.onmousedown = function(e) {
      pos3 = e.clientX;
      pos4 = e.clientY;
      if (opacity != null) {
        element.style.opacity = opacity;
      }
      document.onmouseup = closeDragElement;
      document.onmousemove = elementDrag;
    };
    function elementDrag(e) {
      pos1 = pos3 - e.clientX;
      pos2 = pos4 - e.clientY;
      pos3 = e.clientX;
      pos4 = e.clientY;
      element.style.top = (element.offsetTop - pos2) + "px";
      element.style.left = (element.offsetLeft - pos1) + "px";
    };
    function closeDragElement() {
      if (opacity != null) {
        element.style.opacity = 1;
      }
      document.onmouseup = null;
      document.onmousemove = null;
    };
    return options;
  });
}

To use this mixin you would just wrap your component like so:

import { register } from 'component-register';

register('my-draggable')(withDraggable({opacity: 0.6})((props, { element }) =>
  // ....
))

Or using compose:

import { register, compose } from 'component-register';

compose(
  register('my-draggable'),
  withDraggable({opacity: 0.6})
)(props, { element }) =>
  // ....
))

component-register-extensions includes some other examples of simple mixins.

Alternative the library exposes a getCurrentElement() method that can be used to create mixins that can be added in the constructor or initialization function of the component without explicitly passing in the element.

Context API

For dependency injection this library supports a Provider/Consumer Context API.

createContext(initFn): ContextObject

If an init function is provided it will be called by the provider on creation with the provided value.

withProvider(context, initialValue)

provide(context, initialValue)

HOC and direct method for adding a new provider instance of the supplied context in the render tree.

withConsumer(context, key)

consume(context): contextInstance

HOC mixins in context for the component on that key and direct method returns the context instance.

// counter.js
import { createContext } from 'component-register';

/* You can put whatever you want in here, as this container is not responsible for the reactivity of your application you need to provide your own mechanisms. */
export createContext((count = 0) => {
  return [count, {
    increment() { count += 1; }
    decrement() { count -= 1; }
  }];
});

// app.js
import { register, compose, withProvider } from 'component-register';
import CounterContext from './counter';

const AppComponent = /* Some component */

compose(
  register('app-component'),
  withProvider(CounterContext)
)(AppComponent);

// nested.js
import { register, compose, withConsumer } from 'component-register';
import CounterContext from './counter';

const NestedComponent = (props, { counter }) => { /* ... */ }

compose(
  register('nested-component'),
  withConsumer(CounterContext, 'counter')
)(NestedComponent);

Hot Module Replacement (New)

Component Register provides a Hot Module Replacement solution for Webpack and Parcel. This is a new feature, that is looking for feedback.

Hot Module Replacement with Web Components is not the most trivial thing. Custom Elements can only be defined once. SkateJS uses a mechanism where they increment the tag name to ensure each iteration can be replaced. However, Component Register never exposes the native Web Component APIs so it can handle updates internally. What it does is keep the current Element attached to the page and swaps out the supplied Component with new version.

Since Component Register is unaware of your Component's internals it just calls the release callbacks and instantiates the Component again completely re-rendering the subtree without affecting any ancestors or siblings. In so local state is not preserved.

To use HMR make sure your bundler/dev server is configured(See Webpack instructions here). Then import hot and pass in module and the tag name of your component.

import { register, hot } from 'component-register';

register('my-element')((props, element) => { /* component code */ }));

hot(module, 'my-element');

Or use the webpack loader Component Register Loader to have it automatically apply to your default exported component.

HMR generally will look at the changed file, and if it is not accepted trace up the dependencies. So at a minimum you can do the following on the root element to have the full page reloading. But applying this to specific nested elements you can reduce the impact of the update.