Skip to content

Commit

Permalink
fix: set correct component context when rendering snippets (#11401)
Browse files Browse the repository at this point in the history
fixes #11399
  • Loading branch information
Rich-Harris authored May 2, 2024
1 parent f64d169 commit edefc84
Show file tree
Hide file tree
Showing 12 changed files with 88 additions and 23 deletions.
5 changes: 5 additions & 0 deletions .changeset/good-plums-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: set correct component context when rendering snippets
1 change: 1 addition & 0 deletions packages/svelte/src/compiler/phases/2-analyze/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ export function analyze_component(root, source, options) {
reactive_statements: new Map(),
binding_groups: new Map(),
slot_names: new Map(),
top_level_snippets: [],
css: {
ast: root.css,
hash: root.css
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ export function client_component(source, analysis, options) {
...store_setup,
...legacy_reactive_declarations,
...group_binding_declarations,
...analysis.top_level_snippets,
.../** @type {import('estree').Statement[]} */ (instance.body),
analysis.runes || !analysis.needs_context ? b.empty : b.stmt(b.call('$.init')),
.../** @type {import('estree').Statement[]} */ (template.body)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -835,10 +835,7 @@ function serialize_inline_component(node, component_name, context) {

if (slot_name === 'default') {
push_prop(
b.init(
'children',
context.state.options.dev ? b.call('$.add_snippet_symbol', slot_fn) : slot_fn
)
b.init('children', context.state.options.dev ? b.call('$.wrap_snippet', slot_fn) : slot_fn)
);
} else {
serialized_slots.push(b.init(slot_name, slot_fn));
Expand Down Expand Up @@ -2566,16 +2563,20 @@ export const template_visitors = {
.../** @type {import('estree').BlockStatement} */ (context.visit(node.body)).body
]);

const path = context.path;
// If we're top-level, then we can create a function for the snippet so that it can be referenced
// in the props declaration (default value pattern).
if (path.length === 1 && path[0].type === 'Fragment') {
context.state.init.push(b.function_declaration(node.expression, args, body));
} else {
context.state.init.push(b.const(node.expression, b.arrow(args, body)));
}
/** @type {import('estree').Expression} */
let snippet = b.arrow(args, body);

if (context.state.options.dev) {
context.state.init.push(b.stmt(b.call('$.add_snippet_symbol', node.expression)));
snippet = b.call('$.wrap_snippet', snippet);
}

const declaration = b.var(node.expression, snippet);

// Top-level snippets are hoisted so they can be referenced in the `<script>`
if (context.path.length === 1 && context.path[0].type === 'Fragment') {
context.state.analysis.top_level_snippets.push(declaration);
} else {
context.state.init.push(declaration);
}
},
FunctionExpression: function_visitor,
Expand Down
6 changes: 3 additions & 3 deletions packages/svelte/src/compiler/phases/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ import type {
SlotElement,
SvelteElement,
SvelteNode,
SvelteOptions,
Warning
SvelteOptions
} from '#compiler';
import type { Identifier, LabeledStatement, Program } from 'estree';
import type { Identifier, LabeledStatement, Program, Statement, VariableDeclaration } from 'estree';
import type { Scope, ScopeRoot } from './scope.js';

export interface Js {
Expand Down Expand Up @@ -68,6 +67,7 @@ export interface ComponentAnalysis extends Analysis {
/** If `true`, should append styles through JavaScript */
inject_styles: boolean;
reactive_statements: Map<LabeledStatement, ReactiveStatement>;
top_level_snippets: VariableDeclaration[];
/** Identifiers that make up the `bind:group` expression -> internal group binding name */
binding_groups: Map<[key: string, bindings: Array<Binding | null>], Identifier>;
slot_names: Map<string, SlotElement>;
Expand Down
25 changes: 25 additions & 0 deletions packages/svelte/src/internal/client/dom/blocks/snippet.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { add_snippet_symbol } from '../../../shared/validate.js';
import { EFFECT_TRANSPARENT } from '../../constants.js';
import { branch, block, destroy_effect } from '../../reactivity/effects.js';
import { current_component_context, set_current_component_context } from '../../runtime.js';

/**
* @template {(node: import('#client').TemplateNode, ...args: any[]) => import('#client').Dom} SnippetFn
Expand Down Expand Up @@ -28,3 +30,26 @@ export function snippet(get_snippet, node, ...args) {
}
}, EFFECT_TRANSPARENT);
}

/**
* In development, wrap the snippet function so that it passes validation, and so that the
* correct component context is set for ownership checks
* @param {(node: import('#client').TemplateNode, ...args: any[]) => import('#client').Dom} fn
* @returns
*/
export function wrap_snippet(fn) {
let component = current_component_context;

return add_snippet_symbol(
(/** @type {import('#client').TemplateNode} */ node, /** @type {any[]} */ ...args) => {
var previous_component_context = current_component_context;
set_current_component_context(component);

try {
return fn(node, ...args);
} finally {
set_current_component_context(previous_component_context);
}
}
);
}
2 changes: 1 addition & 1 deletion packages/svelte/src/internal/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export { key_block as key } from './dom/blocks/key.js';
export { css_props } from './dom/blocks/css-props.js';
export { index, each } from './dom/blocks/each.js';
export { html } from './dom/blocks/html.js';
export { snippet } from './dom/blocks/snippet.js';
export { snippet, wrap_snippet } from './dom/blocks/snippet.js';
export { component } from './dom/blocks/svelte-component.js';
export { element } from './dom/blocks/svelte-element.js';
export { head } from './dom/blocks/svelte-head.js';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script>
let { object = $bindable() } = $props();
</script>

<button onclick={() => object.count += 1}>
clicks: {object.count}
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script>
let { children } = $props();
</script>

{@render children?.()}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { test } from '../../test';

export default test({
html: `<button>clicks: 0</button>`,

compileOptions: {
dev: true
},

warnings: []
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
import Outer from './Outer.svelte';
import Inner from './Inner.svelte';
let object = $state({ count: 0 });
</script>

<Outer>
<Inner bind:object />
</Outer>
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,15 @@ var root_1 = $.template(`Something`, 1);
var root = $.template(`<!> `, 1);

export default function Bind_component_snippet($$anchor) {
let value = $.source('');
const _snippet = snippet;
var fragment_1 = root();

function snippet($$anchor) {
var snippet = ($$anchor) => {
var fragment = root_1();

$.append($$anchor, fragment);
}
};

let value = $.source('');
const _snippet = snippet;
var fragment_1 = root();
var node = $.first_child(fragment_1);

TextInput(node, {
Expand Down

0 comments on commit edefc84

Please sign in to comment.