feat: add createRawSnippet API

pull/12409/head
Dominic Gannaway 2 years ago
parent d9569d052e
commit dfa5617419

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: add createRawSnippet API

@ -190,3 +190,5 @@ export {
tick, tick,
untrack untrack
} from './internal/client/runtime.js'; } from './internal/client/runtime.js';
export { createRawSnippet } from './internal/client/dom/blocks/snippet.js';

@ -35,3 +35,5 @@ export function unmount() {
export async function tick() {} export async function tick() {}
export { getAllContexts, getContext, hasContext, setContext } from './internal/server/context.js'; export { getAllContexts, getContext, hasContext, setContext } from './internal/server/context.js';
export { createRawSnippet } from './internal/server/index.js';

@ -6,7 +6,8 @@ import {
dev_current_component_function, dev_current_component_function,
set_dev_current_component_function set_dev_current_component_function
} from '../../runtime.js'; } from '../../runtime.js';
import { hydrate_node, hydrating } from '../hydration.js'; import { hydrate_next, hydrate_node, hydrating } from '../hydration.js';
import { assign_nodes } from '../template.js';
/** /**
* @template {(node: TemplateNode, ...args: any[]) => void} SnippetFn * @template {(node: TemplateNode, ...args: any[]) => void} SnippetFn
@ -60,3 +61,29 @@ export function wrap_snippet(component, fn) {
} }
}); });
} }
/**
* Create a snippet imperatively using mount, hyrdate and render functions.
* @param {{
* mount: (...params: any[]) => Element,
* hydrate?: (element: Element, ...params: any[]) => void,
* render: (...params: any[]) => string
* }} options
*/
export function createRawSnippet({ mount, hydrate }) {
var snippet_fn = (/** @type {TemplateNode} */ anchor, /** @type {any[]} */ ...params) => {
var element;
if (hydrating) {
element = hydrate_node;
hydrate_next();
if (hydrate !== undefined) hydrate(/** @type {Element} */ (element), ...params);
} else {
element = mount(...params);
anchor.before(element);
}
assign_nodes(element, element);
};
add_snippet_symbol(snippet_fn);
return snippet_fn;
}

@ -13,7 +13,7 @@ import { escape_html } from '../../escaping.js';
import { DEV } from 'esm-env'; import { DEV } from 'esm-env';
import { current_component, pop, push } from './context.js'; import { current_component, pop, push } from './context.js';
import { EMPTY_COMMENT, BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js'; import { EMPTY_COMMENT, BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js';
import { validate_store } from '../shared/validate.js'; import { add_snippet_symbol, validate_store } from '../shared/validate.js';
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
// https://infra.spec.whatwg.org/#noncharacter // https://infra.spec.whatwg.org/#noncharacter
@ -155,6 +155,22 @@ export function head(payload, fn) {
head_payload.out += BLOCK_CLOSE; head_payload.out += BLOCK_CLOSE;
} }
/**
* Create a snippet imperatively using mount, hyrdate and render functions.
* @param {{
* mount: (...params: any[]) => Element,
* hydrate?: (element: Element, ...params: any[]) => void,
* render: (...params: any[]) => string
* }} options
*/
export function createRawSnippet({ render }) {
const snippet_fn = (/** @type {Payload} */ payload, /** @type {any[]} */ ...args) => {
payload.out += render(...args);
};
add_snippet_symbol(snippet_fn);
return snippet_fn;
}
/** /**
* @template V * @template V
* @param {string} name * @param {string} name

@ -0,0 +1,17 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
compileOptions: {
dev: true // Render in dev mode to check that the validation error is not thrown
},
html: `<div><div>0</div></div><button>+</button>`,
test({ assert, target }) {
const [b1] = target.querySelectorAll('button');
b1?.click();
flushSync();
assert.htmlEqual(target.innerHTML, `<div><div>1</div></div><button>+</button>`);
}
});

@ -0,0 +1,32 @@
<script>
import { createRawSnippet } from 'svelte';
let count = $state(0);
const snippet = createRawSnippet({
mount(count) {
const div = document.createElement('div');
$effect(() => {
div.textContent = count();
});
return div;
},
hydrate(element, count) {
$effect(() => {
element.textContent = count();
});
},
render(count) {
return `<div>${count}</div>`;
}
});
</script>
<div>
{@render snippet(count)}
</div>
<button onclick={() => count++}>+</button>

@ -0,0 +1,8 @@
import { test } from '../../test';
export default test({
compileOptions: {
dev: true // Render in dev mode to check that the validation error is not thrown
},
html: `<p>hello world</p>`
});

@ -0,0 +1,16 @@
<script>
import { createRawSnippet } from 'svelte';
const hello = createRawSnippet({
mount() {
const p = document.createElement('p')
p.textContent = 'hello world';
return p;
},
render() {
return '<p>hello world</p>';
}
});
</script>
{@render hello()}

@ -365,12 +365,20 @@ declare module 'svelte' {
export function flushSync(fn?: (() => void) | undefined): void; export function flushSync(fn?: (() => void) | undefined): void;
/** Anything except a function */ /** Anything except a function */
type NotFunction<T> = T extends Function ? never : T; type NotFunction<T> = T extends Function ? never : T;
/**
* Create a snippet imperatively using mount, hyrdate and render functions.
* */
export function createRawSnippet({ mount, hydrate }: {
mount: (...params: any[]) => Element;
hydrate?: (element: Element, ...params: any[]) => void;
render: (...params: any[]) => string;
}): (anchor: TemplateNode, ...params: any[]) => void;
/** /**
* Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component. * Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component.
* Transitions will play during the initial render unless the `intro` option is set to `false`. * Transitions will play during the initial render unless the `intro` option is set to `false`.
* *
* */ * */
export function mount<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: {} extends Props ? { function mount_1<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: {} extends Props ? {
target: Document | Element | ShadowRoot; target: Document | Element | ShadowRoot;
anchor?: Node; anchor?: Node;
props?: Props; props?: Props;
@ -389,7 +397,7 @@ declare module 'svelte' {
* Hydrates a component on the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component * Hydrates a component on the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component
* *
* */ * */
export function hydrate<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: {} extends Props ? { function hydrate_1<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: {} extends Props ? {
target: Document | Element | ShadowRoot; target: Document | Element | ShadowRoot;
props?: Props; props?: Props;
events?: Record<string, (e: any) => any>; events?: Record<string, (e: any) => any>;
@ -450,8 +458,9 @@ declare module 'svelte' {
* https://svelte.dev/docs/svelte#getallcontexts * https://svelte.dev/docs/svelte#getallcontexts
* */ * */
export function getAllContexts<T extends Map<any, any> = Map<any, any>>(): T; export function getAllContexts<T extends Map<any, any> = Map<any, any>>(): T;
type TemplateNode = Text | Element | Comment;
export {}; export { hydrate_1 as hydrate, mount_1 as mount };
} }
declare module 'svelte/action' { declare module 'svelte/action' {

Loading…
Cancel
Save