Skip to content

Commit dfa5617

Browse files
committed
feat: add createRawSnippet API
1 parent d9569d0 commit dfa5617

File tree

10 files changed

+139
-5
lines changed

10 files changed

+139
-5
lines changed

.changeset/fresh-zoos-burn.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: add createRawSnippet API

packages/svelte/src/index-client.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,5 @@ export {
190190
tick,
191191
untrack
192192
} from './internal/client/runtime.js';
193+
194+
export { createRawSnippet } from './internal/client/dom/blocks/snippet.js';

packages/svelte/src/index-server.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@ export function unmount() {
3535
export async function tick() {}
3636

3737
export { getAllContexts, getContext, hasContext, setContext } from './internal/server/context.js';
38+
39+
export { createRawSnippet } from './internal/server/index.js';

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

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
dev_current_component_function,
77
set_dev_current_component_function
88
} from '../../runtime.js';
9-
import { hydrate_node, hydrating } from '../hydration.js';
9+
import { hydrate_next, hydrate_node, hydrating } from '../hydration.js';
10+
import { assign_nodes } from '../template.js';
1011

1112
/**
1213
* @template {(node: TemplateNode, ...args: any[]) => void} SnippetFn
@@ -60,3 +61,29 @@ export function wrap_snippet(component, fn) {
6061
}
6162
});
6263
}
64+
65+
/**
66+
* Create a snippet imperatively using mount, hyrdate and render functions.
67+
* @param {{
68+
* mount: (...params: any[]) => Element,
69+
* hydrate?: (element: Element, ...params: any[]) => void,
70+
* render: (...params: any[]) => string
71+
* }} options
72+
*/
73+
export function createRawSnippet({ mount, hydrate }) {
74+
var snippet_fn = (/** @type {TemplateNode} */ anchor, /** @type {any[]} */ ...params) => {
75+
var element;
76+
if (hydrating) {
77+
element = hydrate_node;
78+
hydrate_next();
79+
if (hydrate !== undefined) hydrate(/** @type {Element} */ (element), ...params);
80+
} else {
81+
element = mount(...params);
82+
anchor.before(element);
83+
}
84+
assign_nodes(element, element);
85+
};
86+
add_snippet_symbol(snippet_fn);
87+
88+
return snippet_fn;
89+
}

packages/svelte/src/internal/server/index.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { escape_html } from '../../escaping.js';
1313
import { DEV } from 'esm-env';
1414
import { current_component, pop, push } from './context.js';
1515
import { EMPTY_COMMENT, BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js';
16-
import { validate_store } from '../shared/validate.js';
16+
import { add_snippet_symbol, validate_store } from '../shared/validate.js';
1717

1818
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
1919
// https://infra.spec.whatwg.org/#noncharacter
@@ -155,6 +155,22 @@ export function head(payload, fn) {
155155
head_payload.out += BLOCK_CLOSE;
156156
}
157157

158+
/**
159+
* Create a snippet imperatively using mount, hyrdate and render functions.
160+
* @param {{
161+
* mount: (...params: any[]) => Element,
162+
* hydrate?: (element: Element, ...params: any[]) => void,
163+
* render: (...params: any[]) => string
164+
* }} options
165+
*/
166+
export function createRawSnippet({ render }) {
167+
const snippet_fn = (/** @type {Payload} */ payload, /** @type {any[]} */ ...args) => {
168+
payload.out += render(...args);
169+
};
170+
add_snippet_symbol(snippet_fn);
171+
return snippet_fn;
172+
}
173+
158174
/**
159175
* @template V
160176
* @param {string} name
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
compileOptions: {
6+
dev: true // Render in dev mode to check that the validation error is not thrown
7+
},
8+
html: `<div><div>0</div></div><button>+</button>`,
9+
10+
test({ assert, target }) {
11+
const [b1] = target.querySelectorAll('button');
12+
13+
b1?.click();
14+
flushSync();
15+
assert.htmlEqual(target.innerHTML, `<div><div>1</div></div><button>+</button>`);
16+
}
17+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<script>
2+
import { createRawSnippet } from 'svelte';
3+
4+
let count = $state(0);
5+
6+
const snippet = createRawSnippet({
7+
mount(count) {
8+
const div = document.createElement('div');
9+
10+
$effect(() => {
11+
div.textContent = count();
12+
});
13+
14+
return div;
15+
},
16+
hydrate(element, count) {
17+
18+
$effect(() => {
19+
element.textContent = count();
20+
});
21+
22+
},
23+
render(count) {
24+
return `<div>${count}</div>`;
25+
}
26+
});
27+
</script>
28+
29+
<div>
30+
{@render snippet(count)}
31+
</div>
32+
<button onclick={() => count++}>+</button>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
compileOptions: {
5+
dev: true // Render in dev mode to check that the validation error is not thrown
6+
},
7+
html: `<p>hello world</p>`
8+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script>
2+
import { createRawSnippet } from 'svelte';
3+
4+
const hello = createRawSnippet({
5+
mount() {
6+
const p = document.createElement('p')
7+
p.textContent = 'hello world';
8+
return p;
9+
},
10+
render() {
11+
return '<p>hello world</p>';
12+
}
13+
});
14+
</script>
15+
16+
{@render hello()}

packages/svelte/types/index.d.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -365,12 +365,20 @@ declare module 'svelte' {
365365
export function flushSync(fn?: (() => void) | undefined): void;
366366
/** Anything except a function */
367367
type NotFunction<T> = T extends Function ? never : T;
368+
/**
369+
* Create a snippet imperatively using mount, hyrdate and render functions.
370+
* */
371+
export function createRawSnippet({ mount, hydrate }: {
372+
mount: (...params: any[]) => Element;
373+
hydrate?: (element: Element, ...params: any[]) => void;
374+
render: (...params: any[]) => string;
375+
}): (anchor: TemplateNode, ...params: any[]) => void;
368376
/**
369377
* Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component.
370378
* Transitions will play during the initial render unless the `intro` option is set to `false`.
371379
*
372380
* */
373-
export function mount<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: {} extends Props ? {
381+
function mount_1<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: {} extends Props ? {
374382
target: Document | Element | ShadowRoot;
375383
anchor?: Node;
376384
props?: Props;
@@ -389,7 +397,7 @@ declare module 'svelte' {
389397
* Hydrates a component on the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component
390398
*
391399
* */
392-
export function hydrate<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: {} extends Props ? {
400+
function hydrate_1<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: {} extends Props ? {
393401
target: Document | Element | ShadowRoot;
394402
props?: Props;
395403
events?: Record<string, (e: any) => any>;
@@ -450,8 +458,9 @@ declare module 'svelte' {
450458
* https://svelte.dev/docs/svelte#getallcontexts
451459
* */
452460
export function getAllContexts<T extends Map<any, any> = Map<any, any>>(): T;
461+
type TemplateNode = Text | Element | Comment;
453462

454-
export {};
463+
export { hydrate_1 as hydrate, mount_1 as mount };
455464
}
456465

457466
declare module 'svelte/action' {

0 commit comments

Comments
 (0)