Skip to content
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/wise-grapes-enjoy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': minor
---

feat: Add `uidPrefix` option in `render` function
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export function CallExpression(node, context) {
}

context.state.analysis.props_id = parent.id;
context.state.analysis.needs_context = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also revert this if we don't use context anymore


break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')));
}
Comment on lines +403 to +407
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we don't use context we can revert this change too

component_block.body.unshift(b.stmt(b.call('$.push', ...push_args)));

let to_push;
Expand Down Expand Up @@ -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)))))
Expand Down
4 changes: 4 additions & 0 deletions packages/svelte/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,10 @@ export type MountOptions<Props extends Record<string, any> = Record<string, any>
* @default true
*/
intro?: boolean;
/**
* Provide a prefix for the generated ID from `$props.id`
*/
uidPrefix?: string;
} & ({} extends Props
? {
/**
Expand Down
3 changes: 2 additions & 1 deletion packages/svelte/src/internal/client/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
5 changes: 4 additions & 1 deletion packages/svelte/src/internal/client/dom/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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++;
}
16 changes: 13 additions & 3 deletions packages/svelte/src/internal/client/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,15 @@ export function mount(component, options) {
* context?: Map<any, any>;
* intro?: boolean;
* recover?: boolean;
* uidPrefix?: string;
* } : {
* target: Document | Element | ShadowRoot;
* props: Props;
* events?: Record<string, (e: any) => any>;
* context?: Map<any, any>;
* intro?: boolean;
* recover?: boolean;
* uidPrefix?: string;
* }} options
* @returns {Exports}
*/
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions packages/svelte/src/internal/client/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export type EventCallbackMap = Record<string, EventCallback | EventCallback[]>;
// when the JS VM JITs the code.

export type ComponentContext = {
/** uid */
uid?: string;
/** parent */
p: null | ComponentContext;
/** context */
Expand Down
13 changes: 9 additions & 4 deletions packages/svelte/src/internal/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,21 +86,26 @@ export function element(payload, tag, attributes_fn = noop, children_fn = noop)
*/
export let on_destroy = [];

function props_id_generator() {
/**
* Creates an ID generator
* @param {string | undefined} prefix
* @returns {() => string}
*/
function props_id_generator(prefix) {
let uid = 1;
return () => 's' + uid++;
return () => `s${prefix ? `-${prefix}-` : ''}${uid++}`;
}

/**
* Only available on the server and when compiling with the `server` option.
* 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<string, any>} Props
* @param {import('svelte').Component<Props> | ComponentType<SvelteComponent<Props>>} component
* @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }} [options]
* @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any>; uidPrefix?: string }} [options]
* @returns {RenderOutput}
*/
export function render(component, options = {}) {
const uid = props_id_generator();
const uid = props_id_generator(options.uidPrefix);
/** @type {Payload} */
const payload = {
out: '',
Expand Down
12 changes: 10 additions & 2 deletions packages/svelte/src/server/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,18 @@ export function render<
...args: {} extends Props
? [
component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp,
options?: { props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }
options?: {
props?: Omit<Props, '$$slots' | '$$events'>;
context?: Map<any, any>;
uidPrefix?: string;
}
]
: [
component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp,
options: { props: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }
options: {
props: Omit<Props, '$$slots' | '$$events'>;
context?: Map<any, any>;
uidPrefix?: string;
}
]
): RenderOutput;
4 changes: 3 additions & 1 deletion packages/svelte/tests/hydration/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { flushSync } from 'svelte';
interface HydrationTest extends BaseTest {
load_compiled?: boolean;
server_props?: Record<string, any>;
uid_prefix?: string;
props?: Record<string, any>;
compileOptions?: Partial<CompileOptions>;
/**
Expand Down Expand Up @@ -50,7 +51,8 @@ const { test, run } = suite<HydrationTest>(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`);
Expand Down
1 change: 1 addition & 0 deletions packages/svelte/tests/runtime-browser/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<CompileOptions>;
* test?: (args: {
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/tests/runtime-browser/driver-ssr.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
}
2 changes: 1 addition & 1 deletion packages/svelte/tests/runtime-browser/test-ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
7 changes: 5 additions & 2 deletions packages/svelte/tests/runtime-legacy/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export interface RuntimeTest<Props extends Record<string, any> = Record<string,
compileOptions?: Partial<CompileOptions>;
props?: Props;
server_props?: Props;
uid_prefix?: string;
before_test?: () => void;
after_test?: () => void;
test?: (args: {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -362,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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script>
let id = $props.id();
</script>

<p>{id}</p>
Original file line number Diff line number Diff line change
@@ -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,
`
<button>toggle</button>
<h1>c-myPrefix-1</h1>
<p>c-myPrefix-2</p>
<p>c-myPrefix-3</p>
<p>c-myPrefix-4</p>
`
);
} else {
assert.htmlEqual(
target.innerHTML,
`
<button>toggle</button>
<h1>s-myPrefix-1</h1>
<p>s-myPrefix-2</p>
<p>s-myPrefix-3</p>
<p>s-myPrefix-4</p>
`
);
}

let button = target.querySelector('button');
flushSync(() => button?.click());

if (variant === 'dom') {
assert.htmlEqual(
target.innerHTML,
`
<button>toggle</button>
<h1>c-myPrefix-1</h1>
<p>c-myPrefix-2</p>
<p>c-myPrefix-3</p>
<p>c-myPrefix-4</p>
<p>c-myPrefix-5</p>
`
);
} else {
assert.htmlEqual(
target.innerHTML,
`
<button>toggle</button>
<h1>s-myPrefix-1</h1>
<p>s-myPrefix-2</p>
<p>s-myPrefix-3</p>
<p>s-myPrefix-4</p>
<p>c-myPrefix-1</p>
`
);
}
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script>
import Child from './Child.svelte';

let id = $props.id();

let show = $state(false);
</script>

<button onclick={() => show = !show}>toggle</button>

<h1>{id}</h1>

<Child />
<Child />
<Child />

{#if show}
<Child />
{/if}
3 changes: 2 additions & 1 deletion packages/svelte/tests/server-side-rendering/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type { CompileOptions } from '#compiler';
interface SSRTest extends BaseTest {
compileOptions?: Partial<CompileOptions>;
props?: Record<string, any>;
uid_prefix?: string;
withoutNormalizeHtml?: boolean;
errors?: string[];
}
Expand All @@ -33,7 +34,7 @@ const { test, run } = suite<SSRTest>(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);
Expand Down
Loading