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 <simon.holthausen@vercel.com>
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
nolyfill
Rich Harris 2 weeks ago committed by GitHub
parent 7295facb6c
commit 3da2646b10
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: more accurate `render`/`mount`/`hydrate` options

@ -30,7 +30,7 @@ await createBundle({
[`${pkg.name}/legacy`]: `${dir}/src/legacy/legacy-client.js`,
[`${pkg.name}/motion`]: `${dir}/src/motion/public.d.ts`,
[`${pkg.name}/reactivity`]: `${dir}/src/reactivity/index-client.js`,
[`${pkg.name}/server`]: `${dir}/src/server/index.js`,
[`${pkg.name}/server`]: `${dir}/src/server/index.d.ts`,
[`${pkg.name}/store`]: `${dir}/src/store/public.d.ts`,
[`${pkg.name}/transition`]: `${dir}/src/transition/public.d.ts`,
[`${pkg.name}/events`]: `${dir}/src/events/index.js`,

@ -72,13 +72,20 @@ export function slot(anchor, slot_fn, slot_props, fallback_fn) {
* @template {Record<string, any>} Props
* @template {Record<string, any>} Exports
* @param {import('../../index.js').ComponentType<import('../../index.js').SvelteComponent<Props>> | import('../../index.js').Component<Props, Exports, any>} component
* @param {{
* @param {{} extends Props ? {
* target: Document | Element | ShadowRoot;
* anchor?: Node;
* props?: Props;
* events?: Record<string, (e: any) => any>;
* context?: Map<any, any>;
* intro?: boolean;
* }: {
* target: Document | Element | ShadowRoot;
* props: Props;
* anchor?: Node;
* events?: Record<string, (e: any) => any>;
* context?: Map<any, any>;
* intro?: boolean;
* }} options
* @returns {Exports}
*/
@ -98,13 +105,20 @@ export function mount(component, options) {
* @template {Record<string, any>} Props
* @template {Record<string, any>} Exports
* @param {import('../../index.js').ComponentType<import('../../index.js').SvelteComponent<Props>> | import('../../index.js').Component<Props, Exports, any>} component
* @param {{
* @param {{} extends Props ? {
* target: Document | Element | ShadowRoot;
* props?: Props;
* events?: Record<string, (e: any) => any>;
* context?: Map<any, any>;
* intro?: boolean;
* recover?: boolean;
* } : {
* target: Document | Element | ShadowRoot;
* props: Props;
* events?: Record<string, (e: any) => any>;
* context?: Map<any, any>;
* intro?: boolean;
* recover?: boolean;
* }} options
* @returns {Exports}
*/

@ -97,10 +97,10 @@ export let on_destroy = [];
* 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> | import('svelte').ComponentType<import('svelte').SvelteComponent<Props>>} component
* @param {{ props: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }} options
* @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }} [options]
* @returns {import('#server').RenderOutput}
*/
export function render(component, options) {
export function render(component, options = {}) {
const payload = create_payload();
const prev_on_destroy = on_destroy;

@ -0,0 +1,21 @@
import type { RenderOutput } from '#server';
import type { ComponentProps, Component, SvelteComponent, ComponentType } from 'svelte';
/**
* 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.
*/
export function render<
Comp extends SvelteComponent<any> | Component<any>,
Props extends ComponentProps<Comp> = ComponentProps<Comp>
>(
...args: {} extends Props
? [
component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp,
options?: { props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }
]
: [
component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp,
options: { props: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }
]
): RenderOutput;

@ -131,6 +131,13 @@ mount(NewComponent, {
intro: false,
recover: false
});
mount(
NewComponent,
// @ts-expect-error props missing
{ target: null as any }
);
// if component receives no args, props can be omitted
mount(null as any as typeof SvelteComponent<{}>, { target: null as any });
hydrate(NewComponent, {
target: null as any as Document | Element | ShadowRoot,
@ -148,6 +155,13 @@ hydrate(NewComponent, {
intro: false,
recover: false
});
hydrate(
NewComponent,
// @ts-expect-error props missing
{ target: null as any }
);
// if component receives no args, props can be omitted
hydrate(null as any as typeof SvelteComponent<{}>, { target: null as any });
render(NewComponent, {
props: {
@ -156,6 +170,14 @@ render(NewComponent, {
x: ''
}
});
// @ts-expect-error
render(NewComponent);
render(NewComponent, {
props: {
// @ts-expect-error
prop: 1
}
});
// --------------------------------------------------------------------------- interop
@ -255,6 +277,13 @@ mount(functionComponent, {
readonly: 1
}
});
mount(
functionComponent,
// @ts-expect-error props missing
{ target: null as any }
);
// if component receives no args, props can be omitted
mount(null as any as Component<{}>, { target: null as any });
hydrate(functionComponent, {
target: null as any as Document | Element | ShadowRoot,
@ -272,13 +301,33 @@ hydrate(functionComponent, {
binding: true
}
});
hydrate(
functionComponent,
// @ts-expect-error props missing
{ target: null as any }
);
// if component receives no args, props can be omitted
hydrate(null as any as Component<{}>, { target: null as any });
render(functionComponent, {
props: {
binding: true,
readonly: 'foo',
readonly: 'foo'
}
});
// @ts-expect-error
render(functionComponent);
render(functionComponent, {
// @ts-expect-error
props: {
binding: true
}
});
render(functionComponent, {
props: {
binding: true,
// @ts-expect-error
x: ''
readonly: 1
}
});

@ -23,7 +23,7 @@
"svelte/internal/client": ["./src/internal/client/index.js"],
"svelte/legacy": ["./src/legacy/legacy-client.js"],
"svelte/motion": ["./src/motion/public.d.ts"],
"svelte/server": ["./src/server/index.js"],
"svelte/server": ["./src/server/index.d.ts"],
"svelte/store": ["./src/store/public.d.ts"],
"#compiler": ["./src/compiler/types/index.d.ts"],
"#client": ["./src/internal/client/types.d.ts"],

@ -350,25 +350,39 @@ declare module 'svelte' {
* Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component
*
* */
export function mount<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props, any, any>> | Component<Props, Exports, any>, options: {
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 ? {
target: Document | Element | ShadowRoot;
anchor?: Node | undefined;
props?: Props | undefined;
events?: Record<string, (e: any) => any> | undefined;
context?: Map<any, any> | undefined;
intro?: boolean | undefined;
} : {
target: Document | Element | ShadowRoot;
props: Props;
anchor?: Node | undefined;
events?: Record<string, (e: any) => any> | undefined;
context?: Map<any, any> | undefined;
intro?: boolean | undefined;
}): Exports;
/**
* Hydrates a component on the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component
*
* */
export function hydrate<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props, any, any>> | Component<Props, Exports, any>, options: {
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 ? {
target: Document | Element | ShadowRoot;
props?: Props | undefined;
events?: Record<string, (e: any) => any> | undefined;
context?: Map<any, any> | undefined;
intro?: boolean | undefined;
recover?: boolean | undefined;
} : {
target: Document | Element | ShadowRoot;
props: Props;
events?: Record<string, (e: any) => any> | undefined;
context?: Map<any, any> | undefined;
intro?: boolean | undefined;
recover?: boolean | undefined;
}): Exports;
/**
* Unmounts a component that was previously mounted using `mount` or `hydrate`.
@ -2110,14 +2124,25 @@ declare module 'svelte/reactivity' {
}
declare module 'svelte/server' {
import type { ComponentProps, Component, SvelteComponent, ComponentType } from 'svelte';
/**
* 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.
* */
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: {
props: Omit<Props, "$$slots" | "$$events">;
context?: Map<any, any> | undefined;
}): RenderOutput;
*/
export function render<
Comp extends SvelteComponent<any> | Component<any>,
Props extends ComponentProps<Comp> = ComponentProps<Comp>
>(
...args: {} extends Props
? [
component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp,
options?: { props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }
]
: [
component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp,
options: { props: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }
]
): RenderOutput;
interface RenderOutput {
/** HTML that goes into the `<head>` */
head: string;

@ -1,7 +1,5 @@
// @ts-ignore
import { render } from 'svelte/server';
// @ts-ignore you need to create this file
import App from './App.svelte';
// @ts-ignore
export const { head, body, css } = render(App, { props: { initialCount: 0 } });
export const { head, body } = render(App);

Loading…
Cancel
Save