Skip to content

Commit bc7e127

Browse files
committed
Merge branch 'main' into script-tag-execution
2 parents 178c52b + 3c2f4d2 commit bc7e127

File tree

80 files changed

+1104
-163
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+1104
-163
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`

.changeset/four-mice-hammer.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+
fix: improve internal proxied state signal heuristic

.changeset/giant-plants-grin.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+
fix: keep sibling selectors when dealing with slots/render tags/`svelte:element` tags

.changeset/mighty-frogs-obey.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+
fix: ensure deep mutation ownership widening

.changeset/popular-walls-hunt.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+
fix: improve compiled output of multiple call expression in single text node

.changeset/proud-queens-sniff.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+
fix: improve hydration of svelte head blocks

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/1-parse/read/style.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ export default function read_style(parser, start, attributes) {
3636
content: {
3737
start: content_start,
3838
end: content_end,
39-
styles: parser.template.slice(content_start, content_end)
39+
styles: parser.template.slice(content_start, content_end),
40+
comment: null
4041
}
4142
};
4243
}

packages/svelte/src/compiler/phases/1-parse/state/element.js

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -283,25 +283,26 @@ export default function tag(parser) {
283283

284284
if (is_top_level_script_or_style) {
285285
parser.eat('>', true);
286-
if (name === 'script') {
287-
const content = read_script(parser, start, element.attributes);
288286

289-
/** @type {import('#compiler').Comment | null} */
290-
let prev_comment = null;
291-
for (let i = current.fragment.nodes.length - 1; i >= 0; i--) {
292-
const node = current.fragment.nodes[i];
287+
/** @type {import('#compiler').Comment | null} */
288+
let prev_comment = null;
289+
for (let i = current.fragment.nodes.length - 1; i >= 0; i--) {
290+
const node = current.fragment.nodes[i];
293291

294-
if (i === current.fragment.nodes.length - 1 && node.end !== start) {
295-
break;
296-
}
292+
if (i === current.fragment.nodes.length - 1 && node.end !== start) {
293+
break;
294+
}
297295

298-
if (node.type === 'Comment') {
299-
prev_comment = node;
300-
break;
301-
} else if (node.type !== 'Text' || node.data.trim()) {
302-
break;
303-
}
296+
if (node.type === 'Comment') {
297+
prev_comment = node;
298+
break;
299+
} else if (node.type !== 'Text' || node.data.trim()) {
300+
break;
304301
}
302+
}
303+
304+
if (name === 'script') {
305+
const content = read_script(parser, start, element.attributes);
305306
if (prev_comment) {
306307
// We take advantage of the fact that the root will never have leadingComments set,
307308
// and set the previous comment to it so that the warning mechanism can later
@@ -318,6 +319,7 @@ export default function tag(parser) {
318319
}
319320
} else {
320321
const content = read_style(parser, start, element.attributes);
322+
content.content.comment = prev_comment;
321323

322324
if (current.css) error(start, 'duplicate-style-element');
323325
current.css = content;

packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,13 @@ function apply_selector(relative_selectors, rule, element, stylesheet) {
175175
let sibling_matched = false;
176176

177177
for (const possible_sibling of siblings.keys()) {
178-
if (apply_selector(parent_selectors, rule, possible_sibling, stylesheet)) {
178+
if (possible_sibling.type === 'RenderTag' || possible_sibling.type === 'SlotElement') {
179+
// `{@render foo()}<p>foo</p>` with `:global(.x) + p` is a match
180+
if (parent_selectors.length === 1 && parent_selectors[0].metadata.is_global) {
181+
mark(relative_selector, element);
182+
sibling_matched = true;
183+
}
184+
} else if (apply_selector(parent_selectors, rule, possible_sibling, stylesheet)) {
179185
mark(relative_selector, element);
180186
sibling_matched = true;
181187
}
@@ -564,38 +570,39 @@ function get_element_parent(node) {
564570
function find_previous_sibling(node) {
565571
/** @type {import('#compiler').SvelteNode} */
566572
let current_node = node;
567-
do {
568-
if (current_node.type === 'SlotElement') {
569-
const slot_children = current_node.fragment.nodes;
570-
if (slot_children.length > 0) {
571-
current_node = slot_children.slice(-1)[0]; // go to its last child first
572-
continue;
573-
}
574-
}
575-
while (
576-
// @ts-expect-error TODO
577-
!current_node.prev &&
578-
// @ts-expect-error TODO
579-
current_node.parent &&
580-
// @ts-expect-error TODO
581-
current_node.parent.type === 'SlotElement'
582-
) {
583-
// @ts-expect-error TODO
584-
current_node = current_node.parent;
573+
574+
while (
575+
// @ts-expect-error TODO
576+
!current_node.prev &&
577+
// @ts-expect-error TODO
578+
current_node.parent?.type === 'SlotElement'
579+
) {
580+
// @ts-expect-error TODO
581+
current_node = current_node.parent;
582+
}
583+
584+
// @ts-expect-error
585+
current_node = current_node.prev;
586+
587+
while (current_node?.type === 'SlotElement') {
588+
const slot_children = current_node.fragment.nodes;
589+
if (slot_children.length > 0) {
590+
current_node = slot_children.slice(-1)[0];
591+
} else {
592+
break;
585593
}
586-
// @ts-expect-error
587-
current_node = current_node.prev;
588-
} while (current_node && current_node.type === 'SlotElement');
594+
}
595+
589596
return current_node;
590597
}
591598

592599
/**
593600
* @param {import('#compiler').SvelteNode} node
594601
* @param {boolean} adjacent_only
595-
* @returns {Map<import('#compiler').RegularElement, NodeExistsValue>}
602+
* @returns {Map<import('#compiler').RegularElement | import('#compiler').SvelteElement | import('#compiler').SlotElement | import('#compiler').RenderTag, NodeExistsValue>}
596603
*/
597604
function get_possible_element_siblings(node, adjacent_only) {
598-
/** @type {Map<import('#compiler').RegularElement, NodeExistsValue>} */
605+
/** @type {Map<import('#compiler').RegularElement | import('#compiler').SvelteElement | import('#compiler').SlotElement | import('#compiler').RenderTag, NodeExistsValue>} */
599606
const result = new Map();
600607

601608
/** @type {import('#compiler').SvelteNode} */
@@ -618,6 +625,14 @@ function get_possible_element_siblings(node, adjacent_only) {
618625
if (adjacent_only && has_definite_elements(possible_last_child)) {
619626
return result;
620627
}
628+
} else if (
629+
prev.type === 'SlotElement' ||
630+
prev.type === 'RenderTag' ||
631+
prev.type === 'SvelteElement'
632+
) {
633+
result.set(prev, NODE_PROBABLY_EXISTS);
634+
// Special case: slots, render tags and svelte:element tags could resolve to no siblings,
635+
// so we want to continue until we find a definite sibling even with the adjacent-only combinator
621636
}
622637
}
623638

@@ -720,7 +735,7 @@ function get_possible_last_child(relative_selector, adjacent_only) {
720735
}
721736

722737
/**
723-
* @param {Map<import('#compiler').RegularElement, NodeExistsValue>} result
738+
* @param {Map<unknown, NodeExistsValue>} result
724739
* @returns {boolean}
725740
*/
726741
function has_definite_elements(result) {
@@ -734,8 +749,9 @@ function has_definite_elements(result) {
734749
}
735750

736751
/**
737-
* @param {Map<import('#compiler').RegularElement, NodeExistsValue>} from
738-
* @param {Map<import('#compiler').RegularElement, NodeExistsValue>} to
752+
* @template T
753+
* @param {Map<T, NodeExistsValue>} from
754+
* @param {Map<T, NodeExistsValue>} to
739755
* @returns {void}
740756
*/
741757
function add_to_map(from, to) {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { walk } from 'zimmerframe';
2+
import { warn } from '../../../warnings.js';
3+
import { is_keyframes_node } from '../../css.js';
4+
5+
/**
6+
* @param {import('#compiler').Css.StyleSheet} stylesheet
7+
* @param {import('../../types.js').RawWarning[]} warnings
8+
*/
9+
export function warn_unused(stylesheet, warnings) {
10+
walk(stylesheet, { warnings, stylesheet }, visitors);
11+
}
12+
13+
/** @type {import('zimmerframe').Visitors<import('#compiler').Css.Node, { warnings: import('../../types.js').RawWarning[], stylesheet: import('#compiler').Css.StyleSheet }>} */
14+
const visitors = {
15+
Atrule(node, context) {
16+
if (!is_keyframes_node(node)) {
17+
context.next();
18+
}
19+
},
20+
PseudoClassSelector(node, context) {
21+
if (node.name === 'is' || node.name === 'where') {
22+
context.next();
23+
}
24+
},
25+
ComplexSelector(node, context) {
26+
if (!node.metadata.used) {
27+
const content = context.state.stylesheet.content;
28+
const text = content.styles.substring(node.start - content.start, node.end - content.start);
29+
warn(context.state.warnings, node, context.path, 'css-unused-selector', text);
30+
}
31+
32+
context.next();
33+
}
34+
};

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { should_proxy_or_freeze } from '../3-transform/client/utils.js';
2323
import { analyze_css } from './css/css-analyze.js';
2424
import { prune } from './css/css-prune.js';
2525
import { hash } from './utils.js';
26+
import { warn_unused } from './css/css-warn.js';
2627

2728
/**
2829
* @param {import('#compiler').Script | null} script
@@ -548,6 +549,7 @@ export function analyze_component(root, source, options) {
548549
for (const element of analysis.elements) {
549550
prune(analysis.css.ast, element);
550551
}
552+
warn_unused(analysis.css.ast, analysis.warnings);
551553

552554
outer: for (const element of analysis.elements) {
553555
if (element.metadata.scoped) {

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
}

0 commit comments

Comments
 (0)