Skip to content

feat: $props.id(), a SSR-safe ID generation #15185

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
first impl of $$uid
  • Loading branch information
adiguba committed Feb 2, 2025
commit 657a09aa4c0838d742418eb7f9d28f40312e670d
2 changes: 2 additions & 0 deletions packages/svelte/src/ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -500,3 +500,5 @@ declare namespace $host {
/** @deprecated */
export const toString: never;
}

declare const $$uid: string;
2 changes: 1 addition & 1 deletion packages/svelte/src/compiler/phases/2-analyze/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ function get_component_name(filename) {
return name[0].toUpperCase() + name.slice(1);
}

const RESERVED = ['$$props', '$$restProps', '$$slots'];
const RESERVED = ['$$props', '$$restProps', '$$slots', '$$uid'];

/**
* @param {Program} ast
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ export function Identifier(node, context) {
context.state.analysis.uses_slots = true;
}

if (node.name === '$$uid') {
context.state.analysis.uses_uid = true;
}

if (context.state.analysis.runes) {
if (
is_rune(node.name) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,10 @@ export function client_component(analysis, options) {
component_block.body.unshift(b.stmt(b.call('$.check_target', b.id('new.target'))));
}

if (analysis.uses_uid) {
component_block.body.unshift(b.const('$$uid', b.call('$.create_uid')));
}

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
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,10 @@ export function server_component(analysis, options) {
.../** @type {Statement[]} */ (template.body)
]);

if (analysis.uses_uid) {
component_block.body.unshift(b.const('$$uid', b.call('$.create_uid', b.id('$$payload'))));
}

let should_inject_context = dev || analysis.needs_context;

if (should_inject_context) {
Expand Down
2 changes: 2 additions & 0 deletions packages/svelte/src/compiler/phases/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export interface ComponentAnalysis extends Analysis {
uses_slots: boolean;
uses_component_bindings: boolean;
uses_render_tags: boolean;
/** Whether the component uses `$$uid` */
uses_uid: boolean;
needs_context: boolean;
needs_props: boolean;
/** Set to the first event directive (on:x) found on a DOM element in the code */
Expand Down
21 changes: 21 additions & 0 deletions packages/svelte/src/internal/client/dom/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,24 @@ export function append(anchor, dom) {

anchor.before(/** @type {Node} */ (dom));
}

let NEXT_UID = 100;

/**
* Create (or hydrate) an unique UID for the component instance.
*/
export function create_uid() {
let uid;
if (
hydrating &&
hydrate_node &&
hydrate_node.nodeType === Node.COMMENT_NODE &&
hydrate_node.textContent?.startsWith('#s')
) {
uid = hydrate_node.textContent.substring(1);
hydrate_next();
} else {
uid = 'c' + NEXT_UID++;
}
return uid;
}
3 changes: 2 additions & 1 deletion packages/svelte/src/internal/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ export {
mathml_template,
template,
template_with_script,
text
text,
create_uid
} from './dom/template.js';
export { derived, derived_safe_equal } from './reactivity/deriveds.js';
export {
Expand Down
31 changes: 27 additions & 4 deletions packages/svelte/src/internal/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ const INVALID_ATTR_NAME_CHAR_REGEX =
* @param {Payload} to_copy
* @returns {Payload}
*/
export function copy_payload({ out, css, head }) {
export function copy_payload({ out, css, head, uid }) {
return {
out,
css: new Set(css),
head: {
title: head.title,
out: head.out
}
},
uid
};
}

Expand All @@ -48,6 +49,7 @@ export function copy_payload({ out, css, head }) {
export function assign_payload(p1, p2) {
p1.out = p2.out;
p1.head = p2.head;
p1.uid = p2.uid;
}

/**
Expand Down Expand Up @@ -83,17 +85,27 @@ export function element(payload, tag, attributes_fn = noop, children_fn = noop)
*/
export let on_destroy = [];

function create_uid_generator() {
let uid = 100;
return () => 's' + 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>, uid?: () => string }} [options]
* @returns {RenderOutput}
*/
export function render(component, options = {}) {
/** @type {Payload} */
const payload = { out: '', css: new Set(), head: { title: '', out: '' } };
const payload = {
out: '',
css: new Set(),
head: { title: '', out: '' },
uid: options.uid ?? create_uid_generator()
};

const prev_on_destroy = on_destroy;
on_destroy = [];
Expand Down Expand Up @@ -526,6 +538,17 @@ export function once(get_value) {
};
}

/**
* Create an unique ID
* @param {Payload} payload
* @returns {string}
*/
export function create_uid(payload) {
const UID = payload.uid();
payload.out += '<!--#' + UID + '-->';
return UID;
}

export { attr, clsx };

export { html } from './blocks/html.js';
Expand Down
2 changes: 2 additions & 0 deletions packages/svelte/src/internal/server/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export interface Payload {
title: string;
out: string;
};
/** Function that generates a unique ID */
uid: () => string;
}

export interface RenderOutput {
Expand Down
2 changes: 2 additions & 0 deletions packages/svelte/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3159,4 +3159,6 @@ declare namespace $host {
export const toString: never;
}

declare const $$uid: string;

//# sourceMappingURL=index.d.ts.map
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy