From bf9e411fcd3400d7df871f0f4d564d6175f8c17f Mon Sep 17 00:00:00 2001 From: Hugos68 Date: Thu, 27 Feb 2025 17:57:28 +0100 Subject: [PATCH 01/20] Add `prefix` option to `render` --- packages/svelte/src/internal/server/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 160a1faa653e..920b8323a029 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -86,9 +86,9 @@ export function element(payload, tag, attributes_fn = noop, children_fn = noop) */ export let on_destroy = []; -function props_id_generator() { +function props_id_generator(prefix = '') { let uid = 1; - return () => 's' + uid++; + return () => `s${prefix ? `-${prefix}-` : ''}${uid++}`; } /** @@ -96,11 +96,11 @@ function props_id_generator() { * Takes a component and returns an object with `body` and `head` properties on it, which you can use to populate the HTML when server-rendering your app. * @template {Record} Props * @param {import('svelte').Component | ComponentType>} component - * @param {{ props?: Omit; context?: Map }} [options] + * @param {{ props?: Omit; context?: Map; prefix?: string }} [options] * @returns {RenderOutput} */ export function render(component, options = {}) { - const uid = props_id_generator(); + const uid = props_id_generator(options.prefix); /** @type {Payload} */ const payload = { out: '', From f2ab74a3e516b1f4ee9b6058b641a3087746f8a3 Mon Sep 17 00:00:00 2001 From: Hugos68 Date: Thu, 27 Feb 2025 18:00:37 +0100 Subject: [PATCH 02/20] rename to `uidPrefix` --- .changeset/wise-grapes-enjoy.md | 5 +++++ packages/svelte/src/internal/server/index.js | 11 ++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 .changeset/wise-grapes-enjoy.md diff --git a/.changeset/wise-grapes-enjoy.md b/.changeset/wise-grapes-enjoy.md new file mode 100644 index 000000000000..f18496e64cc7 --- /dev/null +++ b/.changeset/wise-grapes-enjoy.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: Add `uidPrefix` option in `render` function diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 920b8323a029..fa0dc810ca73 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -86,7 +86,12 @@ export function element(payload, tag, attributes_fn = noop, children_fn = noop) */ export let on_destroy = []; -function props_id_generator(prefix = '') { +/** + * Creates an ID generator + * @param {string | undefined} prefix + * @returns {() => string} + */ +function props_id_generator(prefix) { let uid = 1; return () => `s${prefix ? `-${prefix}-` : ''}${uid++}`; } @@ -96,11 +101,11 @@ function props_id_generator(prefix = '') { * Takes a component and returns an object with `body` and `head` properties on it, which you can use to populate the HTML when server-rendering your app. * @template {Record} Props * @param {import('svelte').Component | ComponentType>} component - * @param {{ props?: Omit; context?: Map; prefix?: string }} [options] + * @param {{ props?: Omit; context?: Map; uidPrefix?: string }} [options] * @returns {RenderOutput} */ export function render(component, options = {}) { - const uid = props_id_generator(options.prefix); + const uid = props_id_generator(options.uidPrefix); /** @type {Payload} */ const payload = { out: '', From 7d988c8cfe2b8bb5a6e3c540990565e385184c41 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Fri, 28 Feb 2025 09:58:18 +0100 Subject: [PATCH 03/20] chore: add `uid_prefix` to config for test suites --- packages/svelte/src/server/index.d.ts | 12 ++++++++++-- packages/svelte/tests/hydration/test.ts | 4 +++- packages/svelte/tests/runtime-browser/assert.js | 1 + packages/svelte/tests/runtime-browser/driver-ssr.js | 2 +- packages/svelte/tests/runtime-browser/test-ssr.ts | 2 +- packages/svelte/tests/server-side-rendering/test.ts | 3 ++- packages/svelte/types/index.d.ts | 12 ++++++++++-- 7 files changed, 28 insertions(+), 8 deletions(-) diff --git a/packages/svelte/src/server/index.d.ts b/packages/svelte/src/server/index.d.ts index b65ce5bdaa67..75a64b182813 100644 --- a/packages/svelte/src/server/index.d.ts +++ b/packages/svelte/src/server/index.d.ts @@ -12,10 +12,18 @@ export function render< ...args: {} extends Props ? [ component: Comp extends SvelteComponent ? ComponentType : Comp, - options?: { props?: Omit; context?: Map } + options?: { + props?: Omit; + context?: Map; + uidPrefix?: string; + } ] : [ component: Comp extends SvelteComponent ? ComponentType : Comp, - options: { props: Omit; context?: Map } + options: { + props: Omit; + context?: Map; + uidPrefix?: string; + } ] ): RenderOutput; diff --git a/packages/svelte/tests/hydration/test.ts b/packages/svelte/tests/hydration/test.ts index 3bf2dd286cd0..1b3c548824ca 100644 --- a/packages/svelte/tests/hydration/test.ts +++ b/packages/svelte/tests/hydration/test.ts @@ -13,6 +13,7 @@ import { flushSync } from 'svelte'; interface HydrationTest extends BaseTest { load_compiled?: boolean; server_props?: Record; + uid_prefix?: string; props?: Record; compileOptions?: Partial; /** @@ -50,7 +51,8 @@ const { test, run } = suite(async (config, cwd) => { const head = window.document.head; const rendered = render((await import(`${cwd}/_output/server/main.svelte.js`)).default, { - props: config.server_props ?? config.props ?? {} + props: config.server_props ?? config.props ?? {}, + uidPrefix: config?.uid_prefix }); const override = read(`${cwd}/_override.html`); diff --git a/packages/svelte/tests/runtime-browser/assert.js b/packages/svelte/tests/runtime-browser/assert.js index 249d19f80946..ed79dd43d79e 100644 --- a/packages/svelte/tests/runtime-browser/assert.js +++ b/packages/svelte/tests/runtime-browser/assert.js @@ -119,6 +119,7 @@ function normalize_children(node) { * skip_mode?: Array<'server' | 'client' | 'hydrate'>; * html?: string; * ssrHtml?: string; + * uid_prefix?: string; * props?: Props; * compileOptions?: Partial; * test?: (args: { diff --git a/packages/svelte/tests/runtime-browser/driver-ssr.js b/packages/svelte/tests/runtime-browser/driver-ssr.js index f5f15b64934f..832aa64aa5c0 100644 --- a/packages/svelte/tests/runtime-browser/driver-ssr.js +++ b/packages/svelte/tests/runtime-browser/driver-ssr.js @@ -6,5 +6,5 @@ import config from '__CONFIG__'; import { render } from 'svelte/server'; export default function () { - return render(SvelteComponent, { props: config.props || {} }); + return render(SvelteComponent, { props: config.props || {}, prefixUid: config.prefixUid }); } diff --git a/packages/svelte/tests/runtime-browser/test-ssr.ts b/packages/svelte/tests/runtime-browser/test-ssr.ts index 2ff1659f802a..92f6fd04e160 100644 --- a/packages/svelte/tests/runtime-browser/test-ssr.ts +++ b/packages/svelte/tests/runtime-browser/test-ssr.ts @@ -20,7 +20,7 @@ export async function run_ssr_test( await compile_directory(test_dir, 'server', config.compileOptions); const Component = (await import(`${test_dir}/_output/server/main.svelte.js`)).default; - const { body } = render(Component, { props: config.props || {} }); + const { body } = render(Component, { props: config.props || {}, uidPrefix: config.uid_prefix }); fs.writeFileSync(`${test_dir}/_output/rendered.html`, body); diff --git a/packages/svelte/tests/server-side-rendering/test.ts b/packages/svelte/tests/server-side-rendering/test.ts index f76c5b539f24..5f97ac09c116 100644 --- a/packages/svelte/tests/server-side-rendering/test.ts +++ b/packages/svelte/tests/server-side-rendering/test.ts @@ -15,6 +15,7 @@ import type { CompileOptions } from '#compiler'; interface SSRTest extends BaseTest { compileOptions?: Partial; props?: Record; + uid_prefix?: string; withoutNormalizeHtml?: boolean; errors?: string[]; } @@ -33,7 +34,7 @@ const { test, run } = suite(async (config, test_dir) => { const Component = (await import(`${test_dir}/_output/server/main.svelte.js`)).default; const expected_html = try_read_file(`${test_dir}/_expected.html`); - const rendered = render(Component, { props: config.props || {} }); + const rendered = render(Component, { props: config.props || {}, uidPrefix: config.uid_prefix }); const { body, head } = rendered; fs.writeFileSync(`${test_dir}/_output/rendered.html`, body); diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 4c47661af897..ebc58a040643 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -2080,11 +2080,19 @@ declare module 'svelte/server' { ...args: {} extends Props ? [ component: Comp extends SvelteComponent ? ComponentType : Comp, - options?: { props?: Omit; context?: Map } + options?: { + props?: Omit; + context?: Map; + uidPrefix?: string; + } ] : [ component: Comp extends SvelteComponent ? ComponentType : Comp, - options: { props: Omit; context?: Map } + options: { + props: Omit; + context?: Map; + uidPrefix?: string; + } ] ): RenderOutput; interface RenderOutput { From d079e834b9dc34ead8181e88569ed653228e162c Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Fri, 28 Feb 2025 10:40:07 +0100 Subject: [PATCH 04/20] fix: lint --- packages/svelte/src/internal/server/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index fa0dc810ca73..e1c991c88638 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -88,7 +88,7 @@ export let on_destroy = []; /** * Creates an ID generator - * @param {string | undefined} prefix + * @param {string | undefined} prefix * @returns {() => string} */ function props_id_generator(prefix) { From aad86ebf60366c678efbe7176380d9302a477521 Mon Sep 17 00:00:00 2001 From: Hugos68 Date: Fri, 28 Feb 2025 20:01:35 +0100 Subject: [PATCH 05/20] Add (currently failing) test --- .../samples/props-id-prefix/Child.svelte | 5 ++ .../samples/props-id-prefix/_config.js | 60 +++++++++++++++++++ .../samples/props-id-prefix/main.svelte | 19 ++++++ 3 files changed, 84 insertions(+) create mode 100644 packages/svelte/tests/runtime-runes/samples/props-id-prefix/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/props-id-prefix/main.svelte diff --git a/packages/svelte/tests/runtime-runes/samples/props-id-prefix/Child.svelte b/packages/svelte/tests/runtime-runes/samples/props-id-prefix/Child.svelte new file mode 100644 index 000000000000..ad8bbd6f01ff --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-id-prefix/Child.svelte @@ -0,0 +1,5 @@ + + +

{id}

diff --git a/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js b/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js new file mode 100644 index 000000000000..2861c5c76fb8 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js @@ -0,0 +1,60 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + uid_prefix: 'myPrefix', + test({ assert, target, variant }) { + if (variant === 'dom') { + assert.htmlEqual( + target.innerHTML, + ` + +

c-myPrefix-1

+

c-myPrefix-2

+

c-myPrefix-3

+

c-myPrefix-4

+ ` + ); + } else { + assert.htmlEqual( + target.innerHTML, + ` + +

s-myPrefix-1

+

s-myPrefix-2

+

s-myPrefix-3

+

s-myPrefix-4

+ ` + ); + } + + let button = target.querySelector('button'); + flushSync(() => button?.click()); + + if (variant === 'dom') { + assert.htmlEqual( + target.innerHTML, + ` + +

c-myPrefix-1

+

c-myPrefix-2

+

c-myPrefix-3

+

c-myPrefix-4

+

c-myPrefix-5

+ ` + ); + } else { + assert.htmlEqual( + target.innerHTML, + ` + +

s-myPrefix-1

+

s-myPrefix-2

+

s-myPrefix-3

+

s-myPrefix-4

+

c-myPrefix-1

+ ` + ); + } + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/props-id-prefix/main.svelte b/packages/svelte/tests/runtime-runes/samples/props-id-prefix/main.svelte new file mode 100644 index 000000000000..646bb2ebdefe --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-id-prefix/main.svelte @@ -0,0 +1,19 @@ + + + + +

{id}

+ + + + + +{#if show} + +{/if} From 689ba17040fb0a19fd9dab869be8b6fede96cd26 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Fri, 28 Feb 2025 20:28:56 +0100 Subject: [PATCH 06/20] chore: `uid_prefix` in `runtime-runes` and `runtime-legacy` --- packages/svelte/tests/runtime-legacy/shared.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/svelte/tests/runtime-legacy/shared.ts b/packages/svelte/tests/runtime-legacy/shared.ts index 3ffb3092a46a..7428efc57639 100644 --- a/packages/svelte/tests/runtime-legacy/shared.ts +++ b/packages/svelte/tests/runtime-legacy/shared.ts @@ -37,6 +37,7 @@ export interface RuntimeTest = Record; props?: Props; server_props?: Props; + uid_prefix?: string; before_test?: () => void; after_test?: () => void; test?: (args: { @@ -285,7 +286,8 @@ async function run_test_variant( // ssr into target const SsrSvelteComponent = (await import(`${cwd}/_output/server/main.svelte.js`)).default; const { html, head } = render(SsrSvelteComponent, { - props: config.server_props ?? config.props ?? {} + props: config.server_props ?? config.props ?? {}, + uidPrefix: config.uid_prefix }); fs.writeFileSync(`${cwd}/_output/rendered.html`, html); From bf47d64ba9da77997f561f186fa8b1f1db7a7b12 Mon Sep 17 00:00:00 2001 From: Hugos68 Date: Fri, 28 Feb 2025 21:02:44 +0100 Subject: [PATCH 07/20] Fix prefix test --- .../samples/props-id-prefix/_config.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js b/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js index 2861c5c76fb8..9fc01d31df90 100644 --- a/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js @@ -9,10 +9,10 @@ export default test({ target.innerHTML, ` -

c-myPrefix-1

-

c-myPrefix-2

-

c-myPrefix-3

-

c-myPrefix-4

+

c1

+

c2

+

c3

+

c4

` ); } else { @@ -36,11 +36,11 @@ export default test({ target.innerHTML, ` -

c-myPrefix-1

-

c-myPrefix-2

-

c-myPrefix-3

-

c-myPrefix-4

-

c-myPrefix-5

+

c1

+

c2

+

c3

+

c4

+

c5

` ); } else { @@ -52,7 +52,7 @@ export default test({

s-myPrefix-2

s-myPrefix-3

s-myPrefix-4

-

c-myPrefix-1

+

c1

` ); } From d1906a5cb6b78a2b24f5a0696e229d6b93ab321f Mon Sep 17 00:00:00 2001 From: Hugos68 Date: Fri, 28 Feb 2025 23:00:39 +0100 Subject: [PATCH 08/20] Added `uidPrefix` to `hydrate` and `mount` --- .../2-analyze/visitors/CallExpression.js | 1 + .../3-transform/client/transform-client.js | 11 +++++----- packages/svelte/src/index.d.ts | 4 ++++ .../svelte/src/internal/client/context.js | 3 ++- .../src/internal/client/dom/template.js | 5 ++++- packages/svelte/src/internal/client/render.js | 16 ++++++++++++--- .../svelte/src/internal/client/types.d.ts | 2 ++ .../svelte/tests/runtime-legacy/shared.ts | 3 ++- .../samples/props-id-prefix/_config.js | 20 +++++++++---------- packages/svelte/types/index.d.ts | 6 ++++++ 10 files changed, 50 insertions(+), 21 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js index 4d09d9293fb2..5a85502c8f31 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js @@ -96,6 +96,7 @@ export function CallExpression(node, context) { } context.state.analysis.props_id = parent.id; + context.state.analysis.needs_context = true; break; } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index cf5ba285cbf3..e84c1574fa93 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -398,7 +398,13 @@ export function client_component(analysis, options) { // we want the cleanup function for the stores to run as the very last thing // so that it can effectively clean up the store subscription even after the user effects runs + // if we have $props.id `should_inject_context` will always be true if (should_inject_context) { + // we need to put the `$props.id` after the `$.push` because the `component_context` will be properly initialized + if (analysis.props_id) { + // need to be placed on first line of the component for hydration + component_block.body.unshift(b.const(analysis.props_id, b.call('$.props_id'))); + } component_block.body.unshift(b.stmt(b.call('$.push', ...push_args))); let to_push; @@ -562,11 +568,6 @@ export function client_component(analysis, options) { component_block.body.unshift(b.stmt(b.call('$.check_target', b.id('new.target')))); } - if (analysis.props_id) { - // need to be placed on first line of the component for hydration - component_block.body.unshift(b.const(analysis.props_id, b.call('$.props_id'))); - } - if (state.events.size > 0) { body.push( b.stmt(b.call('$.delegate', b.array(Array.from(state.events).map((name) => b.literal(name))))) diff --git a/packages/svelte/src/index.d.ts b/packages/svelte/src/index.d.ts index 554510542e2e..8609124675da 100644 --- a/packages/svelte/src/index.d.ts +++ b/packages/svelte/src/index.d.ts @@ -337,6 +337,10 @@ export type MountOptions = Record * @default true */ intro?: boolean; + /** + * Provide a prefix for the generated ID from `$props.id` + */ + uidPrefix?: string; } & ({} extends Props ? { /** diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js index bd94d5ad8a19..64244b6413a0 100644 --- a/packages/svelte/src/internal/client/context.js +++ b/packages/svelte/src/internal/client/context.js @@ -119,7 +119,8 @@ export function push(props, runes = false, fn) { m: false, s: props, x: null, - l: null + l: null, + uid: component_context?.uid }; if (legacy_mode_flag && !runes) { diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index 575bf55cf62b..cf0e7e9288f6 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -4,6 +4,7 @@ import { create_text, get_first_child, is_firefox } from './operations.js'; import { create_fragment_from_html } from './reconciler.js'; import { active_effect } from '../runtime.js'; import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../constants.js'; +import { component_context } from '../context.js'; /** * @param {TemplateNode} start @@ -270,6 +271,8 @@ export function props_id() { hydrate_next(); return id; } - + if (component_context?.uid) { + return `c-${component_context.uid}-${uid++}`; + } return 'c' + uid++; } diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 3256fe827410..5d5c689cb747 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -86,6 +86,7 @@ export function mount(component, options) { * context?: Map; * intro?: boolean; * recover?: boolean; + * uidPrefix?: string; * } : { * target: Document | Element | ShadowRoot; * props: Props; @@ -93,6 +94,7 @@ export function mount(component, options) { * context?: Map; * intro?: boolean; * recover?: boolean; + * uidPrefix?: string; * }} options * @returns {Exports} */ @@ -165,7 +167,10 @@ const document_listeners = new Map(); * @param {MountOptions} options * @returns {Exports} */ -function _mount(Component, { target, anchor, props = {}, events, context, intro = true }) { +function _mount( + Component, + { target, anchor, props = {}, events, context, intro = true, uidPrefix } +) { init_operations(); var registered_events = new Set(); @@ -209,10 +214,15 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro var anchor_node = anchor ?? target.appendChild(create_text()); branch(() => { - if (context) { + if (context || uidPrefix != null) { push({}); var ctx = /** @type {ComponentContext} */ (component_context); - ctx.c = context; + if (context) { + ctx.c = context; + } + if (uidPrefix != null) { + ctx.uid = uidPrefix; + } } if (events) { diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index 7208ed77837e..500cf89623a5 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -10,6 +10,8 @@ export type EventCallbackMap = Record; // when the JS VM JITs the code. export type ComponentContext = { + /** uid */ + uid?: string; /** parent */ p: null | ComponentContext; /** context */ diff --git a/packages/svelte/tests/runtime-legacy/shared.ts b/packages/svelte/tests/runtime-legacy/shared.ts index 7428efc57639..671446d04122 100644 --- a/packages/svelte/tests/runtime-legacy/shared.ts +++ b/packages/svelte/tests/runtime-legacy/shared.ts @@ -364,7 +364,8 @@ async function run_test_variant( target, props, intro: config.intro, - recover: config.recover ?? false + recover: config.recover ?? false, + uidPrefix: config.uid_prefix }); } } else { diff --git a/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js b/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js index 9fc01d31df90..2861c5c76fb8 100644 --- a/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js @@ -9,10 +9,10 @@ export default test({ target.innerHTML, ` -

c1

-

c2

-

c3

-

c4

+

c-myPrefix-1

+

c-myPrefix-2

+

c-myPrefix-3

+

c-myPrefix-4

` ); } else { @@ -36,11 +36,11 @@ export default test({ target.innerHTML, ` -

c1

-

c2

-

c3

-

c4

-

c5

+

c-myPrefix-1

+

c-myPrefix-2

+

c-myPrefix-3

+

c-myPrefix-4

+

c-myPrefix-5

` ); } else { @@ -52,7 +52,7 @@ export default test({

s-myPrefix-2

s-myPrefix-3

s-myPrefix-4

-

c1

+

c-myPrefix-1

` ); } diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index ebc58a040643..9a8a479ab0db 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -334,6 +334,10 @@ declare module 'svelte' { * @default true */ intro?: boolean; + /** + * Provide a prefix for the generated ID from `$props.id` + */ + uidPrefix?: string; } & ({} extends Props ? { /** @@ -485,6 +489,7 @@ declare module 'svelte' { context?: Map; intro?: boolean; recover?: boolean; + uidPrefix?: string; } : { target: Document | Element | ShadowRoot; props: Props; @@ -492,6 +497,7 @@ declare module 'svelte' { context?: Map; intro?: boolean; recover?: boolean; + uidPrefix?: string; }): Exports; /** * Unmounts a component that was previously mounted using `mount` or `hydrate`. From 5471700fece74d0d079c5d30e7e4af2560207256 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Fri, 28 Feb 2025 23:40:53 +0100 Subject: [PATCH 09/20] Apply suggestions from code review --- .changeset/wise-grapes-enjoy.md | 2 +- packages/svelte/src/internal/client/types.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/wise-grapes-enjoy.md b/.changeset/wise-grapes-enjoy.md index f18496e64cc7..6c2878ef7e8a 100644 --- a/.changeset/wise-grapes-enjoy.md +++ b/.changeset/wise-grapes-enjoy.md @@ -2,4 +2,4 @@ 'svelte': minor --- -feat: Add `uidPrefix` option in `render` function +feat: Add `uidPrefix` option in `render`/`mount`/`hydrate` functions diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index 500cf89623a5..0dc5d62d07a5 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -10,7 +10,7 @@ export type EventCallbackMap = Record; // when the JS VM JITs the code. export type ComponentContext = { - /** uid */ + /** used as prefix for $props.id */ uid?: string; /** parent */ p: null | ComponentContext; From 34d84fc54d199040caebf01321361339911279f1 Mon Sep 17 00:00:00 2001 From: Hugos68 Date: Sat, 1 Mar 2025 00:17:29 +0100 Subject: [PATCH 10/20] fix naming inconsistency --- packages/svelte/tests/runtime-browser/driver-ssr.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/tests/runtime-browser/driver-ssr.js b/packages/svelte/tests/runtime-browser/driver-ssr.js index 832aa64aa5c0..7705a6ecfa60 100644 --- a/packages/svelte/tests/runtime-browser/driver-ssr.js +++ b/packages/svelte/tests/runtime-browser/driver-ssr.js @@ -6,5 +6,5 @@ import config from '__CONFIG__'; import { render } from 'svelte/server'; export default function () { - return render(SvelteComponent, { props: config.props || {}, prefixUid: config.prefixUid }); + return render(SvelteComponent, { props: config.props || {}, uidPrefix: config?.uid_prefix }); } From fb05aaff7da24611b70d29fb18f431f05d4ef12e Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Sat, 1 Mar 2025 20:55:02 +0100 Subject: [PATCH 11/20] chore: add `uidPrefix` to `createClassComponent` --- packages/svelte/src/index.d.ts | 1 + packages/svelte/src/legacy/legacy-client.js | 3 ++- packages/svelte/tests/hydration/test.ts | 17 +++++++++-------- packages/svelte/types/index.d.ts | 1 + 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/svelte/src/index.d.ts b/packages/svelte/src/index.d.ts index 8609124675da..cb675d0328d7 100644 --- a/packages/svelte/src/index.d.ts +++ b/packages/svelte/src/index.d.ts @@ -16,6 +16,7 @@ export interface ComponentConstructorOptions< props?: Props; context?: Map; hydrate?: boolean; + uidPrefix?: string; intro?: boolean; recover?: boolean; sync?: boolean; diff --git a/packages/svelte/src/legacy/legacy-client.js b/packages/svelte/src/legacy/legacy-client.js index bb9a5a9c039b..c2381188eceb 100644 --- a/packages/svelte/src/legacy/legacy-client.js +++ b/packages/svelte/src/legacy/legacy-client.js @@ -116,7 +116,8 @@ class Svelte4Component { props, context: options.context, intro: options.intro ?? false, - recover: options.recover + recover: options.recover, + uidPrefix: options.uidPrefix }); // We don't flushSync for custom element wrappers or if the user doesn't want it diff --git a/packages/svelte/tests/hydration/test.ts b/packages/svelte/tests/hydration/test.ts index 1b3c548824ca..f2ffa00d4235 100644 --- a/packages/svelte/tests/hydration/test.ts +++ b/packages/svelte/tests/hydration/test.ts @@ -1,14 +1,14 @@ // @vitest-environment jsdom +import type { CompileOptions } from '#compiler'; import * as fs from 'node:fs'; -import { assert } from 'vitest'; -import { compile_directory, should_update_expected } from '../helpers.js'; -import { assert_html_equal } from '../html_equal.js'; -import { suite, assert_ok, type BaseTest } from '../suite.js'; +import { flushSync } from 'svelte'; import { createClassComponent } from 'svelte/legacy'; import { render } from 'svelte/server'; -import type { CompileOptions } from '#compiler'; -import { flushSync } from 'svelte'; +import { assert } from 'vitest'; +import { compile_directory } from '../helpers.js'; +import { assert_html_equal } from '../html_equal.js'; +import { assert_ok, suite, type BaseTest } from '../suite.js'; interface HydrationTest extends BaseTest { load_compiled?: boolean; @@ -105,7 +105,8 @@ const { test, run } = suite(async (config, cwd) => { component: (await import(`${cwd}/_output/client/main.svelte.js`)).default, target, hydrate: true, - props: config.props + props: config.props, + uidPrefix: config?.uid_prefix }); console.warn = warn; @@ -166,6 +167,6 @@ const { test, run } = suite(async (config, cwd) => { config.after_test?.(); } }); -export { test, assert_ok }; +export { assert_ok, test }; await run(__dirname); diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 9a8a479ab0db..0da5e1ec3fa7 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -13,6 +13,7 @@ declare module 'svelte' { props?: Props; context?: Map; hydrate?: boolean; + uidPrefix?: string; intro?: boolean; recover?: boolean; sync?: boolean; From 740a1914bb8a3102ce0dc0b46371d8b2ccb0fc86 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Mar 2025 10:48:29 -0500 Subject: [PATCH 12/20] make prefix a prefix --- .../src/internal/client/dom/template.js | 14 ++++---- packages/svelte/src/internal/server/index.js | 2 +- .../samples/props-id-prefix/_config.js | 36 +++++++++---------- playgrounds/sandbox/index.html | 3 +- playgrounds/sandbox/ssr-dev.js | 4 ++- 5 files changed, 32 insertions(+), 27 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index cf0e7e9288f6..73f70dfe6852 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -1,4 +1,4 @@ -/** @import { Effect, TemplateNode } from '#client' */ +/** @import { ComponentContext, Effect, TemplateNode } from '#client' */ import { hydrate_next, hydrate_node, hydrating, set_hydrate_node } from './hydration.js'; import { create_text, get_first_child, is_firefox } from './operations.js'; import { create_fragment_from_html } from './reconciler.js'; @@ -261,18 +261,20 @@ export function reset_props_id() { * Create (or hydrate) an unique UID for the component instance. */ export function props_id() { + var prefix = /** @type {ComponentContext} */ (component_context).uid + ? `${/** @type {ComponentContext} */ (component_context).uid}-` + : ''; + if ( hydrating && hydrate_node && hydrate_node.nodeType === 8 && - hydrate_node.textContent?.startsWith('#s') + hydrate_node.textContent?.startsWith(`#${prefix}s`) ) { const id = hydrate_node.textContent.substring(1); hydrate_next(); return id; } - if (component_context?.uid) { - return `c-${component_context.uid}-${uid++}`; - } - return 'c' + uid++; + + return `${prefix}c${uid++}`; } diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index e1c991c88638..ea6606fceed4 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -93,7 +93,7 @@ export let on_destroy = []; */ function props_id_generator(prefix) { let uid = 1; - return () => `s${prefix ? `-${prefix}-` : ''}${uid++}`; + return () => `${prefix ? `${prefix}-` : ''}s${uid++}`; } /** diff --git a/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js b/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js index 2861c5c76fb8..fad802e91405 100644 --- a/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js @@ -9,10 +9,10 @@ export default test({ target.innerHTML, ` -

c-myPrefix-1

-

c-myPrefix-2

-

c-myPrefix-3

-

c-myPrefix-4

+

myPrefix-c1

+

myPrefix-c2

+

myPrefix-c3

+

myPrefix-c4

` ); } else { @@ -20,10 +20,10 @@ export default test({ target.innerHTML, ` -

s-myPrefix-1

-

s-myPrefix-2

-

s-myPrefix-3

-

s-myPrefix-4

+

myPrefix-s1

+

myPrefix-s2

+

myPrefix-s3

+

myPrefix-s4

` ); } @@ -36,11 +36,11 @@ export default test({ target.innerHTML, ` -

c-myPrefix-1

-

c-myPrefix-2

-

c-myPrefix-3

-

c-myPrefix-4

-

c-myPrefix-5

+

myPrefix-c1

+

myPrefix-c2

+

myPrefix-c3

+

myPrefix-c4

+

myPrefix-c5

` ); } else { @@ -48,11 +48,11 @@ export default test({ target.innerHTML, ` -

s-myPrefix-1

-

s-myPrefix-2

-

s-myPrefix-3

-

s-myPrefix-4

-

c-myPrefix-1

+

myPrefix-s1

+

myPrefix-s2

+

myPrefix-s3

+

myPrefix-s4

+

myPrefix-c1

` ); } diff --git a/playgrounds/sandbox/index.html b/playgrounds/sandbox/index.html index 845538abf073..21d83479862e 100644 --- a/playgrounds/sandbox/index.html +++ b/playgrounds/sandbox/index.html @@ -18,7 +18,8 @@ const render = root.firstChild?.nextSibling ? hydrate : mount; const component = render(App, { - target: document.getElementById('root') + target: document.getElementById('root'), + uidPrefix: 'myPrefix' }); diff --git a/playgrounds/sandbox/ssr-dev.js b/playgrounds/sandbox/ssr-dev.js index 01ce14e2664d..002f425fe15c 100644 --- a/playgrounds/sandbox/ssr-dev.js +++ b/playgrounds/sandbox/ssr-dev.js @@ -23,7 +23,9 @@ polka() const template = fs.readFileSync(path.resolve(__dirname, 'index.html'), 'utf-8'); const transformed_template = await vite.transformIndexHtml(req.url, template); const { default: App } = await vite.ssrLoadModule('/src/App.svelte'); - const { head, body } = render(App); + const { head, body } = render(App, { + uidPrefix: 'myPrefix' + }); const html = transformed_template .replace(``, head) From 7194bb8f2fa3ca61926ddb141edec439d273eb9e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Mar 2025 10:52:26 -0500 Subject: [PATCH 13/20] rename to idPrefix --- packages/svelte/src/index.d.ts | 4 +-- packages/svelte/src/internal/client/render.js | 12 +++---- packages/svelte/src/internal/server/index.js | 4 +-- packages/svelte/src/legacy/legacy-client.js | 2 +- packages/svelte/src/server/index.d.ts | 4 +-- packages/svelte/tests/hydration/test.ts | 6 ++-- .../svelte/tests/runtime-browser/assert.js | 2 +- .../tests/runtime-browser/driver-ssr.js | 2 +- .../svelte/tests/runtime-browser/test-ssr.ts | 2 +- .../svelte/tests/runtime-legacy/shared.ts | 6 ++-- .../samples/props-id-prefix/_config.js | 2 +- .../tests/server-side-rendering/test.ts | 4 +-- packages/svelte/types/index.d.ts | 32 +++++++++---------- 13 files changed, 41 insertions(+), 41 deletions(-) diff --git a/packages/svelte/src/index.d.ts b/packages/svelte/src/index.d.ts index cb675d0328d7..05e86b2c7146 100644 --- a/packages/svelte/src/index.d.ts +++ b/packages/svelte/src/index.d.ts @@ -16,7 +16,7 @@ export interface ComponentConstructorOptions< props?: Props; context?: Map; hydrate?: boolean; - uidPrefix?: string; + idPrefix?: string; intro?: boolean; recover?: boolean; sync?: boolean; @@ -341,7 +341,7 @@ export type MountOptions = Record /** * Provide a prefix for the generated ID from `$props.id` */ - uidPrefix?: string; + idPrefix?: string; } & ({} extends Props ? { /** diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 5d5c689cb747..4b88673eefef 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -86,7 +86,7 @@ export function mount(component, options) { * context?: Map; * intro?: boolean; * recover?: boolean; - * uidPrefix?: string; + * idPrefix?: string; * } : { * target: Document | Element | ShadowRoot; * props: Props; @@ -94,7 +94,7 @@ export function mount(component, options) { * context?: Map; * intro?: boolean; * recover?: boolean; - * uidPrefix?: string; + * idPrefix?: string; * }} options * @returns {Exports} */ @@ -169,7 +169,7 @@ const document_listeners = new Map(); */ function _mount( Component, - { target, anchor, props = {}, events, context, intro = true, uidPrefix } + { target, anchor, props = {}, events, context, intro = true, idPrefix } ) { init_operations(); @@ -214,14 +214,14 @@ function _mount( var anchor_node = anchor ?? target.appendChild(create_text()); branch(() => { - if (context || uidPrefix != null) { + if (context || idPrefix != null) { push({}); var ctx = /** @type {ComponentContext} */ (component_context); if (context) { ctx.c = context; } - if (uidPrefix != null) { - ctx.uid = uidPrefix; + if (idPrefix != null) { + ctx.uid = idPrefix; } } diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index ea6606fceed4..5283f1903a13 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -101,11 +101,11 @@ function props_id_generator(prefix) { * Takes a component and returns an object with `body` and `head` properties on it, which you can use to populate the HTML when server-rendering your app. * @template {Record} Props * @param {import('svelte').Component | ComponentType>} component - * @param {{ props?: Omit; context?: Map; uidPrefix?: string }} [options] + * @param {{ props?: Omit; context?: Map; idPrefix?: string }} [options] * @returns {RenderOutput} */ export function render(component, options = {}) { - const uid = props_id_generator(options.uidPrefix); + const uid = props_id_generator(options.idPrefix); /** @type {Payload} */ const payload = { out: '', diff --git a/packages/svelte/src/legacy/legacy-client.js b/packages/svelte/src/legacy/legacy-client.js index c2381188eceb..270c377ac9f3 100644 --- a/packages/svelte/src/legacy/legacy-client.js +++ b/packages/svelte/src/legacy/legacy-client.js @@ -117,7 +117,7 @@ class Svelte4Component { context: options.context, intro: options.intro ?? false, recover: options.recover, - uidPrefix: options.uidPrefix + idPrefix: options.idPrefix }); // We don't flushSync for custom element wrappers or if the user doesn't want it diff --git a/packages/svelte/src/server/index.d.ts b/packages/svelte/src/server/index.d.ts index 75a64b182813..d5a3b813e6cb 100644 --- a/packages/svelte/src/server/index.d.ts +++ b/packages/svelte/src/server/index.d.ts @@ -15,7 +15,7 @@ export function render< options?: { props?: Omit; context?: Map; - uidPrefix?: string; + idPrefix?: string; } ] : [ @@ -23,7 +23,7 @@ export function render< options: { props: Omit; context?: Map; - uidPrefix?: string; + idPrefix?: string; } ] ): RenderOutput; diff --git a/packages/svelte/tests/hydration/test.ts b/packages/svelte/tests/hydration/test.ts index f2ffa00d4235..dc56c424b353 100644 --- a/packages/svelte/tests/hydration/test.ts +++ b/packages/svelte/tests/hydration/test.ts @@ -13,7 +13,7 @@ import { assert_ok, suite, type BaseTest } from '../suite.js'; interface HydrationTest extends BaseTest { load_compiled?: boolean; server_props?: Record; - uid_prefix?: string; + id_prefix?: string; props?: Record; compileOptions?: Partial; /** @@ -52,7 +52,7 @@ const { test, run } = suite(async (config, cwd) => { const rendered = render((await import(`${cwd}/_output/server/main.svelte.js`)).default, { props: config.server_props ?? config.props ?? {}, - uidPrefix: config?.uid_prefix + idPrefix: config?.id_prefix }); const override = read(`${cwd}/_override.html`); @@ -106,7 +106,7 @@ const { test, run } = suite(async (config, cwd) => { target, hydrate: true, props: config.props, - uidPrefix: config?.uid_prefix + idPrefix: config?.id_prefix }); console.warn = warn; diff --git a/packages/svelte/tests/runtime-browser/assert.js b/packages/svelte/tests/runtime-browser/assert.js index ed79dd43d79e..fb460c722a0f 100644 --- a/packages/svelte/tests/runtime-browser/assert.js +++ b/packages/svelte/tests/runtime-browser/assert.js @@ -119,7 +119,7 @@ function normalize_children(node) { * skip_mode?: Array<'server' | 'client' | 'hydrate'>; * html?: string; * ssrHtml?: string; - * uid_prefix?: string; + * id_prefix?: string; * props?: Props; * compileOptions?: Partial; * test?: (args: { diff --git a/packages/svelte/tests/runtime-browser/driver-ssr.js b/packages/svelte/tests/runtime-browser/driver-ssr.js index 7705a6ecfa60..7067e48a1fb9 100644 --- a/packages/svelte/tests/runtime-browser/driver-ssr.js +++ b/packages/svelte/tests/runtime-browser/driver-ssr.js @@ -6,5 +6,5 @@ import config from '__CONFIG__'; import { render } from 'svelte/server'; export default function () { - return render(SvelteComponent, { props: config.props || {}, uidPrefix: config?.uid_prefix }); + return render(SvelteComponent, { props: config.props || {}, idPrefix: config?.id_prefix }); } diff --git a/packages/svelte/tests/runtime-browser/test-ssr.ts b/packages/svelte/tests/runtime-browser/test-ssr.ts index 92f6fd04e160..6987fac9155a 100644 --- a/packages/svelte/tests/runtime-browser/test-ssr.ts +++ b/packages/svelte/tests/runtime-browser/test-ssr.ts @@ -20,7 +20,7 @@ export async function run_ssr_test( await compile_directory(test_dir, 'server', config.compileOptions); const Component = (await import(`${test_dir}/_output/server/main.svelte.js`)).default; - const { body } = render(Component, { props: config.props || {}, uidPrefix: config.uid_prefix }); + const { body } = render(Component, { props: config.props || {}, idPrefix: config.id_prefix }); fs.writeFileSync(`${test_dir}/_output/rendered.html`, body); diff --git a/packages/svelte/tests/runtime-legacy/shared.ts b/packages/svelte/tests/runtime-legacy/shared.ts index 671446d04122..f54ef763e7fa 100644 --- a/packages/svelte/tests/runtime-legacy/shared.ts +++ b/packages/svelte/tests/runtime-legacy/shared.ts @@ -37,7 +37,7 @@ export interface RuntimeTest = Record; props?: Props; server_props?: Props; - uid_prefix?: string; + id_prefix?: string; before_test?: () => void; after_test?: () => void; test?: (args: { @@ -287,7 +287,7 @@ async function run_test_variant( const SsrSvelteComponent = (await import(`${cwd}/_output/server/main.svelte.js`)).default; const { html, head } = render(SsrSvelteComponent, { props: config.server_props ?? config.props ?? {}, - uidPrefix: config.uid_prefix + idPrefix: config.id_prefix }); fs.writeFileSync(`${cwd}/_output/rendered.html`, html); @@ -365,7 +365,7 @@ async function run_test_variant( props, intro: config.intro, recover: config.recover ?? false, - uidPrefix: config.uid_prefix + idPrefix: config.id_prefix }); } } else { diff --git a/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js b/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js index fad802e91405..fc3b7e416cd8 100644 --- a/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/props-id-prefix/_config.js @@ -2,7 +2,7 @@ import { flushSync } from 'svelte'; import { test } from '../../test'; export default test({ - uid_prefix: 'myPrefix', + id_prefix: 'myPrefix', test({ assert, target, variant }) { if (variant === 'dom') { assert.htmlEqual( diff --git a/packages/svelte/tests/server-side-rendering/test.ts b/packages/svelte/tests/server-side-rendering/test.ts index 5f97ac09c116..3e5753942745 100644 --- a/packages/svelte/tests/server-side-rendering/test.ts +++ b/packages/svelte/tests/server-side-rendering/test.ts @@ -15,7 +15,7 @@ import type { CompileOptions } from '#compiler'; interface SSRTest extends BaseTest { compileOptions?: Partial; props?: Record; - uid_prefix?: string; + id_prefix?: string; withoutNormalizeHtml?: boolean; errors?: string[]; } @@ -34,7 +34,7 @@ const { test, run } = suite(async (config, test_dir) => { const Component = (await import(`${test_dir}/_output/server/main.svelte.js`)).default; const expected_html = try_read_file(`${test_dir}/_expected.html`); - const rendered = render(Component, { props: config.props || {}, uidPrefix: config.uid_prefix }); + const rendered = render(Component, { props: config.props || {}, idPrefix: config.id_prefix }); const { body, head } = rendered; fs.writeFileSync(`${test_dir}/_output/rendered.html`, body); diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 0da5e1ec3fa7..2fd67c837c86 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -13,7 +13,7 @@ declare module 'svelte' { props?: Props; context?: Map; hydrate?: boolean; - uidPrefix?: string; + idPrefix?: string; intro?: boolean; recover?: boolean; sync?: boolean; @@ -338,7 +338,7 @@ declare module 'svelte' { /** * Provide a prefix for the generated ID from `$props.id` */ - uidPrefix?: string; + idPrefix?: string; } & ({} extends Props ? { /** @@ -490,7 +490,7 @@ declare module 'svelte' { context?: Map; intro?: boolean; recover?: boolean; - uidPrefix?: string; + idPrefix?: string; } : { target: Document | Element | ShadowRoot; props: Props; @@ -498,7 +498,7 @@ declare module 'svelte' { context?: Map; intro?: boolean; recover?: boolean; - uidPrefix?: string; + idPrefix?: string; }): Exports; /** * Unmounts a component that was previously mounted using `mount` or `hydrate`. @@ -1882,10 +1882,10 @@ declare module 'svelte/motion' { * const tween = Tween.of(() => number); * * ``` - * + * */ static of(fn: () => U, options?: TweenedOptions | undefined): Tween; - + constructor(value: T, options?: TweenedOptions); /** * Sets `tween.target` to `value` and returns a `Promise` that resolves if and when `tween.current` catches up to it. @@ -1904,21 +1904,21 @@ declare module 'svelte/motion' { declare module 'svelte/reactivity' { export class SvelteDate extends Date { - + constructor(...params: any[]); #private; } export class SvelteSet extends Set { - + constructor(value?: Iterable | null | undefined); - + add(value: T): this; #private; } export class SvelteMap extends Map { - + constructor(value?: Iterable | null | undefined); - + set(key: K, value: V): this; #private; } @@ -1928,7 +1928,7 @@ declare module 'svelte/reactivity' { } const REPLACE: unique symbol; export class SvelteURLSearchParams extends URLSearchParams { - + [REPLACE](params: URLSearchParams): void; #private; } @@ -2000,7 +2000,7 @@ declare module 'svelte/reactivity' { */ export function createSubscriber(start: (update: () => void) => (() => void) | void): () => void; class ReactiveValue { - + constructor(fn: () => T, onsubscribe: (update: () => void) => void); get current(): T; #private; @@ -2065,7 +2065,7 @@ declare module 'svelte/reactivity/window' { get current(): number | undefined; }; class ReactiveValue { - + constructor(fn: () => T, onsubscribe: (update: () => void) => void); get current(): T; #private; @@ -2090,7 +2090,7 @@ declare module 'svelte/server' { options?: { props?: Omit; context?: Map; - uidPrefix?: string; + idPrefix?: string; } ] : [ @@ -2098,7 +2098,7 @@ declare module 'svelte/server' { options: { props: Omit; context?: Map; - uidPrefix?: string; + idPrefix?: string; } ] ): RenderOutput; From 20792170b555101415ef40298d8951b294ba332d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Mar 2025 11:06:42 -0500 Subject: [PATCH 14/20] set prefix globally, rather than adding a property to component context --- packages/svelte/src/internal/client/context.js | 3 +-- packages/svelte/src/internal/client/dom/hydration.js | 7 +++++++ packages/svelte/src/internal/client/dom/template.js | 10 +++------- packages/svelte/src/internal/client/render.js | 8 ++++---- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js index 64244b6413a0..bd94d5ad8a19 100644 --- a/packages/svelte/src/internal/client/context.js +++ b/packages/svelte/src/internal/client/context.js @@ -119,8 +119,7 @@ export function push(props, runes = false, fn) { m: false, s: props, x: null, - l: null, - uid: component_context?.uid + l: null }; if (legacy_mode_flag && !runes) { diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index 8523ff97d559..9d743396e6d5 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -20,6 +20,13 @@ export function set_hydrating(value) { hydrating = value; } +export let id_prefix = ''; + +/** @param {string} v */ +export function set_id_prefix(v) { + id_prefix = v; +} + /** * The node that is currently being hydrated. This starts out as the first node inside the opening * comment, and updates each time a component calls `$.child(...)` or `$.sibling(...)`. diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index 73f70dfe6852..e56ec0cc06f7 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -1,5 +1,5 @@ /** @import { ComponentContext, Effect, TemplateNode } from '#client' */ -import { hydrate_next, hydrate_node, hydrating, set_hydrate_node } from './hydration.js'; +import { hydrate_next, hydrate_node, hydrating, id_prefix, set_hydrate_node } from './hydration.js'; import { create_text, get_first_child, is_firefox } from './operations.js'; import { create_fragment_from_html } from './reconciler.js'; import { active_effect } from '../runtime.js'; @@ -261,20 +261,16 @@ export function reset_props_id() { * Create (or hydrate) an unique UID for the component instance. */ export function props_id() { - var prefix = /** @type {ComponentContext} */ (component_context).uid - ? `${/** @type {ComponentContext} */ (component_context).uid}-` - : ''; - if ( hydrating && hydrate_node && hydrate_node.nodeType === 8 && - hydrate_node.textContent?.startsWith(`#${prefix}s`) + hydrate_node.textContent?.startsWith(`#${id_prefix}s`) ) { const id = hydrate_node.textContent.substring(1); hydrate_next(); return id; } - return `${prefix}c${uid++}`; + return `${id_prefix}c${uid++}`; } diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 4b88673eefef..e15405e3fb44 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -17,7 +17,8 @@ import { hydrate_node, hydrating, set_hydrate_node, - set_hydrating + set_hydrating, + set_id_prefix } from './dom/hydration.js'; import { array_from } from '../shared/utils.js'; import { @@ -220,9 +221,8 @@ function _mount( if (context) { ctx.c = context; } - if (idPrefix != null) { - ctx.uid = idPrefix; - } + + set_id_prefix(idPrefix ? idPrefix + '-' : ''); } if (events) { From ee92e9622a6187f4cbef983b202a27ce9bd26a53 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Mar 2025 11:07:19 -0500 Subject: [PATCH 15/20] update changeset --- .changeset/wise-grapes-enjoy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/wise-grapes-enjoy.md b/.changeset/wise-grapes-enjoy.md index 6c2878ef7e8a..3a83ae9f732e 100644 --- a/.changeset/wise-grapes-enjoy.md +++ b/.changeset/wise-grapes-enjoy.md @@ -2,4 +2,4 @@ 'svelte': minor --- -feat: Add `uidPrefix` option in `render`/`mount`/`hydrate` functions +feat: Add `idPrefix` option in `render`/`mount`/`hydrate` functions From 04dd8e1523464c19c14ea9c06f67d80fd6f71944 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Mar 2025 11:08:24 -0500 Subject: [PATCH 16/20] unused --- packages/svelte/src/internal/client/types.d.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index 0dc5d62d07a5..7208ed77837e 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -10,8 +10,6 @@ export type EventCallbackMap = Record; // when the JS VM JITs the code. export type ComponentContext = { - /** used as prefix for $props.id */ - uid?: string; /** parent */ p: null | ComponentContext; /** context */ From 247f5868d4d75e7cca4e94f1aebac7e36675ee42 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Mar 2025 11:10:05 -0500 Subject: [PATCH 17/20] do work once rather than n times --- packages/svelte/src/internal/server/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 5283f1903a13..2591dbe4eaab 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -88,12 +88,12 @@ export let on_destroy = []; /** * Creates an ID generator - * @param {string | undefined} prefix + * @param {string} prefix * @returns {() => string} */ function props_id_generator(prefix) { let uid = 1; - return () => `${prefix ? `${prefix}-` : ''}s${uid++}`; + return () => `${prefix}s${uid++}`; } /** @@ -105,7 +105,7 @@ function props_id_generator(prefix) { * @returns {RenderOutput} */ export function render(component, options = {}) { - const uid = props_id_generator(options.idPrefix); + const uid = props_id_generator(options.idPrefix ? options.idPrefix + '-' : ''); /** @type {Payload} */ const payload = { out: '', From 4538613cef28f926b091294eed9ea92cef239daf Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Mar 2025 11:10:36 -0500 Subject: [PATCH 18/20] unused --- packages/svelte/src/internal/client/dom/template.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index e56ec0cc06f7..71653805b557 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -1,10 +1,9 @@ -/** @import { ComponentContext, Effect, TemplateNode } from '#client' */ +/** @import { Effect, TemplateNode } from '#client' */ import { hydrate_next, hydrate_node, hydrating, id_prefix, set_hydrate_node } from './hydration.js'; import { create_text, get_first_child, is_firefox } from './operations.js'; import { create_fragment_from_html } from './reconciler.js'; import { active_effect } from '../runtime.js'; import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../constants.js'; -import { component_context } from '../context.js'; /** * @param {TemplateNode} start From 396575d9942d1da1811d0b0045e91c0536c97b9d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Mar 2025 11:12:16 -0500 Subject: [PATCH 19/20] fix whitespace --- packages/svelte/src/internal/client/render.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index e15405e3fb44..7a7aa1dea196 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -87,7 +87,7 @@ export function mount(component, options) { * context?: Map; * intro?: boolean; * recover?: boolean; - * idPrefix?: string; + * idPrefix?: string; * } : { * target: Document | Element | ShadowRoot; * props: Props; @@ -95,7 +95,7 @@ export function mount(component, options) { * context?: Map; * intro?: boolean; * recover?: boolean; - * idPrefix?: string; + * idPrefix?: string; * }} options * @returns {Exports} */ From e5498290ede98b98f402e500962de8decb191b14 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Mar 2025 11:12:42 -0500 Subject: [PATCH 20/20] revert --- playgrounds/sandbox/index.html | 3 +-- playgrounds/sandbox/ssr-dev.js | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/playgrounds/sandbox/index.html b/playgrounds/sandbox/index.html index 21d83479862e..845538abf073 100644 --- a/playgrounds/sandbox/index.html +++ b/playgrounds/sandbox/index.html @@ -18,8 +18,7 @@ const render = root.firstChild?.nextSibling ? hydrate : mount; const component = render(App, { - target: document.getElementById('root'), - uidPrefix: 'myPrefix' + target: document.getElementById('root') }); diff --git a/playgrounds/sandbox/ssr-dev.js b/playgrounds/sandbox/ssr-dev.js index 002f425fe15c..01ce14e2664d 100644 --- a/playgrounds/sandbox/ssr-dev.js +++ b/playgrounds/sandbox/ssr-dev.js @@ -23,9 +23,7 @@ polka() const template = fs.readFileSync(path.resolve(__dirname, 'index.html'), 'utf-8'); const transformed_template = await vite.transformIndexHtml(req.url, template); const { default: App } = await vite.ssrLoadModule('/src/App.svelte'); - const { head, body } = render(App, { - uidPrefix: 'myPrefix' - }); + const { head, body } = render(App); const html = transformed_template .replace(``, head)