Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow creating a CustomElement without defining the tagName #55

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

jvdsande
Copy link

@jvdsande jvdsande commented Jan 6, 2021

What does this PR do

The goal of this PR is to separate the Preact-to-CustomElement process from the tag-name registration process.

Why is this PR needed

Registering a CustomElement using window.customElements.define uses a shared, global registry for custom-elements. Thus, only one CustomElement can be associated with a given tag-name. This can cause problems when authoring a component library: we "reserve" keywords on behalf of our end users. It also causes isolation issue for patterns such a micro-frontend architecture, where two versions of our library can't be used together, as they would clash while registering components.

Implementation details

This PR moves all the creation of PreactElement into its own function, toCustomElement. This function takes the same parameters as the default register export, omitting the second parameter (tag name).

The default exports is unchanged, and still registers the element right away.

Usage is as follow:

import register from 'preact-custom-element'

function MyComponent({ name = "World" }) {
  return <span>Hello {name}!</span>  
}

// Usage 1: Register the element right away (default)
register(MyComponent, "my-component", ['name'])


// Usage 2: Create the element, but do not register it
export const MyElement = register.toCustomElement(MyComponent, ['name'])

// Users can later do their own window.customElements.define('some-lib-element', MyElement)

Misc

Right now the toCustomElement function is attached as a property of register, as to not introduce a named export next to the default one (microbundle was not happy with that). However it's not the most elegant thing. We could maybe introduce a new option to the register function, called define: boolean, defaults to "true": whether to define the element after creating it. If false, the customElement will be returned instead of being defined

@leifriksheim
Copy link

👍

This would be so useful for our use case. In our app, we are dynamically importing web components and registering them manually so we avoid name collisions.

Copy link
Contributor

@bspaulding bspaulding left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes a lot of sense to me! Couple nits, can we use a named export and tweak the name?

@@ -1,6 +1,6 @@
import { h, cloneElement, render, hydrate } from 'preact';

export default function register(Component, tagName, propNames, options) {
function toCustomElement(Component, propNames, options) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function toCustomElement(Component, propNames, options) {
export function createCustomElement(Component, propNames, options) {

}

export default function register(Component, tagName, propNames, options) {
const PreactElement = toCustomElement(Component, propNames, options);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const PreactElement = toCustomElement(Component, propNames, options);
const PreactElement = createCustomElement(Component, propNames, options);

return customElements.define(
tagName || Component.tagName || Component.displayName || Component.name,
PreactElement
);
}

register.toCustomElement = toCustomElement;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
register.toCustomElement = toCustomElement;

return customElements.define(
tagName || Component.tagName || Component.displayName || Component.name,
PreactElement
);
}

register.toCustomElement = toCustomElement;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

}

it('allows manual deferred registration by just exposing the element', async () => {
const CustomElement = registerElement.toCustomElement(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const CustomElement = registerElement.toCustomElement(
const CustomElement = createCustomElement(

will need to add an import up top obv

This can be achieved by calling the `toCustomElements` function on the default export:

```js
import register from 'preact-custom-element'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import register from 'preact-custom-element'
import { toCustomElement } from 'preact-custom-element'

return <span>Hello {name}!</span>
}

export const MyElement = register.toCustomElement(MyComponent, ['name'])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export const MyElement = register.toCustomElement(MyComponent, ['name'])
export const MyElement = createCustomElement(MyComponent, ['name'])

export const MyElement = register.toCustomElement(MyComponent, ['name'])
```

`toCustomElement` has the same signature as `register`, omitting the second parameter (tag name).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`toCustomElement` has the same signature as `register`, omitting the second parameter (tag name).
`createCustomElement` has the same signature as `register`, omitting the second parameter (tag name).


If authoring a custom-elements library, you might want to expose your CustomElements without registering their tag name, to let your users register their own tags.

This can be achieved by calling the `toCustomElements` function on the default export:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This can be achieved by calling the `toCustomElements` function on the default export:
This can be achieved by calling the named export `createCustomElement`:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants