Skip to content

Using customElements.define() is not possible in v4 #8681

Closed
@patricknelson

Description

@patricknelson

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:

  1. Take arguments consistent with <svelte:options customElement={...} /> when called
  2. 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).
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions