[WICG/webcomponents] [scoped-registries] Proposal for registry extension (Issue #989)

I would like to propose a new `extends` option that could be passed on `new CustomElementRegistry()`.

```js
const registry = new CustomElementRegistry({
  extends: [registryA, registryB, registryC]
});
```

## Use case

As a developer, I am building a new component inside the `Products` page of my application. For this component, I will use components coming from the following scopes:

- Global components for the entire app
- Components only available to the `Products` section.
- Components coming from my company's own `Core` library.
- Components coming from 3rd party LibA and Lib2.

In turn, those component may use other component libraries.

Without registry extension, it may be quite a challenge to build a registry that define every dependencies correctly.

With registry extension, I would do:

```js
const registry = new CustomElementRegistry({
  extends: [appRegistry, productsRegistry, coreRegistry, libARegistry, libBRegistry]
});
```

Conflicts between tag names (when 2 registries try to register the same name) would throw an error and would need to be handled manually.

## About scoped-constructor invocation

I believe that registry extension could be a good alternative to forbidding constructor on scoped elements, when combined with the restriction that constructor are registered on a single registry. 

Extending a common registry would allow reuse of component without having to subclass over and over. The only cases where subclassing would be needed would be when registering some components on a different name than the default one. But this could be handle automatically and the new registry could be cached, see `getRegistry` helper in userland extensions below.

## Userland extensions

You could see some userland ways to make life even easier, here are a few ideas.

Good practice from libraries would be to export a `getRegistry` function with an optional prefix so conflicts could be avoided.

```js
import { getRegistry as getLibARegistry  } from 'lib-a';
import { getRegistry as getLibBRegistry } from 'lib-b';

const registry = new CustomElementRegistry({
  extends: [
    // We register lib-a using predefined names
    getLibARegistry(), 
    // As lib-b conflicts with lib-a, we use a custom prefix for those elements.
    getLibBRegistry({ prefix: 'foobar' })
  ]
});
```

Libraries could build their own registries of multiple sub-registries, for when only part of the library is imported.

```js
const coreRegistry  = new CustomElementRegistry({
   extends: [
      modalRegistry,
      formRegistry,
      gridRegistry,
      titleRegistry,
     /// Etc.
   ];
});
```
Component could include a `getRegistry` static prop that would return a registry with the component's dependencies. Combines nicely with the [requiredElements proposal](https://github.com/WICG/webcomponents/issues/988).

```js
// Could be improved with registry caching and automatic subclassing.
function getRegistry({ prefix = DEFAULT_PREFIX } = {}) {
  const registry = new CustomElementRegistry({
   extends: (this.requiredElements || []).map((Element) => Element.getRegistry({ prefix }))
  });
  registry.define(`${prefix}-${this.baseName}`, this);
  return registry;
}

class ComponentA extends HTMLElement {
  static baseName 'a'
  static getRegistry = getRegistry;
}

class ComponentB extends HTMLElement {
  static baseName 'b'
  static requiredElements = [ComponentA];
  static getRegistry = getRegistry;
  connectedCallback() { this.appendChild(new ComponentA()); } 
}
```




-- 
Reply to this email directly or view it on GitHub:
https://github.com/WICG/webcomponents/issues/989
You are receiving this because you are subscribed to this thread.

Message ID: <WICG/webcomponents/issues/989@github.com>

Received on Thursday, 9 March 2023 13:45:51 UTC