Skip to content

Commit 05d6658

Browse files
committed
Merge branch 'main' into deconflict-snippet
2 parents 4267924 + 2c807ad commit 05d6658

File tree

45 files changed

+778
-816
lines changed

Some content is hidden

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

45 files changed

+778
-816
lines changed

.changeset/dry-parrots-bathe.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: support contenteditable binding undefined fallback

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"private": true,
66
"type": "module",
77
"license": "MIT",
8-
"packageManager": "pnpm@9.2.0",
8+
"packageManager": "pnpm@9.4.0",
99
"engines": {
1010
"pnpm": "^9.0.0"
1111
},
@@ -30,18 +30,18 @@
3030
},
3131
"devDependencies": {
3232
"@changesets/cli": "^2.27.6",
33-
"@sveltejs/eslint-config": "^7.0.1",
33+
"@sveltejs/eslint-config": "^8.0.1",
3434
"@svitejs/changesets-changelog-github-compact": "^1.1.0",
3535
"@types/node": "^20.11.5",
3636
"@vitest/coverage-v8": "^1.2.1",
37-
"eslint": "^9.0.0",
37+
"eslint": "^9.6.0",
3838
"eslint-plugin-lube": "^0.4.3",
3939
"jsdom": "22.0.0",
4040
"playwright": "^1.41.1",
4141
"prettier": "^3.2.4",
4242
"prettier-plugin-svelte": "^3.1.2",
4343
"typescript": "^5.5.2",
44-
"typescript-eslint": "^8.0.0-alpha.20",
44+
"typescript-eslint": "^8.0.0-alpha.34",
4545
"v8-natives": "^1.2.5",
4646
"vitest": "^1.2.1"
4747
},

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,10 @@ function special(parser) {
594594
type: 'RenderTag',
595595
start,
596596
end: parser.index,
597-
expression: expression
597+
expression: expression,
598+
metadata: {
599+
dynamic: false
600+
}
598601
});
599602
}
600603
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1520,6 +1520,13 @@ const common_visitors = {
15201520
return;
15211521
}
15221522
}
1523+
},
1524+
Component(node, context) {
1525+
const binding = context.state.scope.get(
1526+
node.name.includes('.') ? node.name.slice(0, node.name.indexOf('.')) : node.name
1527+
);
1528+
1529+
node.metadata.dynamic = binding !== null && binding.kind !== 'normal';
15231530
}
15241531
};
15251532

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,11 @@ const validation = {
633633
});
634634
},
635635
RenderTag(node, context) {
636+
const callee = unwrap_optional(node.expression).callee;
637+
638+
node.metadata.dynamic =
639+
callee.type !== 'Identifier' || context.state.scope.get(callee.name)?.kind !== 'normal';
640+
636641
context.state.analysis.uses_render_tags = true;
637642

638643
const raw_args = unwrap_optional(node.expression).arguments;
@@ -642,7 +647,6 @@ const validation = {
642647
}
643648
}
644649

