Skip to content

Commit 22494be

Browse files
authored
feat: introduce $host rune, deprecate createEventDispatcher (#11059)
closes #11022
1 parent 8578857 commit 22494be

File tree

18 files changed

+189
-11
lines changed

18 files changed

+189
-11
lines changed

.changeset/chilly-rocks-hug.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
feat: introduce `$host` rune, deprecate `createEventDispatcher`

packages/svelte/src/ambient.d.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,24 @@ declare function $bindable<T>(t?: T): T;
211211
declare function $inspect<T extends any[]>(
212212
...values: T
213213
): { with: (fn: (type: 'init' | 'update', ...values: T) => void) => void };
214+
215+
/**
216+
* Retrieves the `this` reference of the custom element that contains this component. Example:
217+
*
218+
* ```svelte
219+
* <svelte:options customElement="my-element" />
220+
*
221+
* <script>
222+
* function greet(greeting) {
223+
* $host().dispatchEvent(new CustomEvent('greeting', { detail: greeting }))
224+
* }
225+
* </script>
226+
*
227+
* <button onclick={() => greet('hello')}>say hello</button>
228+
* ```
229+
*
230+
* Only available inside custom element components, and only on the client-side.
231+
*
232+
* https://svelte-5-preview.vercel.app/docs/runes#$host
233+
*/
234+
declare function $host<El extends HTMLElement = HTMLElement>(): El;

packages/svelte/src/compiler/errors.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ const runes = {
187187
'invalid-state-location': (rune) =>
188188
`${rune}(...) can only be used as a variable declaration initializer or a class field`,
189189
'invalid-effect-location': () => `$effect() can only be used as an expression statement`,
190+
'invalid-host-location': () =>
191+
`$host() can only be used inside custom element component instances`,
190192
/**
191193
* @param {boolean} is_binding
192194
* @param {boolean} show_details

packages/svelte/src/compiler/phases/2-analyze/validation.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,9 @@ export const validation_runes_js = {
896896
}
897897
},
898898
CallExpression(node, { state, path }) {
899+
if (get_rune(node, state.scope) === '$host') {
900+
error(node, 'invalid-host-location');
901+
}
899902
validate_call_expression(node, state.scope, path);
900903
},
901904
VariableDeclarator(node, { state }) {
@@ -1063,9 +1066,17 @@ export const validation_runes = merge(validation, a11y_validators, {
10631066
}
10641067
},
10651068
CallExpression(node, { state, path }) {
1066-
if (get_rune(node, state.scope) === '$bindable' && node.arguments.length > 1) {
1069+
const rune = get_rune(node, state.scope);
1070+
if (rune === '$bindable' && node.arguments.length > 1) {
10671071
error(node, 'invalid-rune-args-length', '$bindable', [0, 1]);
1072+
} else if (rune === '$host') {
1073+
if (node.arguments.length > 0) {
1074+
error(node, 'invalid-rune-args-length', '$host', [0]);
1075+
} else if (state.ast_type === 'module' || !state.analysis.custom_element) {
1076+
error(node, 'invalid-host-location');
1077+
}
10681078
}
1079+
10691080
validate_call_expression(node, state.scope, path);
10701081
},
10711082
EachBlock(node, { next, state }) {

packages/svelte/src/compiler/phases/3-transform/client/transform-client.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -399,15 +399,12 @@ export function client_component(source, analysis, options) {
399399
}
400400

401401
if (analysis.uses_props || analysis.uses_rest_props) {
402+
const to_remove = [b.literal('children'), b.literal('$$slots'), b.literal('$$events')];
403+
if (analysis.custom_element) {
404+
to_remove.push(b.literal('$$host'));
405+
}
402406
component_block.body.unshift(
403-
b.const(
404-
'$$sanitized_props',
405-
b.call(
406-
'$.rest_props',
407-
b.id('$$props'),
408-
b.array([b.literal('children'), b.literal('$$slots'), b.literal('$$events')])
409-
)
410-
)
407+
b.const('$$sanitized_props', b.call('$.rest_props', b.id('$$props'), b.array(to_remove)))
411408
);
412409
}
413410

packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,10 @@ export const javascript_visitors_runes = {
380380
CallExpression(node, context) {
381381
const rune = get_rune(node, context.state.scope);
382382

383+
if (rune === '$host') {
384+
return b.id('$$props.$$host');
385+
}
386+
383387
if (rune === '$effect.active') {
384388
return b.call('$.effect_active');
385389
}

packages/svelte/src/compiler/phases/3-transform/server/transform-server.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,10 @@ const javascript_visitors_runes = {
785785
CallExpression(node, context) {
786786
const rune = get_rune(node, context.state.scope);
787787

788+
if (rune === '$host') {
789+
return b.id('undefined');
790+
}
791+
788792
if (rune === '$effect.active') {
789793
return b.literal(false);
790794
}

packages/svelte/src/compiler/phases/constants.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ export const Runes = /** @type {const} */ ([
4040
'$effect.active',
4141
'$effect.root',
4242
'$inspect',
43-
'$inspect().with'
43+
'$inspect().with',
44+
'$host'
4445
]);
4546

4647
/**

packages/svelte/src/index-client.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ function create_custom_event(type, detail, { bubbles = false, cancelable = false
8080
* ```
8181
*
8282
* https://svelte.dev/docs/svelte#createeventdispatcher
83+
* @deprecated Use callback props and/or the `$host()` rune instead — see https://svelte-5-preview.vercel.app/docs/deprecations#createeventdispatcher
8384
* @template {Record<string, any>} [EventMap = any]
8485
* @returns {import('./index.js').EventDispatcher<EventMap>}
8586
*/

packages/svelte/src/internal/client/dom/elements/custom-element.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ if (typeof HTMLElement === 'function') {
138138
target: this.shadowRoot || this,
139139
props: {
140140
...this.$$d,
141-
$$slots
141+
$$slots,
142+
$$host: this
142143
}
143144
});
144145

0 commit comments

Comments
 (0)