Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prop state on first instance of a custom element are lost when using HMR #21

Closed
patricknelson opened this issue Nov 9, 2023 · 1 comment
Assignees
Labels
bug Something isn't working

Comments

@patricknelson
Copy link
Owner

patricknelson commented Nov 9, 2023

Describe the bug

When using HMR (i.e. vite in development mode), the first instance of any particular svelte-retag loaded component loses state after the first save. This is likely due to an issue with the props proxy which was setup for lit style lowercase props here: https://github.com/patricknelson/svelte-retag/blob/1.3.1/index.js#L430

Likely a regression from #18.

Reproduction

  1. Setup a component that takes a prop (such as link="https://example.com") called ExampleComponent.svelte and ensure link prop is used somehow in the component
  2. Define a custom element for it in svelte retag (e.g. example-element)
  3. Setup page with <example-element link="https://example.com">
  4. Start up yarn vite to start up dev mode and load page setup in step above
  5. Edit ExampleComponent.svelte

Bug appears when link is now empty.

Logs

No response

System Info

System:
    OS: Linux 5.15 Debian GNU/Linux 11 (bullseye) 11 (bullseye)
    CPU: (16) x64 Intel(R) Core(TM) i7-10875H CPU @ 2.30GHz
    Memory: 3.19 GB / 5.79 GB
    Container: Yes
    Shell: 5.1.4 - /bin/bash
  Binaries:
    Node: 16.14.2 - ~/.nvm/versions/node/v16.14.2/bin/node
    Yarn: 1.22.19 - ~/.nvm/versions/node/v16.14.2/bin/yarn
    npm: 8.5.0 - ~/.nvm/versions/node/v16.14.2/bin/npm
  Browsers:
    Chrome: 119.0.6045.105
  npmPackages:
    svelte: ^4.2.2 => 4.2.2
    svelte-retag: 1.3.0 => 1.3.0

Severity

annoyance

@patricknelson patricknelson added the bug Something isn't working label Nov 9, 2023
@patricknelson patricknelson self-assigned this Nov 9, 2023
@patricknelson patricknelson changed the title Bug in prop cache when using hot module reloading Prop state on first instance of a custom element are lost when using HMR Nov 9, 2023
patricknelson added a commit that referenced this issue Nov 17, 2023
…fter HMR updates and that we persist props the same way that normal HMR does when done entirely within Svelte (i.e. restore defaults after HMR only if prop is undefined).
patricknelson added a commit that referenced this issue Nov 17, 2023
…fter HMR updates and that we persist props the same way that normal HMR does when done entirely within Svelte (i.e. restore defaults after HMR only if prop is undefined).
@patricknelson
Copy link
Owner Author

patricknelson commented Nov 17, 2023

Root cause analysis

Background

I wanna get a few concepts and definitions out of the way:

  1. Initial state: The state both your Svelte & web components start out with
  2. Evolved state: The state your svelte component has developed over time (this is primarily with regard to the state of prop values, like the Counter.svelte in this example). The count prop has an initial value, but can also be modified over time (e.g. incrementing)
  3. "Attribute" definition: Always in reference to the attribute on the custom element itself (not on the Svelte component)
  4. "Prop" definition: Always in reference to the props on the Svelte component itself (not on the custom element)

When using Vite HMR + Svelte on its own: Editing a Svelte component results in the component being reloaded via HMR, however there are differences when it comes to maintaining state. For example, using Counter.svelte here as an example:

  • Prop count is unset: If you invoke <Counter /> inside another component without any props, the props will reset to their default state after an HMR update (e.g. count always resets to 0 in the case of Counter.svelte after every single HMR update)
  • Prop count is set: If you invoke <Counter count="1" /> inside another component where count is set to 1, HMR updates don't necessarily affect count. In fact, even if you click the counter and count increments to 99 within the scope of that component instance, but you add a console.log() statement somewhere in the file, that "evolved state" is retained. That is, it stays 99 and is not reset to 0 nor 1.

The Bug 🐞

Before fixing in #22, the bug in svelte-retag was that even if you did have the attribute set in the HTML like <counter-tag count="1">, the value of count inside of the Svelte component would still reset to the default prop state of 0. Due to how Svelte normally works (with props instead of attributes, in this case) this is unexpected. However, this only affected the first <counter-tag> and not subsequent ones in the HTML.

Cause: This occurred because the Proxy (setup here out of necessity to support context in #18) wasn't also updating the target props object with the newly discovered props (which it automatically infers on get). That's an issue because svelte-hmr's createProxiedComponent() needs to enumerate the full set of props (via the Object.assign()):

https://github.com/sveltejs/svelte-hmr/blob/02fa71cdaf35b03c18d81c4a20ac07447f148fd1/packages/svelte-hmr/runtime/svelte-hooks.js#L139-L154

The subsequent instance of <counter-tag> (for example) worked fine because those props were already cached and primed into the props array, so later HMR updates were handled and reconstituted back into the swapped/replaced component.

The Fix 🔧

The fix was simply to add each prop to the target props object on access in the Proxy so that createProxiedComponent() (snippet above) sees it when it gets enumerated like so:

return target[propName] = attribValue.value;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant