From e7696311698d57ea6bf9c24e48caaff79d0dc90c Mon Sep 17 00:00:00 2001 From: Theodor Steiner Date: Wed, 7 Aug 2024 02:30:52 +0900 Subject: [PATCH] feat: make svelte:option customElement tag property optional (#12751) --- .changeset/four-kids-flow.md | 5 +++++ .../02-template-syntax/07-special-elements.md | 2 +- .../04-custom-elements-api.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 | 6 +++--- .../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, 43 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 000000000000..61744bc09cf5 --- /dev/null +++ b/.changeset/four-kids-flow.md @@ -0,0 +1,5 @@ +--- +"svelte": minor +--- + +feat: make customElement tag property optional (#12751) diff --git a/documentation/docs/02-template-syntax/07-special-elements.md b/documentation/docs/02-template-syntax/07-special-elements.md index 20d7bcd7ea67..c45550f91284 100644 --- a/documentation/docs/02-template-syntax/07-special-elements.md +++ b/documentation/docs/02-template-syntax/07-special-elements.md @@ -319,7 +319,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 name or [fine grained settings](/docs/custom-elements-api#component-options) to use when compiling this component as a custom element. ```svelte diff --git a/documentation/docs/04-compiler-and-api/04-custom-elements-api.md b/documentation/docs/04-compiler-and-api/04-custom-elements-api.md index 2227ae83c27d..ee0d8883e5ce 100644 --- a/documentation/docs/04-compiler-and-api/04-custom-elements-api.md +++ b/documentation/docs/04-compiler-and-api/04-custom-elements-api.md @@ -69,7 +69,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 4c78d4d13efc..4e1e4f4a923a 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 name or [fine grained settings](/docs/custom-elements-api#component-options) to use when compiling this component as a custom element. 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 2bb4197a1dec..c36ff9c4872f 100644 --- a/packages/svelte/messages/compile-errors/template.md +++ b/packages/svelte/messages/compile-errors/template.md @@ -318,7 +318,7 @@ ## 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 ee8afd8f5d88..282cd4bcfc40 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -1281,12 +1281,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 cae13c271ec2..3f53f9c94e33 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/options.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/options.js @@ -38,7 +38,7 @@ export default function read_options(node) { } case 'customElement': { /** @type {import('#compiler').SvelteOptions['customElement']} */ - const ce = { tag: '' }; + const ce = {}; const { value } = attribute; if (value === true) { @@ -76,8 +76,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]; @@ -233,8 +231,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 90b2a36f9511..4efcfcf60095 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 @@ -522,9 +522,9 @@ export function client_component(source, 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 set, we register the custom element directly. Else we still create + // the custom element class so that the user may instantiate/register a custom element themselves later. + 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 05ac91bda256..86256cc34fb7 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -76,7 +76,7 @@ export interface SvelteOptions { preserveWhitespace?: boolean; namespace?: Namespace; 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 000000000000..050ae7752098 --- /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 000000000000..054efab1dc79 --- /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 b74358966cea..71f8df4d0092 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 5d12875dc161..6eb3e4d1871f 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -1400,7 +1400,7 @@ declare module 'svelte/compiler' { preserveWhitespace?: boolean; namespace?: Namespace; customElement?: { - tag: string; + tag?: string; shadow?: 'open' | 'none'; props?: Record< string,