645-
const callee = unwrap_optional(node.expression).callee;
646650
if (
647651
callee.type === 'MemberExpression' &&
648652
callee.property.type === 'Identifier' &&

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

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
EACH_KEYED,
3737
is_capture_event,
3838
TEMPLATE_FRAGMENT,
39+
TEMPLATE_UNSET_START,
3940
TEMPLATE_USE_IMPORT_NODE,
4041
TRANSITION_GLOBAL,
4142
TRANSITION_IN,
@@ -942,6 +943,7 @@ function serialize_inline_component(node, component_name, context) {
942943
fn = (node_id) => {
943944
return b.call(
944945
'$.component',
946+
node_id,
945947
b.thunk(/** @type {import('estree').Expression} */ (context.visit(node.expression))),
946948
b.arrow(
947949
[b.id(component_name)],
@@ -1679,14 +1681,35 @@ export const template_visitors = {
16791681

16801682
process_children(trimmed, expression, false, { ...context, state });
16811683

1684+
var first = trimmed[0];
1685+
1686+
/**
1687+
* If the first item in an effect is a static slot or render tag, it will clone
1688+
* a template but without creating a child effect. In these cases, we need to keep
1689+
* the current `effect.nodes.start` undefined, so that it can be populated by
1690+
* the item in question
1691+
* TODO come up with a better name than `unset`
1692+
*/
1693+
var unset = false;
1694+
1695+
if (first.type === 'SlotElement') unset = true;
1696+
if (first.type === 'RenderTag' && !first.metadata.dynamic) unset = true;
1697+
if (first.type === 'Component' && !first.metadata.dynamic && !context.state.options.hmr) {
1698+
unset = true;
1699+
}
1700+
16821701
const use_comment_template = state.template.length === 1 && state.template[0] === '<!>';
16831702

16841703
if (use_comment_template) {
16851704
// special case — we can use `$.comment` instead of creating a unique template
1686-
body.push(b.var(id, b.call('$.comment')));
1705+
body.push(b.var(id, b.call('$.comment', unset && b.literal(unset))));
16871706
} else {
16881707
let flags = TEMPLATE_FRAGMENT;
16891708

1709+
if (unset) {
1710+
flags |= TEMPLATE_UNSET_START;
1711+
}
1712+
16901713
if (state.metadata.context.template_needs_import_node) {
16911714
flags |= TEMPLATE_USE_IMPORT_NODE;
16921715
}
@@ -1831,27 +1854,26 @@ export const template_visitors = {
18311854
context.state.template.push('<!>');
18321855
const callee = unwrap_optional(node.expression).callee;
18331856
const raw_args = unwrap_optional(node.expression).arguments;
1834-
const is_reactive =
1835-
callee.type !== 'Identifier' || context.state.scope.get(callee.name)?.kind !== 'normal';
18361857

1837-
/** @type {import('estree').Expression[]} */
1838-
const args = [context.state.node];
1839-
for (const arg of raw_args) {
1840-
args.push(b.thunk(/** @type {import('estree').Expression} */ (context.visit(arg))));
1841-
}
1858+
const args = raw_args.map((arg) =>
1859+
b.thunk(/** @type {import('estree').Expression} */ (context.visit(arg)))
1860+
);
18421861

18431862
let snippet_function = /** @type {import('estree').Expression} */ (context.visit(callee));
18441863
if (context.state.options.dev) {
18451864
snippet_function = b.call('$.validate_snippet', snippet_function);
18461865
}
18471866

1848-
if (is_reactive) {
1849-
context.state.init.push(b.stmt(b.call('$.snippet', b.thunk(snippet_function), ...args)));
1867+
if (node.metadata.dynamic) {
1868+
context.state.init.push(
1869+
b.stmt(b.call('$.snippet', context.state.node, b.thunk(snippet_function), ...args))
1870+
);
18501871
} else {
18511872
context.state.init.push(
18521873
b.stmt(
18531874
(node.expression.type === 'CallExpression' ? b.call : b.maybe_call)(
18541875
snippet_function,
1876+
context.state.node,
18551877
...args
18561878
)
18571879
)
@@ -1914,7 +1936,7 @@ export const template_visitors = {
19141936
}
19151937

19161938
if (node.name === 'noscript') {
1917-
context.state.template.push('<!>');
1939+
context.state.template.push('<noscript></noscript>');
19181940
return;
19191941
}
19201942
if (node.name === 'script') {
@@ -3001,16 +3023,14 @@ export const template_visitors = {
30013023
}
30023024
},
30033025
Component(node, context) {
3004-
const binding = context.state.scope.get(
3005-
node.name.includes('.') ? node.name.slice(0, node.name.indexOf('.')) : node.name
3006-
);
3007-
if (binding !== null && binding.kind !== 'normal') {
3026+
if (node.metadata.dynamic) {
30083027
// Handle dynamic references to what seems like static inline components
30093028
const component = serialize_inline_component(node, '$$component', context);
30103029
context.state.init.push(
30113030
b.stmt(
30123031
b.call(
30133032
'$.component',
3033+
context.state.node,
30143034
// TODO use untrack here to not update when binding changes?
30153035
// Would align with Svelte 4 behavior, but it's arguably nicer/expected to update this
30163036
b.thunk(
@@ -3022,6 +3042,7 @@ export const template_visitors = {
30223042
);
30233043
return;
30243044
}
3045+
30253046
const component = serialize_inline_component(node, node.name, context);
30263047
context.state.init.push(component);
30273048
},

packages/svelte/src/compiler/types/template.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ export interface DebugTag extends BaseNode {
152152
export interface RenderTag extends BaseNode {
153153
type: 'RenderTag';
154154
expression: SimpleCallExpression | (ChainExpression & { expression: SimpleCallExpression });
155+
metadata: {
156+
dynamic: boolean;
157+
};
155158
}
156159

157160
type Tag = ExpressionTag | HtmlTag | ConstTag | DebugTag | RenderTag;
@@ -271,6 +274,9 @@ interface BaseElement extends BaseNode {
271274

272275
export interface Component extends BaseElement {
273276
type: 'Component';
277+
metadata: {
278+
dynamic: boolean;
279+
};
274280
}
275281

276282
interface TitleElement extends BaseElement {

packages/svelte/src/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const TRANSITION_GLOBAL = 1 << 2;
1818

1919
export const TEMPLATE_FRAGMENT = 1;
2020
export const TEMPLATE_USE_IMPORT_NODE = 1 << 1;
21+
export const TEMPLATE_UNSET_START = 1 << 2;
2122

2223
export const HYDRATION_START = '[';
2324
export const HYDRATION_END = ']';

packages/svelte/src/internal/client/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const EFFECT_TRANSPARENT = 1 << 15;
1717
/** Svelte 4 legacy mode props need to be handled with deriveds and be recognized elsewhere, hence the dedicated flag */
1818
export const LEGACY_DERIVED_PROP = 1 << 16;
1919
export const INSPECT_EFFECT = 1 << 17;
20+
export const HEAD_EFFECT = 1 << 18;
2021

2122
export const STATE_SYMBOL = Symbol('$state');
2223
export const STATE_FROZEN_SYMBOL = Symbol('$state.frozen');

packages/svelte/src/internal/client/dev/hmr.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function hmr(source) {
1818
/** @type {import("#client").Effect} */
1919
let effect;
2020

21-
block(() => {
21+
block(anchor, 0, () => {
2222
const component = get(source);
2323

2424
if (effect) {

packages/svelte/src/internal/client/dom/blocks/await.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export function await_block(anchor, get_input, pending_fn, then_fn, catch_fn) {
105105
}
106106
}
107107

108-
var effect = block(() => {
108+
var effect = block(anchor, 0, () => {
109109
if (input === (input = get_input())) return;
110110

111111
if (is_promise(input)) {

0 commit comments

Comments
 (0)