Description
Describe the bug
For v4, improvements were made in how custom elements were compiled in #8457:
Instead of compiling to a custom element class, the Svelte component class is mostly preserved as-is. Instead a wrapper is introduced which wraps a Svelte component constructor and returns a HTML element constructor
So, since components are no longer compiled directly to an instance of HTMLElement
, the traditional v3 approach of manually defining your custom element will no longer work:
import ExampleElement from './lib/ExampleElement.svelte';
customElements.define('example-element', ExampleElement);
Unfortunately, a public API doesn't exist for handling this. To workaround it right now, you need to reach into Svelte's internal API:
import ExampleElement from './lib/ExampleElement.svelte';
import { create_custom_element } from 'svelte/internal'; // ❌
customElements.define('example-element',
// ❌
create_custom_element(
ExampleElement, // Component constructor
{}, // props_definition
[], // slots
[], // accessors
false, // use_shadow_dom
)
);
Possible Solutions
In v4, we still need generate a class descending from HTMLElement
. Maybe we could get a new wrapper function that would either:
- Take arguments consistent with
<svelte:options customElement={...} />
when called - Just pass the component itself and Svelte can infer all the necessary options defined in
<svelte:options customElement={...} />
in the component itself for us (closer parity to v3). - Hybrid approach, i.e.: Take arguments but make them optional. If omitted, failover to inferring options defined in
<svelte:options customElement={...} />
. Explicitly passed options to this wrapper would probably take precedence over the ones inferred from the component file.
The goal of this would be to simplify documentation and refactoring as much as possible. That should hopefully make it far easier to reason about when reading docs or when needing to refactor/separate the options out when manually calling customElements.define()
.
For example:
main.js
import ExampleElement from './lib/ExampleElement.svelte';
import { createCustomElement } from 'svelte'; // ✔️
customElements.define('example-element',
// ✔️
createCustomElement(
ExampleElement,
// Options from <svelte:options customElement={...} /> (excluding "tag")
{
shadow: 'none',
props: {
greetPerson: { reflect: true, attribute: 'greet-person' },
},
}
)
);
ExampleElement.svelte
<svelte:options
customElement={{
tag: null,
shadow: 'none',
props: {
greetPerson: { reflect: true, attribute: 'greet-person' },
},
}}
/>
<script>
export let greetPerson = 'world';
</script>
<h1>Hello {greetPerson}!</h1>
Reproduction
Code: https://github.com/patricknelson/svelte-v4-custom-elements-define
Init repo
git clone https://github.com/patricknelson/svelte-v4-custom-elements-define.git
cd svelte-v4-custom-elements-define
npm i
Reproduce bug
git checkout main
npm run dev
Test workaround
git checkout workaround
npm run dev
Logs
No response
System Info
System:
OS: Linux 5.15 Debian GNU/Linux 11 (bullseye) 11 (bullseye)
CPU: (16) x64 Intel(R) Core(TM) i7-10875H CPU @ 2.30GHz
Memory: 2.41 GB / 5.79 GB
Container: Yes
Shell: 5.1.4 - /bin/bash
Binaries:
Node: 16.14.2 - ~/.nvm/versions/node/v16.14.2/bin/node
Yarn: 1.22.19 - ~/.nvm/versions/node/v16.14.2/bin/yarn
npm: 8.5.0 - ~/.nvm/versions/node/v16.14.2/bin/npm
Browsers:
Chrome: 111.0.5563.146
Severity
blocking an upgrade