Skip to content

Commit 3da2646

Browse files
feat: more accurate render/mount/hydrate options (#12111)
make the props parameter optional only when there are no or only optional props --------- Co-authored-by: Simon Holthausen <[email protected]> Co-authored-by: Simon H <[email protected]>
1 parent 7295fac commit 3da2646

File tree

9 files changed

+130
-18
lines changed

9 files changed

+130
-18
lines changed

.changeset/famous-chairs-notice.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
feat: more accurate `render`/`mount`/`hydrate` options

packages/svelte/scripts/generate-types.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ await createBundle({
3030
[`${pkg.name}/legacy`]: `${dir}/src/legacy/legacy-client.js`,
3131
[`${pkg.name}/motion`]: `${dir}/src/motion/public.d.ts`,
3232
[`${pkg.name}/reactivity`]: `${dir}/src/reactivity/index-client.js`,
33-
[`${pkg.name}/server`]: `${dir}/src/server/index.js`,
33+
[`${pkg.name}/server`]: `${dir}/src/server/index.d.ts`,
3434
[`${pkg.name}/store`]: `${dir}/src/store/public.d.ts`,
3535
[`${pkg.name}/transition`]: `${dir}/src/transition/public.d.ts`,
3636
[`${pkg.name}/events`]: `${dir}/src/events/index.js`,

packages/svelte/src/internal/client/render.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,20 @@ export function slot(anchor, slot_fn, slot_props, fallback_fn) {
7272
* @template {Record<string, any>} Props
7373
* @template {Record<string, any>} Exports
7474
* @param {import('../../index.js').ComponentType<import('../../index.js').SvelteComponent<Props>> | import('../../index.js').Component<Props, Exports, any>} component
75-
* @param {{
75+
* @param {{} extends Props ? {
7676
* target: Document | Element | ShadowRoot;
7777
* anchor?: Node;
7878
* props?: Props;
7979
* events?: Record<string, (e: any) => any>;
8080
* context?: Map<any, any>;
8181
* intro?: boolean;
82+
* }: {
83+
* target: Document | Element | ShadowRoot;
84+
* props: Props;
85+
* anchor?: Node;
86+
* events?: Record<string, (e: any) => any>;
87+
* context?: Map<any, any>;
88+
* intro?: boolean;
8289
* }} options
8390
* @returns {Exports}
8491
*/
@@ -98,13 +105,20 @@ export function mount(component, options) {
98105
* @template {Record<string, any>} Props
99106
* @template {Record<string, any>} Exports
100107
* @param {import('../../index.js').ComponentType<import('../../index.js').SvelteComponent<Props>> | import('../../index.js').Component<Props, Exports, any>} component
101-
* @param {{
108+
* @param {{} extends Props ? {
102109
* target: Document | Element | ShadowRoot;
103110
* props?: Props;
104111
* events?: Record<string, (e: any) => any>;
105112
* context?: Map<any, any>;
106113
* intro?: boolean;
107114
* recover?: boolean;
115+
* } : {
116+
* target: Document | Element | ShadowRoot;
117+
* props: Props;
118+
* events?: Record<string, (e: any) => any>;
119+
* context?: Map<any, any>;
120+
* intro?: boolean;
121+
* recover?: boolean;
108122
* }} options
109123
* @returns {Exports}
110124
*/

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,10 @@ export let on_destroy = [];
9797
* 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.
9898
* @template {Record<string, any>} Props
9999
* @param {import('svelte').Component<Props> | import('svelte').ComponentType<import('svelte').SvelteComponent<Props>>} component
100-
* @param {{ props: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }} options
100+
* @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }} [options]
101101
* @returns {import('#server').RenderOutput}
102102
*/
103-
export function render(component, options) {
103+
export function render(component, options = {}) {
104104
const payload = create_payload();
105105

106106
const prev_on_destroy = on_destroy;

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

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { RenderOutput } from '#server';
2+
import type { ComponentProps, Component, SvelteComponent, ComponentType } from 'svelte';
3+
4+
/**
5+
* Only available on the server and when compiling with the `server` option.
6+
* 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.
7+
*/
8+
export function render<
9+
Comp extends SvelteComponent<any> | Component<any>,
10+
Props extends ComponentProps<Comp> = ComponentProps<Comp>
11+
>(
12+
...args: {} extends Props
13+
? [
14+
component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp,
15+
options?: { props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }
16+
]
17+
: [
18+
component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp,
19+
options: { props: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }
20+
]
21+
): RenderOutput;

packages/svelte/tests/types/component.ts

+51-2
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@ mount(NewComponent, {
131131
intro: false,
132132
recover: false
133133
});
134+
mount(
135+
NewComponent,
136+
// @ts-expect-error props missing
137+
{ target: null as any }
138+
);
139+
// if component receives no args, props can be omitted
140+
mount(null as any as typeof SvelteComponent<{}>, { target: null as any });
134141

135142
hydrate(NewComponent, {
136143
target: null as any as Document | Element | ShadowRoot,
@@ -148,6 +155,13 @@ hydrate(NewComponent, {
148155
intro: false,
149156
recover: false
150157
});
158+
hydrate(
159+
NewComponent,
160+
// @ts-expect-error props missing
161+
{ target: null as any }
162+
);
163+
// if component receives no args, props can be omitted
164+
hydrate(null as any as typeof SvelteComponent<{}>, { target: null as any });
151165

152166
render(NewComponent, {
153167
props: {
@@ -156,6 +170,14 @@ render(NewComponent, {
156170
x: ''
157171
}
158172
});
173+
// @ts-expect-error
174+
render(NewComponent);
175+
render(NewComponent, {
176+
props: {
177+
// @ts-expect-error
178+
prop: 1
179+
}
180+
});
159181

160182
// --------------------------------------------------------------------------- interop
161183

@@ -255,6 +277,13 @@ mount(functionComponent, {
255277
readonly: 1
256278
}
257279
});
280+
mount(
281+
functionComponent,
282+
// @ts-expect-error props missing
283+
{ target: null as any }
284+
);
285+
// if component receives no args, props can be omitted
286+
mount(null as any as Component<{}>, { target: null as any });
258287

259288
hydrate(functionComponent, {
260289
target: null as any as Document | Element | ShadowRoot,
@@ -272,13 +301,33 @@ hydrate(functionComponent, {
272301
binding: true
273302
}
274303
});
304+
hydrate(
305+
functionComponent,
306+
// @ts-expect-error props missing
307+
{ target: null as any }
308+
);
309+
// if component receives no args, props can be omitted
310+
hydrate(null as any as Component<{}>, { target: null as any });
275311

276312
render(functionComponent, {
277313
props: {
278314
binding: true,
279-
readonly: 'foo',
315+
readonly: 'foo'
316+
}
317+
});
318+
// @ts-expect-error
319+
render(functionComponent);
320+
render(functionComponent, {
321+
// @ts-expect-error
322+
props: {
323+
binding: true
324+
}
325+
});
326+
render(functionComponent, {
327+
props: {
328+
binding: true,
280329
// @ts-expect-error
281-
x: ''
330+
readonly: 1
282331
}
283332
});
284333

packages/svelte/tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"svelte/internal/client": ["./src/internal/client/index.js"],
2424
"svelte/legacy": ["./src/legacy/legacy-client.js"],
2525
"svelte/motion": ["./src/motion/public.d.ts"],
26-
"svelte/server": ["./src/server/index.js"],
26+
"svelte/server": ["./src/server/index.d.ts"],
2727
"svelte/store": ["./src/store/public.d.ts"],
2828
"#compiler": ["./src/compiler/types/index.d.ts"],
2929
"#client": ["./src/internal/client/types.d.ts"],

packages/svelte/types/index.d.ts

+32-7
Original file line numberDiff line numberDiff line change
@@ -350,25 +350,39 @@ declare module 'svelte' {
350350
* Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component
351351
*
352352
* */
353-
export function mount<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props, any, any>> | Component<Props, Exports, any>, options: {
353+
export function mount<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props, any, any>> | Component<Props, Exports, any>, options: {} extends Props ? {
354354
target: Document | Element | ShadowRoot;
355355
anchor?: Node | undefined;
356356
props?: Props | undefined;
357357
events?: Record<string, (e: any) => any> | undefined;
358358
context?: Map<any, any> | undefined;
359359
intro?: boolean | undefined;
360+
} : {
361+
target: Document | Element | ShadowRoot;
362+
props: Props;
363+
anchor?: Node | undefined;
364+
events?: Record<string, (e: any) => any> | undefined;
365+
context?: Map<any, any> | undefined;
366+
intro?: boolean | undefined;
360367
}): Exports;
361368
/**
362369
* Hydrates a component on the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component
363370
*
364371
* */
365-
export function hydrate<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props, any, any>> | Component<Props, Exports, any>, options: {
372+
export function hydrate<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props, any, any>> | Component<Props, Exports, any>, options: {} extends Props ? {
366373
target: Document | Element | ShadowRoot;
367374
props?: Props | undefined;
368375
events?: Record<string, (e: any) => any> | undefined;
369376
context?: Map<any, any> | undefined;
370377
intro?: boolean | undefined;
371378
recover?: boolean | undefined;
379+
} : {
380+
target: Document | Element | ShadowRoot;
381+
props: Props;
382+
events?: Record<string, (e: any) => any> | undefined;
383+
context?: Map<any, any> | undefined;
384+
intro?: boolean | undefined;
385+
recover?: boolean | undefined;
372386
}): Exports;
373387
/**
374388
* Unmounts a component that was previously mounted using `mount` or `hydrate`.
@@ -2110,14 +2124,25 @@ declare module 'svelte/reactivity' {
21102124
}
21112125

21122126
declare module 'svelte/server' {
2127+
import type { ComponentProps, Component, SvelteComponent, ComponentType } from 'svelte';
21132128
/**
21142129
* Only available on the server and when compiling with the `server` option.
21152130
* 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.
2116-
* */
2117-
export function render<Props extends Record<string, any>>(component: import("svelte").Component<Props, any, string> | import("svelte").ComponentType<import("svelte").SvelteComponent<Props, any, any>>, options: {
2118-
props: Omit<Props, "$$slots" | "$$events">;
2119-
context?: Map<any, any> | undefined;
2120-
}): RenderOutput;
2131+
*/
2132+
export function render<
2133+
Comp extends SvelteComponent<any> | Component<any>,
2134+
Props extends ComponentProps<Comp> = ComponentProps<Comp>
2135+
>(
2136+
...args: {} extends Props
2137+
? [
2138+
component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp,
2139+
options?: { props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }
2140+
]
2141+
: [
2142+
component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp,
2143+
options: { props: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }
2144+
]
2145+
): RenderOutput;
21212146
interface RenderOutput {
21222147
/** HTML that goes into the `<head>` */
21232148
head: string;

playgrounds/demo/src/entry-server.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
// @ts-ignore
21
import { render } from 'svelte/server';
32
// @ts-ignore you need to create this file
43
import App from './App.svelte';
54

6-
// @ts-ignore
7-
export const { head, body, css } = render(App, { props: { initialCount: 0 } });
5+
export const { head, body } = render(App);

0 commit comments

Comments
 (0)