Skip to content

Commit 350c6c3

Browse files
chrskerrbaseballyamadummdidumm
authored
breaking: update onMount type definition to prevent async function return (#8136)
--------- Co-authored-by: Yuichiro Yamashita <[email protected]> Co-authored-by: Simon H <[email protected]>
1 parent 662804e commit 350c6c3

File tree

5 files changed

+120
-59
lines changed

5 files changed

+120
-59
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* **breaking** Minimum supported TypeScript version is now 5 (it will likely work with lower versions, but we make no guarantess about that)
77
* **breaking** Stricter types for `createEventDispatcher` (see PR for migration instructions) ([#7224](https://github.com/sveltejs/svelte/pull/7224))
88
* **breaking** Stricter types for `Action` and `ActionReturn` (see PR for migration instructions) ([#7224](https://github.com/sveltejs/svelte/pull/7224))
9+
* **breaking** Stricter types for `onMount` - now throws a type error when returning a function asynchronously to catch potential mistakes around callback functions (see PR for migration instructions) ([#8136](https://github.com/sveltejs/svelte/pull/8136))
910
* Add `a11y no-noninteractive-element-interactions` rule ([#8391](https://github.com/sveltejs/svelte/pull/8391))
1011
* Add `a11y-no-static-element-interactions`rule ([#8251](https://github.com/sveltejs/svelte/pull/8251))
1112
* Bind `null` option and input values consistently ([#8312](https://github.com/sveltejs/svelte/issues/8312))

src/runtime/internal/lifecycle.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@ export function beforeUpdate(fn: () => any) {
2727
* It must be called during the component's initialisation (but doesn't need to live *inside* the component;
2828
* it can be called from an external module).
2929
*
30+
* If a function is returned _synchronously_ from `onMount`, it will be called when the component is unmounted.
31+
*
3032
* `onMount` does not run inside a [server-side component](/docs#run-time-server-side-component-api).
3133
*
3234
* https://svelte.dev/docs#run-time-svelte-onmount
3335
*/
34-
export function onMount(fn: () => any) {
36+
export function onMount<T>(fn: () => T extends Promise<() => any> ? "Returning a function asynchronously from onMount won't call that function on destroy" : T): void {
3537
get_current_component().$$.on_mount.push(fn);
3638
}
3739

test/types/actions.ts

+54-54
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ import type { Action, ActionReturn } from '$runtime/action';
33
// ---------------- Action
44

55
const href: Action<HTMLAnchorElement> = (node) => {
6-
node.href = '';
7-
// @ts-expect-error
8-
node.href = 1;
6+
node.href = '';
7+
// @ts-expect-error
8+
node.href = 1;
99
};
1010
href;
1111

1212
const required: Action<HTMLElement, boolean> = (node, param) => {
13-
node;
14-
param;
13+
node;
14+
param;
1515
};
1616
required(null as any, true);
1717
// @ts-expect-error (only in strict mode) boolean missing
@@ -20,74 +20,74 @@ required(null as any);
2020
required(null as any, 'string');
2121

2222
const required1: Action<HTMLElement, boolean> = (node, param) => {
23-
node;
24-
param;
25-
return {
26-
update: (p) => p === true,
27-
destroy: () => {}
28-
};
23+
node;
24+
param;
25+
return {
26+
update: (p) => p === true,
27+
destroy: () => {}
28+
};
2929
};
3030
required1;
3131

3232
const required2: Action<HTMLElement, boolean> = (node) => {
33-
node;
33+
node;
3434
};
3535
required2;
3636

3737
const required3: Action<HTMLElement, boolean> = (node, param) => {
38-
node;
39-
param;
40-
return {
41-
// @ts-expect-error comparison always resolves to false
42-
update: (p) => p === 'd',
43-
destroy: () => {}
44-
};
38+
node;
39+
param;
40+
return {
41+
// @ts-expect-error comparison always resolves to false
42+
update: (p) => p === 'd',
43+
destroy: () => {}
44+
};
4545
};
4646
required3;
4747

4848
const optional: Action<HTMLElement, boolean | undefined> = (node, param?) => {
49-
node;
50-
param;
49+
node;
50+
param;
5151
};
5252
optional(null as any, true);
5353
optional(null as any);
5454
// @ts-expect-error no boolean
5555
optional(null as any, 'string');
5656

5757
const optional1: Action<HTMLElement, boolean | undefined> = (node, param?) => {
58-
node;
59-
param;
60-
return {
61-
update: (p) => p === true,
62-
destroy: () => {}
63-
};
58+
node;
59+
param;
60+
return {
61+
update: (p) => p === true,
62+
destroy: () => {}
63+
};
6464
};
6565
optional1;
6666

6767
const optional2: Action<HTMLElement, boolean | undefined> = (node) => {
68-
node;
68+
node;
6969
};
7070
optional2;
7171

7272
const optional3: Action<HTMLElement, boolean | undefined> = (node, param) => {
73-
node;
74-
param;
73+
node;
74+
param;
7575
};
7676
optional3;
7777

7878
const optional4: Action<HTMLElement, boolean | undefined> = (node, param?) => {
79-
node;
80-
param;
81-
return {
82-
// @ts-expect-error comparison always resolves to false
83-
update: (p) => p === 'd',
84-
destroy: () => {}
85-
};
79+
node;
80+
param;
81+
return {
82+
// @ts-expect-error comparison always resolves to false
83+
update: (p) => p === 'd',
84+
destroy: () => {}
85+
};
8686
};
8787
optional4;
8888

8989
const no: Action<HTMLElement, never> = (node) => {
90-
node;
90+
node;
9191
};
9292
// @ts-expect-error second param
9393
no(null as any, true);
@@ -96,10 +96,10 @@ no(null as any);
9696
no(null as any, 'string');
9797

9898
const no1: Action<HTMLElement, never> = (node) => {
99-
node;
100-
return {
101-
destroy: () => {}
102-
};
99+
node;
100+
return {
101+
destroy: () => {}
102+
};
103103
};
104104
no1;
105105

@@ -113,32 +113,32 @@ no3;
113113

114114
// @ts-expect-error update method given
115115
const no4: Action<HTMLElement, never> = (node) => {
116-
return {
117-
update: () => {},
118-
destroy: () => {}
119-
};
116+
return {
117+
update: () => {},
118+
destroy: () => {}
119+
};
120120
};
121121
no4;
122122

123123
// ---------------- ActionReturn
124124

125125
const requiredReturn: ActionReturn<string> = {
126-
update: (p) => p.toString()
126+
update: (p) => p.toString()
127127
};
128128
requiredReturn;
129129

130130
const optionalReturn: ActionReturn<boolean | undefined> = {
131-
update: (p) => {
132-
p === true;
133-
// @ts-expect-error could be undefined
134-
p.toString();
135-
}
131+
update: (p) => {
132+
p === true;
133+
// @ts-expect-error could be undefined
134+
p.toString();
135+
}
136136
};
137137
optionalReturn;
138138

139139
const invalidProperty: ActionReturn = {
140-
// @ts-expect-error invalid property
141-
invalid: () => {}
140+
// @ts-expect-error invalid property
141+
invalid: () => {}
142142
};
143143
invalidProperty;
144144

test/types/create-event-dispatcher.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { createEventDispatcher } from '$runtime/internal/lifecycle';
22

33
const dispatch = createEventDispatcher<{
4-
loaded: never
5-
change: string
6-
valid: boolean
7-
optional: number | null
4+
loaded: never
5+
change: string
6+
valid: boolean
7+
optional: number | null
88
}>();
99

1010
// @ts-expect-error: dispatch invalid event

test/types/on-mount.ts

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { onMount } from '$runtime/index';
2+
3+
// sync and no return
4+
onMount(() => {
5+
console.log('mounted');
6+
});
7+
8+
// sync and return value
9+
onMount(() => {
10+
return 'done';
11+
});
12+
13+
// sync and return sync
14+
onMount(() => {
15+
return () => {
16+
return 'done';
17+
};
18+
});
19+
20+
// sync and return async
21+
onMount(() => {
22+
return async () => {
23+
const res = await fetch('');
24+
return res;
25+
};
26+
});
27+
28+
// async and no return
29+
onMount(async () => {
30+
await fetch('');
31+
});
32+
33+
// async and return value
34+
onMount(async () => {
35+
const res = await fetch('');
36+
return res;
37+
});
38+
39+
// @ts-expect-error async and return sync
40+
onMount(async () => {
41+
return () => {
42+
return 'done';
43+
};
44+
});
45+
46+
// @ts-expect-error async and return async
47+
onMount(async () => {
48+
return async () => {
49+
const res = await fetch('');
50+
return res;
51+
};
52+
});
53+
54+
// @ts-expect-error async and return any
55+
onMount(async () => {
56+
const a: any = null as any;
57+
return a;
58+
});

0 commit comments

Comments
 (0)