Skip to content

Commit

Permalink
feat: make <svelte:option> customElement configuration's tag property…
Browse files Browse the repository at this point in the history
… optional (#12751) (#12754)

* feat: make svelte:option customElement tag property optional (#12751)

* tweak comment

* tweak docs

* tweak some more wording

* Update .changeset/four-kids-flow.md

---------

Co-authored-by: Rich Harris <[email protected]>
Co-authored-by: Rich Harris <[email protected]>
  • Loading branch information
3 people authored Aug 11, 2024
1 parent 97c0150 commit 7ae21ea
Show file tree
Hide file tree
Showing 13 changed files with 42 additions and 19 deletions.
5 changes: 5 additions & 0 deletions .changeset/four-kids-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"svelte": minor
---

feat: make custom element `tag` property optional
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ The `<svelte:options>` element provides a place to specify per-component compile
- `accessors={true}` — adds getters and setters for the component's props
- `accessors={false}` — the default
- `namespace="..."` — the namespace where this component will be used, most commonly "svg"; use the "foreign" namespace to opt out of case-insensitive attribute names and HTML-specific warnings
- `customElement="..."` — the name to use when compiling this component as a custom element
- `customElement={...}` — the [options](/docs/custom-elements-api#component-options) to use when compiling this component as a custom element. If a string is passed, it is used as the `tag` option

```svelte
<svelte:options customElement="my-custom-element" />
Expand Down
2 changes: 1 addition & 1 deletion documentation/docs/05-misc/04-custom-elements.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ The inner Svelte component is destroyed in the next tick after the `disconnected

When constructing a custom element, you can tailor several aspects by defining `customElement` as an object within `<svelte:options>` since Svelte 4. This object may contain the following properties:

- `tag`: the mandatory `tag` property for the custom element's name
- `tag: string`: an optional `tag` property for the custom element's name. If set, a custom element with this tag name will be defined with the document's `customElements` registry upon importing this component.
- `shadow`: an optional property that can be set to `"none"` to forgo shadow root creation. Note that styles are then no longer encapsulated, and you can't use slots
- `props`: an optional property to modify certain details and behaviors of your component's properties. It offers the following settings:
- `attribute: string`: To update a custom element's prop, you have two alternatives: either set the property on the custom element's reference as illustrated above or use an HTML attribute. For the latter, the default attribute name is the lowercase property name. Modify this by assigning `attribute: "<desired name>"`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ The options that can be set here are:
- `accessors={true}` — adds getters and setters for the component's props
- `accessors={false}` — the default
- `namespace="..."` — the namespace where this component will be used, most commonly `"svg"`
- `customElement="..."` — the name to use when compiling this component as a custom element
- `customElement={...}` — the [options](/docs/custom-elements-api#component-options) to use when compiling this component as a custom element. If a string is passed, it is used as the `tag` option

Consult the [API reference](/docs) for more information on these options.
2 changes: 1 addition & 1 deletion packages/svelte/messages/compile-errors/template.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ HTML restricts where certain elements can appear. In case of a violation the bro
## svelte_options_invalid_customelement

> "customElement" must be a string literal defining a valid custom element name or an object of the form { tag: string; shadow?: "open" | "none"; props?: { [key: string]: { attribute?: string; reflect?: boolean; type: .. } } }
> "customElement" must be a string literal defining a valid custom element name or an object of the form { tag?: string; shadow?: "open" | "none"; props?: { [key: string]: { attribute?: string; reflect?: boolean; type: .. } } }
## svelte_options_invalid_customelement_props

Expand Down
4 changes: 2 additions & 2 deletions packages/svelte/src/compiler/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1285,12 +1285,12 @@ export function svelte_options_invalid_attribute_value(node, list) {
}

/**
* "customElement" must be a string literal defining a valid custom element name or an object of the form { tag: string; shadow?: "open" | "none"; props?: { [key: string]: { attribute?: string; reflect?: boolean; type: .. } } }
* "customElement" must be a string literal defining a valid custom element name or an object of the form { tag?: string; shadow?: "open" | "none"; props?: { [key: string]: { attribute?: string; reflect?: boolean; type: .. } } }
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function svelte_options_invalid_customelement(node) {
e(node, "svelte_options_invalid_customelement", "\"customElement\" must be a string literal defining a valid custom element name or an object of the form { tag: string; shadow?: \"open\" | \"none\"; props?: { [key: string]: { attribute?: string; reflect?: boolean; type: .. } } }");
e(node, "svelte_options_invalid_customelement", "\"customElement\" must be a string literal defining a valid custom element name or an object of the form { tag?: string; shadow?: \"open\" | \"none\"; props?: { [key: string]: { attribute?: string; reflect?: boolean; type: .. } } }");
}

/**
Expand Down
8 changes: 1 addition & 7 deletions packages/svelte/src/compiler/phases/1-parse/read/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default function read_options(node) {
}
case 'customElement': {
/** @type {SvelteOptions['customElement']} */
const ce = { tag: '' };
const ce = {};
const { value: v } = attribute;
const value = v === true || Array.isArray(v) ? v : [v];

Expand Down Expand Up @@ -79,8 +79,6 @@ export default function read_options(node) {
const tag_value = tag[1]?.value;
validate_tag(tag, tag_value);
ce.tag = tag_value;
} else {
e.svelte_options_invalid_customelement(attribute);
}

const props = properties.find(([name]) => name === 'props')?.[1];
Expand Down Expand Up @@ -251,8 +249,4 @@ function validate_tag(attribute, tag) {
if (tag && !regex_valid_tag_name.test(tag)) {
e.svelte_options_invalid_tagname(attribute);
}
// TODO do we still need this?
// if (tag && !component.compile_options.customElement) {
// component.warn(attribute, compiler_warnings.missing_custom_element_compile_options);
// }
}
Original file line number Diff line number Diff line change
Expand Up @@ -611,9 +611,8 @@ export function client_component(analysis, options) {
/** @type {any} */ (typeof ce !== 'boolean' ? ce.extend : undefined)
);

// If customElement option is set, we define the custom element directly. Else we still create
// the custom element class so that the user may instantiate a custom element themselves later.
if (typeof ce !== 'boolean') {
// If a tag name is provided, call `customElements.define`, otherwise leave to the user
if (typeof ce !== 'boolean' && typeof ce.tag === 'string') {
body.push(b.stmt(b.call('customElements.define', b.literal(ce.tag), create_ce)));
} else {
body.push(b.stmt(create_ce));
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/compiler/types/template.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export interface SvelteOptions {
namespace?: Namespace;
css?: 'injected';
customElement?: {
tag: string;
tag?: string;
shadow?: 'open' | 'none';
props?: Record<
string,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { test } from '../../assert';
const tick = () => Promise.resolve();

export default test({
warnings: [],
async test({ assert, target, componentCtor }) {
customElements.define('no-tag', componentCtor.element);
target.innerHTML = '<no-tag name="world"></no-tag>';
await tick();

/** @type {any} */
const el = target.querySelector('no-tag');
const h1 = el.querySelector('h1');

assert.equal(el.shadowRoot, null);
assert.equal(h1.textContent, 'Hello world!');
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<svelte:options customElement={{ shadow: "none" }} />

<script>
export let name;
</script>

<h1>Hello {name}!</h1>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[
{
"code": "svelte_options_invalid_customelement",
"message": "\"customElement\" must be a string literal defining a valid custom element name or an object of the form { tag: string; shadow?: \"open\" | \"none\"; props?: { [key: string]: { attribute?: string; reflect?: boolean; type: .. } } }",
"message": "\"customElement\" must be a string literal defining a valid custom element name or an object of the form { tag?: string; shadow?: \"open\" | \"none\"; props?: { [key: string]: { attribute?: string; reflect?: boolean; type: .. } } }",
"start": {
"line": 1,
"column": 16
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1533,7 +1533,7 @@ declare module 'svelte/compiler' {
namespace?: Namespace;
css?: 'injected';
customElement?: {
tag: string;
tag?: string;
shadow?: 'open' | 'none';
props?: Record<
string,
Expand Down

0 comments on commit 7ae21ea

Please sign in to comment.