Skip to content

Commit b82692a

Browse files
Rich-HarrisHugos68paoloricciutidummdidumm
authored
feat: add idPrefix to render (#15428)
This ensures someone like Astro can render multiple islands and the unique ids produced in them are able to be deduplicated --------- Co-authored-by: Hugos68 <[email protected]> Co-authored-by: paoloricciuti <[email protected]> Co-authored-by: Simon Holthausen <[email protected]>
1 parent 7ce2dfc commit b82692a

File tree

16 files changed

+149
-29
lines changed

16 files changed

+149
-29
lines changed

.changeset/wise-grapes-enjoy.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': minor
3+
---
4+
5+
feat: Add `idPrefix` option to `render`

packages/svelte/src/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface ComponentConstructorOptions<
1919
intro?: boolean;
2020
recover?: boolean;
2121
sync?: boolean;
22+
idPrefix?: string;
2223
$$inline?: boolean;
2324
}
2425

packages/svelte/src/internal/client/dom/template.js

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -250,12 +250,6 @@ export function append(anchor, dom) {
250250
anchor.before(/** @type {Node} */ (dom));
251251
}
252252

253-
let uid = 1;
254-
255-
export function reset_props_id() {
256-
uid = 1;
257-
}
258-
259253
/**
260254
* Create (or hydrate) an unique UID for the component instance.
261255
*/
@@ -264,12 +258,16 @@ export function props_id() {
264258
hydrating &&
265259
hydrate_node &&
266260
hydrate_node.nodeType === 8 &&
267-
hydrate_node.textContent?.startsWith('#s')
261+
hydrate_node.textContent?.startsWith(`#`)
268262
) {
269263
const id = hydrate_node.textContent.substring(1);
270264
hydrate_next();
271265
return id;
272266
}
273267

274-
return 'c' + uid++;
268+
// @ts-expect-error This way we ensure the id is unique even across Svelte runtimes
269+
(window.__svelte ??= {}).uid ??= 1;
270+
271+
// @ts-expect-error
272+
return `c${window.__svelte.uid++}`;
275273
}
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { PUBLIC_VERSION } from '../version.js';
22

3-
if (typeof window !== 'undefined')
4-
// @ts-ignore
5-
(window.__svelte ||= { v: new Set() }).v.add(PUBLIC_VERSION);
3+
if (typeof window !== 'undefined') {
4+
// @ts-expect-error
5+
((window.__svelte ??= {}).v ??= new Set()).add(PUBLIC_VERSION);
6+
}

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,21 +86,26 @@ export function element(payload, tag, attributes_fn = noop, children_fn = noop)
8686
*/
8787
export let on_destroy = [];
8888

89-
function props_id_generator() {
89+
/**
90+
* Creates an ID generator
91+
* @param {string} prefix
92+
* @returns {() => string}
93+
*/
94+
function props_id_generator(prefix) {
9095
let uid = 1;
91-
return () => 's' + uid++;
96+
return () => `${prefix}s${uid++}`;
9297
}
9398

9499
/**
95100
* Only available on the server and when compiling with the `server` option.
96101
* 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.
97102
* @template {Record<string, any>} Props
98103
* @param {import('svelte').Component<Props> | ComponentType<SvelteComponent<Props>>} component
99-
* @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }} [options]
104+
* @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any>; idPrefix?: string }} [options]
100105
* @returns {RenderOutput}
101106
*/
102107
export function render(component, options = {}) {
103-
const uid = props_id_generator();
108+
const uid = props_id_generator(options.idPrefix ? options.idPrefix + '-' : '');
104109
/** @type {Payload} */
105110
const payload = {
106111
out: '',

packages/svelte/src/server/index.d.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,18 @@ export function render<
1212
...args: {} extends Props
1313
? [
1414
component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp,
15-
options?: { props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }
15+
options?: {
16+
props?: Omit<Props, '$$slots' | '$$events'>;
17+
context?: Map<any, any>;
18+
idPrefix?: string;
19+
}
1620
]
1721
: [
1822
component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp,
19-
options: { props: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }
23+
options: {
24+
props: Omit<Props, '$$slots' | '$$events'>;
25+
context?: Map<any, any>;
26+
idPrefix?: string;
27+
}
2028
]
2129
): RenderOutput;

packages/svelte/tests/hydration/test.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
import * as fs from 'node:fs';
44
import { assert } from 'vitest';
5-
import { compile_directory, should_update_expected } from '../helpers.js';
5+
import { compile_directory } from '../helpers.js';
66
import { assert_html_equal } from '../html_equal.js';
7-
import { suite, assert_ok, type BaseTest } from '../suite.js';
7+
import { assert_ok, suite, type BaseTest } from '../suite.js';
88
import { createClassComponent } from 'svelte/legacy';
99
import { render } from 'svelte/server';
1010
import type { CompileOptions } from '#compiler';
@@ -13,6 +13,7 @@ import { flushSync } from 'svelte';
1313
interface HydrationTest extends BaseTest {
1414
load_compiled?: boolean;
1515
server_props?: Record<string, any>;
16+
id_prefix?: string;
1617
props?: Record<string, any>;
1718
compileOptions?: Partial<CompileOptions>;
1819
/**
@@ -50,7 +51,8 @@ const { test, run } = suite<HydrationTest>(async (config, cwd) => {
5051
const head = window.document.head;
5152

5253
const rendered = render((await import(`${cwd}/_output/server/main.svelte.js`)).default, {
53-
props: config.server_props ?? config.props ?? {}
54+
props: config.server_props ?? config.props ?? {},
55+
idPrefix: config?.id_prefix
5456
});
5557

5658
const override = read(`${cwd}/_override.html`);
@@ -103,7 +105,8 @@ const { test, run } = suite<HydrationTest>(async (config, cwd) => {
103105
component: (await import(`${cwd}/_output/client/main.svelte.js`)).default,
104106
target,
105107
hydrate: true,
106-
props: config.props
108+
props: config.props,
109+
idPrefix: config?.id_prefix
107110
});
108111

109112
console.warn = warn;

packages/svelte/tests/runtime-browser/assert.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ function normalize_children(node) {
119119
* skip_mode?: Array<'server' | 'client' | 'hydrate'>;
120120
* html?: string;
121121
* ssrHtml?: string;
122+
* id_prefix?: string;
122123
* props?: Props;
123124
* compileOptions?: Partial<CompileOptions>;
124125
* test?: (args: {

packages/svelte/tests/runtime-browser/driver-ssr.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ import config from '__CONFIG__';
66
import { render } from 'svelte/server';
77

88
export default function () {
9-
return render(SvelteComponent, { props: config.props || {} });
9+
return render(SvelteComponent, { props: config.props || {}, idPrefix: config?.id_prefix });
1010
}

packages/svelte/tests/runtime-browser/test-ssr.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export async function run_ssr_test(
2020
await compile_directory(test_dir, 'server', config.compileOptions);
2121

2222
const Component = (await import(`${test_dir}/_output/server/main.svelte.js`)).default;
23-
const { body } = render(Component, { props: config.props || {} });
23+
const { body } = render(Component, { props: config.props || {}, idPrefix: config.id_prefix });
2424

2525
fs.writeFileSync(`${test_dir}/_output/rendered.html`, body);
2626

0 commit comments

Comments
 (0)