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

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

Closed
patricknelson opened this issue Jun 1, 2023 · 8 comments
Closed

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

patricknelson opened this issue Jun 1, 2023 · 8 comments

Comments

@patricknelson
Copy link

patricknelson commented Jun 1, 2023

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

@dummdidumm
Copy link
Member

dummdidumm commented Jun 1, 2023

Assuming you compile with custom element mode, then customElements.define("tag-name", Component.element) works.
We need to document this.

@dummdidumm dummdidumm added this to the 4.x milestone Jun 1, 2023
@patricknelson
Copy link
Author

patricknelson commented Jun 1, 2023

I just tested that @dummdidumm and unfortunately that doesn't seem to be a complete workaround since HMR doesn't seem to work with that at all. Is there something else I need to do to facilitate HMR in Vite? 🤔

// HMR not working
import ExampleElement from './lib/ExampleElement.svelte';
customElements.define('example-element', ExampleElement.element);

vs.

// HMR works
import ExampleElement from './lib/ExampleElement.svelte';
import { create_custom_element } from 'svelte/internal';

customElements.define('example-element',
	create_custom_element(
		ExampleElement,
		{},
		[],
		[],
		false,
	)
);

@patricknelson
Copy link
Author

Setup a repro of the HMR issue in the hmr-bug branch at https://github.com/patricknelson/svelte-v4-custom-elements-define

git clone https://github.com/patricknelson/svelte-v4-custom-elements-define.git
cd svelte-v4-custom-elements-define
npm i
git checkout hmr-bug
npm run dev

@dominikg
Copy link
Member

dominikg commented Jun 1, 2023

oh, this is nice "side effect" of the changes to custom elements. hmr for these didn't work at all in svelte-3

you would have to ensure that customElements.define is only called once, as redifining them won't work. But the wrapper inside doing hmr would be awesome. Not sure how callbacks and prop changes would work but as long as the external interface stays unchanged, you'd be good to go.

Not sure if svelte-hmr could do something special to improve this even more.

cc @rixo

@patricknelson
Copy link
Author

Yeah, would definitely love to get HMR along with custom elements. My primary use case is in a fairly large and old codebase that cannot be converted to a Svelte (or SvelteKit) app.

That said, svelte-tag works flawlessly with combining both custom elements and HMR. It has some other issues though (mainly with slots and handling attributes with capital letters) so I figured it might be best to work toward improving Svelte itself as a longer term solution.

@patricknelson
Copy link
Author

patricknelson commented Jun 8, 2023

Putting this (i.e. HMR compatible declaration of Svelte v4 custom elements) on my roadmap to support in v2 of my new package svelte-retag (v1 being backward compatible with svelte-tag, as it is a fork). In v2 of the release, svelte-retag's API could theoretically support Svelte 4's <svelte:options customElements={...} /> syntax to ensure HMR support, e.g.

// Totally hypothetical...
svelteRetag({
	tag: 'example-element',
	shadow: 'none',
	props: {
		greetPerson: { reflect: true, attribute: 'greet-person' },
	},
});

All this is assuming of course HMR support isn't added by the time Svelte v4 comes out (since ideally svelte-retag itself would become irrelevant when v4 is out 🤞). Might have to create a new ticket for that. But essentially, right now what I'm getting with my svelte-retag package is basically the whole suite of features 🍰:

Apologies for this comment getting slightly out of hand, but I'm so excited for Svelte 4's upcoming Custom Elements that I want to do it now but also not lose functionality if I can help it (particularly simple bug-free HMR)! 😅

@patricknelson
Copy link
Author

patricknelson commented Jun 22, 2023

It appears this is now documented at https://svelte.dev/docs/custom-elements-api:

import MyElement from './MyElement.svelte';
 
customElements.define('my-element', MyElement.element);
// In Svelte 3, do this instead:
// customElements.define('my-element', MyElement);

I'll close this out and instead reopen a new ticket to address the HMR support as a separate feature request.

@dominikg: Should that ticket go here or into svelte-hmr?

@dominikg
Copy link
Member

svelte-hmr.

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

No branches or pull requests

3 participants