From 7ae21ea97de84bd42b95cff5b8de77c9ce0830df Mon Sep 17 00:00:00 2001 From: Theodor Steiner <40017636+Theo-Steiner@users.noreply.github.com> Date: Sun, 11 Aug 2024 20:02:33 +0900 Subject: [PATCH] feat: make customElement configuration's tag property 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 Co-authored-by: Rich Harris --- .changeset/four-kids-flow.md | 5 +++++ .../02-template-syntax/09-special-elements.md | 2 +- .../docs/05-misc/04-custom-elements.md | 2 +- .../09-svelte-options/text.md | 2 +- .../svelte/messages/compile-errors/template.md | 2 +- packages/svelte/src/compiler/errors.js | 4 ++-- .../compiler/phases/1-parse/read/options.js | 8 +------- .../3-transform/client/transform-client.js | 5 ++--- .../svelte/src/compiler/types/template.d.ts | 2 +- .../no-tag-ce-options/_config.js | 18 ++++++++++++++++++ .../no-tag-ce-options/main.svelte | 7 +++++++ .../samples/tag-non-string/errors.json | 2 +- packages/svelte/types/index.d.ts | 2 +- 13 files changed, 42 insertions(+), 19 deletions(-) create mode 100644 .changeset/four-kids-flow.md create mode 100644 packages/svelte/tests/runtime-browser/custom-elements-samples/no-tag-ce-options/_config.js create mode 100644 packages/svelte/tests/runtime-browser/custom-elements-samples/no-tag-ce-options/main.svelte diff --git a/.changeset/four-kids-flow.md b/.changeset/four-kids-flow.md new file mode 100644 index 0000000000..894816dfb6 --- /dev/null +++ b/.changeset/four-kids-flow.md @@ -0,0 +1,5 @@ +--- +"svelte": minor +--- + +feat: make custom element `tag` property optional diff --git a/documentation/docs/02-template-syntax/09-special-elements.md b/documentation/docs/02-template-syntax/09-special-elements.md index a46779c1cc..b2f6e1e134 100644 --- a/documentation/docs/02-template-syntax/09-special-elements.md +++ b/documentation/docs/02-template-syntax/09-special-elements.md @@ -185,7 +185,7 @@ The `` 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 diff --git a/documentation/docs/05-misc/04-custom-elements.md b/documentation/docs/05-misc/04-custom-elements.md index 46c1bb6f13..5cb3bbfbb1 100644 --- a/documentation/docs/05-misc/04-custom-elements.md +++ b/documentation/docs/05-misc/04-custom-elements.md @@ -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 `` 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: ""`. diff --git a/documentation/tutorial/16-special-elements/09-svelte-options/text.md b/documentation/tutorial/16-special-elements/09-svelte-options/text.md index 4c78d4d13e..2049365efc 100644 --- a/documentation/tutorial/16-special-elements/09-svelte-options/text.md +++ b/documentation/tutorial/16-special-elements/09-svelte-options/text.md @@ -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. diff --git a/packages/svelte/messages/compile-errors/template.md b/packages/svelte/messages/compile-errors/template.md index 7920329c8b..affd44e943 100644 --- a/packages/svelte/messages/compile-errors/template.md +++ b/packages/svelte/messages/compile-errors/template.md @@ -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 diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index 958c86a817..8b650ebf3c 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -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: .. } } }"); } /** diff --git a/packages/svelte/src/compiler/phases/1-parse/read/options.js b/packages/svelte/src/compiler/phases/1-parse/read/options.js index 7c83946336..a8ba7e216c 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/options.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/options.js @@ -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]; @@ -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]; @@ -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); - // } } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index c4af60f24b..15c0de5df8 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -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)); diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index 20fdb25ee4..7f2e808ade 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -78,7 +78,7 @@ export interface SvelteOptions { namespace?: Namespace; css?: 'injected'; customElement?: { - tag: string; + tag?: string; shadow?: 'open' | 'none'; props?: Record< string, diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/no-tag-ce-options/_config.js b/packages/svelte/tests/runtime-browser/custom-elements-samples/no-tag-ce-options/_config.js new file mode 100644 index 0000000000..050ae77520 --- /dev/null +++ b/packages/svelte/tests/runtime-browser/custom-elements-samples/no-tag-ce-options/_config.js @@ -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 = ''; + 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!'); + } +}); diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/no-tag-ce-options/main.svelte b/packages/svelte/tests/runtime-browser/custom-elements-samples/no-tag-ce-options/main.svelte new file mode 100644 index 0000000000..054efab1dc --- /dev/null +++ b/packages/svelte/tests/runtime-browser/custom-elements-samples/no-tag-ce-options/main.svelte @@ -0,0 +1,7 @@ + + + + +

Hello {name}!

diff --git a/packages/svelte/tests/validator/samples/tag-non-string/errors.json b/packages/svelte/tests/validator/samples/tag-non-string/errors.json index b74358966c..71f8df4d00 100644 --- a/packages/svelte/tests/validator/samples/tag-non-string/errors.json +++ b/packages/svelte/tests/validator/samples/tag-non-string/errors.json @@ -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 diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index df012254f6..4307b5c267 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -1533,7 +1533,7 @@ declare module 'svelte/compiler' { namespace?: Namespace; css?: 'injected'; customElement?: { - tag: string; + tag?: string; shadow?: 'open' | 'none'; props?: Record< string,