From 116de3eb8e21de5bf50789f81e959c4e7a401eac Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 11 Jul 2024 18:24:42 +0100 Subject: [PATCH] feat: add createRawSnippet API --- .changeset/fresh-zoos-burn.md | 5 +++ packages/svelte/src/index-client.js | 2 ++ packages/svelte/src/index-server.js | 2 ++ .../src/internal/client/dom/blocks/snippet.js | 29 ++++++++++++++++- packages/svelte/src/internal/server/index.js | 18 ++++++++++- .../samples/snippet-raw-args/_config.js | 17 ++++++++++ .../samples/snippet-raw-args/main.svelte | 32 +++++++++++++++++++ .../samples/snippet-raw/_config.js | 8 +++++ .../samples/snippet-raw/main.svelte | 16 ++++++++++ packages/svelte/types/index.d.ts | 15 +++++++-- 10 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 .changeset/fresh-zoos-burn.md create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-raw-args/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-raw-args/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-raw/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-raw/main.svelte diff --git a/.changeset/fresh-zoos-burn.md b/.changeset/fresh-zoos-burn.md new file mode 100644 index 0000000000..6b68a02726 --- /dev/null +++ b/.changeset/fresh-zoos-burn.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: add createRawSnippet API diff --git a/packages/svelte/src/index-client.js b/packages/svelte/src/index-client.js index b4f5e9a12b..b6a53bb40a 100644 --- a/packages/svelte/src/index-client.js +++ b/packages/svelte/src/index-client.js @@ -190,3 +190,5 @@ export { tick, untrack } from './internal/client/runtime.js'; + +export { createRawSnippet } from './internal/client/dom/blocks/snippet.js'; diff --git a/packages/svelte/src/index-server.js b/packages/svelte/src/index-server.js index 5bcb13e5f2..6cea28c9f3 100644 --- a/packages/svelte/src/index-server.js +++ b/packages/svelte/src/index-server.js @@ -35,3 +35,5 @@ export function unmount() { export async function tick() {} export { getAllContexts, getContext, hasContext, setContext } from './internal/server/context.js'; + +export { createRawSnippet } from './internal/server/index.js'; diff --git a/packages/svelte/src/internal/client/dom/blocks/snippet.js b/packages/svelte/src/internal/client/dom/blocks/snippet.js index c0fc350ce4..31b02a153f 100644 --- a/packages/svelte/src/internal/client/dom/blocks/snippet.js +++ b/packages/svelte/src/internal/client/dom/blocks/snippet.js @@ -6,7 +6,8 @@ import { dev_current_component_function, set_dev_current_component_function } 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 @@ -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; +} diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 841794e544..65e55ffad0 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -13,7 +13,7 @@ import { escape_html } from '../../escaping.js'; import { DEV } from 'esm-env'; import { current_component, pop, push } from './context.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://infra.spec.whatwg.org/#noncharacter @@ -155,6 +155,22 @@ export function head(payload, fn) { 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 * @param {string} name diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-raw-args/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-raw-args/_config.js new file mode 100644 index 0000000000..a22776d7e7 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-raw-args/_config.js @@ -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: `
0
`, + + test({ assert, target }) { + const [b1] = target.querySelectorAll('button'); + + b1?.click(); + flushSync(); + assert.htmlEqual(target.innerHTML, `
1
`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-raw-args/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-raw-args/main.svelte new file mode 100644 index 0000000000..6568abd987 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-raw-args/main.svelte @@ -0,0 +1,32 @@ + + +
+ {@render snippet(count)} +
+ diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-raw/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-raw/_config.js new file mode 100644 index 0000000000..129a573402 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-raw/_config.js @@ -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: `

hello world

` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-raw/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-raw/main.svelte new file mode 100644 index 0000000000..6a1dd3915a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-raw/main.svelte @@ -0,0 +1,16 @@ + + +{@render hello()} diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 3991078153..4219e14d52 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -365,12 +365,20 @@ declare module 'svelte' { export function flushSync(fn?: (() => void) | undefined): void; /** Anything except a function */ type NotFunction = 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. * Transitions will play during the initial render unless the `intro` option is set to `false`. * * */ - export function mount, Exports extends Record>(component: ComponentType> | Component, options: {} extends Props ? { + function mount_1, Exports extends Record>(component: ComponentType> | Component, options: {} extends Props ? { target: Document | Element | ShadowRoot; anchor?: Node; 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 * * */ - export function hydrate, Exports extends Record>(component: ComponentType> | Component, options: {} extends Props ? { + function hydrate_1, Exports extends Record>(component: ComponentType> | Component, options: {} extends Props ? { target: Document | Element | ShadowRoot; props?: Props; events?: Record any>; @@ -450,8 +458,9 @@ declare module 'svelte' { * https://svelte.dev/docs/svelte#getallcontexts * */ export function getAllContexts = Map>(): T; + type TemplateNode = Text | Element | Comment; - export {}; + export { hydrate_1 as hydrate, mount_1 as mount }; } declare module 'svelte/action' {