sveltejs / svelte

Cybernetically enhanced web apps
https://svelte.dev
MIT License
79.15k stars 4.16k forks source link

Components pass proxy objects to external functions #12661

Closed sphinxc0re closed 2 months ago

sphinxc0re commented 2 months ago

Describe the bug

The boundary between Svelte 5 and a Svelte 4 components is unclear to me. In the reproduction below, I'm outlining this problem.

A Svelte 5 component in runes mode embeds a Svelte 4 component that doesn't have the tooling to work with runes (e.g. $state.snapshot). When the inner Svelte 4 component calls an external function, my expectation is, that this function receives a POJO. This would actually be my expectation regardless of what version the different Svelte components have.

In the reproduction below, the external function uses a structuredClone, which then crashes due to the input data being a Proxy object

Reproduction

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAAA5WRwW6DMAyGX8WKKrVIFdwprUShh-6wHnYsPTAwKxUkKAnbKsS7zyG0bJ00aYdItuM4__e7Y0VZoWL-sWM8rZH5LGwatmT62phEvWOlkXIlWpmZSqAyWTZ6k_BEl3UjpIaDPqOEQooa5q43ZK59OKcu6ssEVxryVKewhpnSqcZFB0rUqM8lf_PhmLAwDBO2hIRtt1sbRFFkgziObbDb7RJ2gt5JeOBNOnhgFXTmhx68oUaaa5GXRYk587VssV_eETNRN61GOXFe1HdG_By4rOyxmZQv7AcOrDfQGS7Pg_28hlwQxIQDpAWXsAf1gamc-LNKcMxj64IiSZluJeaRKS_MZGdl7ZJIF_xbP9X7v4kGA_65tm4k6--ru_niXpTZnDZn9KJCu8BR4syH11QSx9PL4dklGOIui-sPDOophKCecezNvtXD9ihtJG66h1H01ukDz1z9Jj_1X-hi_ou6AgAA

EDIT: Okay, I made another reproduction that might illustrate my problem a little better.

https://github.com/sphinxc0re/testing-svelte-5-bug

With this, it should be clear, that while I'm in Svelte 5 (runes) country when mounting the component, I have no choice but to pass in a full $state(...) object to keep reactivity even when rendering a Svelte 4 (no runes) component

Logs

No response

System Info

System:
    OS: Linux 6.9 Arch Linux
    CPU: (12) x64 Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
    Memory: 16.38 GB / 30.88 GB
    Container: Yes
    Shell: 5.9 - /usr/bin/zsh
  Binaries:
    Node: 20.9.0 - ~/.local/share/mise/installs/node/20.9.0/bin/node
    Yarn: 1.22.22 - ~/.local/share/mise/installs/yarn/1/bin/yarn
    npm: 10.1.0 - ~/.local/share/mise/installs/node/20.9.0/bin/npm
    pnpm: 8.15.8 - ~/.local/share/mise/installs/pnpm/8/bin/pnpm
  npmPackages:
    svelte: ^5.0.0-next.201 => 5.0.0-next.201

Severity

blocking an upgrade

Conduitry commented 2 months ago

It's the Svelte 5 component's responsibility to pass a non-proxied object to any external code that can't deal with proxies - whether that data is passed directly or indirectly. In the Svelte 5 component, you could do something like <Other data={$state.snapshot(data)} />.

paoloricciuti commented 2 months ago

Since you already have compute in a separate file you can make it a svelte.js file and use runes there.

This fixes your problem and you could even have a file that just does export an unstate function that calls $state.snapshot....i do wonder if such a function could be exported as an utility from svelte itself tho.

paoloricciuti commented 2 months ago

It's the Svelte 5 component's responsibility to pass a non-proxied object to any external code that can't deal with proxies - whether that data is passed directly or indirectly. In the Svelte 5 component, you could do something like <Other data={$state.snapshot(data)} />.

This is not entirely true tho since maybe you need the data to be reactive in the svelte 4 component and then the component may need to pass it to something that doesn't deal with proxies. But as i've commented above is technically still doable by extracting the function to a svelte.js file

brunnerh commented 2 months ago

The property should still be set on every change to data and in Svelte 4 components the reactivity is not based on proxies. I think this should just work.

sphinxc0re commented 2 months ago

Okay, I made another reproduction that might illustrate my problem a little better.

https://github.com/sphinxc0re/testing-svelte-5-bug

With this, it should be clear, that while I'm in Svelte 5 (runes) country when mounting the component, I have no choice but to pass in a full $state(...) object to keep reactivity even when rendering a Svelte 4 (no runes) component

dummdidumm commented 2 months ago

So your problem is that you want to mount something and update the component from outside? In that case I suggest to use createClassComponent(..) from svelte/legacy, which doesn't require you to use $state and doesn't create a proxy for the object.

paoloricciuti commented 2 months ago

Okay, I made another reproduction that might illustrate my problem a little better.

https://github.com/sphinxc0re/testing-svelte-5-bug

With this, it should be clear, that while I'm in Svelte 5 (runes) country when mounting the component, I have no choice but to pass in a full $state(...) object to keep reactivity even when rendering a Svelte 4 (no runes) component

Did you see my comment? You can make complexComputing.js into complexComputing.svelte.js and use $state.snapshot there...and you can call that function from a svelte 4 component perfectly fine.

sphinxc0re commented 2 months ago

@dummdidumm We actually thought about using this function, but we also thought it might be wiser not to use it in case it gets dropped in a future version of Svelte. Our specific problem is not only do we have a ton of components, but we also use the component API a lot. Are you suggesting createClassComponent is a valid start to a migration strategy?

@paoloricciuti I did see your comment, but unfortunately this is not an option 😞

paoloricciuti commented 2 months ago

@paoloricciuti I did see your comment, but unfortunately this is not an option 😞

Why?

sphinxc0re commented 2 months ago

@paoloricciuti It's out of my control. The code has to work as-is 🤷🏼‍♂️

dummdidumm commented 2 months ago

@sphinxc0re createClassComponent will be dropped when Svelte 4 syntax is dropped entirely, at which point you could not use your old components anyway. So yes, it's a valid migration strategy to use it in places where you run into problems like this.