Open
Description
Describe the bug
Problem
When providing an async function to @attach
, svelte-check throws a type error:
Promise<void>; autoplay: true; playsinline: true; }' is not assignable to parameter of type 'HTMLProps<"video", HTMLAttributes<any>>'.
Type '{ [x: symbol]: (element: HTMLVideoElement) => Promise<void>; autoplay: true; playsinline: true; }' is not assignable to type 'Omit<HTMLVideoAttributes, never>'.
'symbol' index signatures are incompatible.
Type '(element: HTMLVideoElement) => Promise<void>' is not assignable to type 'false | Attachment<HTMLVideoElement> | null | undefined'.
Type '(element: HTMLVideoElement) => Promise<void>' is not assignable to type 'Attachment<HTMLVideoElement>'.
Type 'Promise<void>' is not assignable to type 'void | (() => void)'. (ts)
The async function is being consumed as:
<video {@attach init} autoplay={true} playsinline={true}></video>
This is due to @attach
expecting functions that do not return a Promise. My understanding is @attach
runs $effect
, and $effect
cannot track dependencies within async tasks ($effect
: Understanding dependencies).
Workaround
A workaround I found is passing a wrapper function such as {@attach function(el) { init(el) }}
since:
- There are no longer type errors because we're passing a function definition that returns
void
and not a promise. - Reactive dependencies can be tracked, such as parameters passed to
init
.
This is a bug or 'working as intended' depending on interpretation.
Proposals
I suggest:
- Add callout to
@attach
docs, mentioning async functions will not have their dependencies tracked in the@attach
clause. - Update types to no longer throw during
svelte-check
. - Show suppressible warning whenever async functions are passed, reminding developers those methods won't track dependencies.
Alternative proposal:
- Maintain current behaviour.
- Provide clear alternatives alongside the type error, such as providing a wrapper method like I did in my workaround above.
Original thread on Discord: https://discord.com/channels/457912077277855764/1382427644448215080
Reproduction
Demo: https://stackblitz.com/edit/svelte-async-attach?file=src%2Flib%2FVideo.svelte
Video.svelte (the relevant parts)
<script lang="ts">
let stream
let error = $state('')
async function init(element: HTMLVideoElement) {
console.log('init', element)
try {
stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
element.srcObject = stream;
} catch (err) {
if (err instanceof Error) {
error = `Error accessing camera: ${err.name} - ${err.message}. Please ensure permissions are granted and you are on HTTPS or localhost.`;
} else {
error = 'Error accessing camera.';
}
}
}
</script>
<!--
svelte-ignore a11y_media_has_caption
-->
<video {@attach init} autoplay={true} playsinline={true}></video>
<p>{error}</p>
- Load demo
- Observe no runtime error
- Run
npm run check
- Observe type error:
Promise<void>; autoplay: true; playsinline: true; }' is not assignable to parameter of type 'HTMLProps<"video", HTMLAttributes<any>>'. Type '{ [x: symbol]: (element: HTMLVideoElement) => Promise<void>; autoplay: true; playsinline: true; }' is not assignable to type 'Omit<HTMLVideoAttributes, never>'. 'symbol' index signatures are incompatible. Type '(element: HTMLVideoElement) => Promise<void>' is not assignable to type 'false | Attachment<HTMLVideoElement> | null | undefined'. Type '(element: HTMLVideoElement) => Promise<void>' is not assignable to type 'Attachment<HTMLVideoElement>'. Type 'Promise<void>' is not assignable to type 'void | (() => void)'. (ts)
System Info
System:
OS: Linux 5.0 undefined
CPU: (4) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
Memory: 0 Bytes / 0 Bytes
Shell: 1.0 - /bin/jsh
Binaries:
Node: 20.19.1 - /usr/local/bin/node
Yarn: 1.22.19 - /usr/local/bin/yarn
npm: 10.8.2 - /usr/local/bin/npm
pnpm: 8.15.6 - /usr/local/bin/pnpm
npmPackages:
svelte: ^5.25.0 => 5.33.19
Severity
annoyance