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

fix(vanilla): improve snapshot creation ignoring object setters in prototype #656

Merged
merged 5 commits into from
Feb 7, 2023

Conversation

dai-shi
Copy link
Member

@dai-shi dai-shi commented Feb 5, 2023

ref: #653 (comment)

  • impl
  • tests
  • update proxy-compare

@vercel
Copy link

vercel bot commented Feb 5, 2023

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated
valtio ✅ Ready (Inspect) Visit Preview 💬 Add your feedback Feb 7, 2023 at 2:17PM (UTC)

@codesandbox-ci
Copy link

codesandbox-ci bot commented Feb 5, 2023

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 97707c3:

Sandbox Source
React Configuration
React Typescript Configuration
React Browserify Configuration
React Snowpack Configuration
React Parcel Configuration

@dai-shi
Copy link
Member Author

dai-shi commented Feb 5, 2023

@stephenh how do you like this?
The test is failing, and I found a subtle bug in proxy-compare.

@github-actions
Copy link

github-actions bot commented Feb 5, 2023

Size Change: +187 B (0%)

Total Size: 55.6 kB

Filename Size Change
dist/esm/vanilla.js 2.32 kB +35 B (+2%)
dist/system/vanilla.development.js 2.46 kB +35 B (+1%)
dist/system/vanilla.production.js 1.5 kB +38 B (+3%)
dist/umd/vanilla.development.js 2.63 kB +31 B (+1%)
dist/umd/vanilla.production.js 1.55 kB +18 B (+1%)
dist/vanilla.js 2.52 kB +30 B (+1%)
ℹ️ View Unchanged
Filename Size
dist/esm/index.js 62 B
dist/esm/macro.js 698 B
dist/esm/macro/vite.js 864 B
dist/esm/react.js 710 B
dist/esm/react/utils.js 221 B
dist/esm/utils.js 68 B
dist/esm/vanilla/utils.js 4.21 kB
dist/index.js 232 B
dist/macro.js 937 B
dist/macro/vite.js 1.09 kB
dist/react.js 652 B
dist/react/utils.js 237 B
dist/system/index.development.js 236 B
dist/system/index.production.js 170 B
dist/system/macro.development.js 779 B
dist/system/macro.production.js 556 B
dist/system/macro/vite.development.js 951 B
dist/system/macro/vite.production.js 660 B
dist/system/react.development.js 851 B
dist/system/react.production.js 466 B
dist/system/react/utils.development.js 316 B
dist/system/react/utils.production.js 221 B
dist/system/utils.development.js 241 B
dist/system/utils.production.js 176 B
dist/system/vanilla/utils.development.js 4.43 kB
dist/system/vanilla/utils.production.js 2.84 kB
dist/umd/index.development.js 372 B
dist/umd/index.production.js 322 B
dist/umd/macro.development.js 1.05 kB
dist/umd/macro.production.js 724 B
dist/umd/macro/vite.development.js 1.23 kB
dist/umd/macro/vite.production.js 882 B
dist/umd/react.development.js 797 B
dist/umd/react.production.js 519 B
dist/umd/react/utils.development.js 396 B
dist/umd/react/utils.production.js 297 B
dist/umd/utils.development.js 386 B
dist/umd/utils.production.js 333 B
dist/umd/vanilla/utils.development.js 4.7 kB
dist/umd/vanilla/utils.production.js 2.92 kB
dist/utils.js 236 B
dist/vanilla/utils.js 4.54 kB

compressed-size-action

@@ -108,30 +108,35 @@ const buildProxyFunction = (
markToTrack(snap, true) // mark to track
snapCache.set(target, [version, snap])
Reflect.ownKeys(target).forEach((key) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dai-shi this PR in general looks great!

I copy/pasted in the tests from #653 to see what happens, and what I'm observing is that:

  1. For object getters, this PR is actually not necessary, b/c new snapshots have a pure/empty prototype, so the original snap[key] = value line finds nothing on Object.prototype, and so does a regular storage of double: 2 directly on snap (no invocation of the proxy's object setter).

  2. For class getters, the snapshot comes in with Counter.prototype and so snap[key] = value would get routed to the Counter.double setter method, and so this PR avoids that, and so yeah would define double: 2 directly on snap.

However, the reason I say "would" is that Reflect.ownKeys still only sees count as the field it should copy over. If I do a super-hacky:

    ;[...Reflect.ownKeys(target), 'doubled'].forEach((key) => {

On this line, then this loops sees doubled, and so defineProperty(snap, doubled, 4) runs, and then we end up with the same behavior of object getters, the snap.doubled is the primitive 4 value.

So, that's what I've observed. I'll defer to you on whether you think this PR as-is is still an improvement/simplification (even though afaict it doesn't behaviorally change either object or class getter behavior, which could be fine!), or if you want to nudge it further to pick up doubled (and align the object/class getter behavior).

Thanks!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR itself doesn't change the behavior. My hope is it would make withClass util possible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My hope is it would make withClass util possible.

Gotcha. Fwiw I think proxyWithClass is what I would prefer b/c it handles the case of "author has some books" and wanting all authors + books to get the same modified behavior.

With withClass, I think the caller would have to withClass each of the books. I guess withClass could recursively withClass its contents, but then as new books are added (author.books.push(new Book()) I think withClass would be missed.

So, in a good way, I think proxyWithClass is a cleaner/easier way to ensure the upgraded behavior is gotten by the entire proxy tree.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with proxyWithClass. Even so, it can be implemented easily, without unstable_buildProxyFunction, if this PR is merged, or not? I didn't confirm. Oh, I see. You want it recursively. Then, this PR might not help.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You want it recursively. Then, this PR might not help.

Right, although it would help the proxyWithClass's custom createSnapshot be closer to the real createSnapshot (the main change just being the ownKeys line), so if you wanted to merge it anyway / think this is a good improvement, I think that'd be great.

But also np to close out, but still great to see, as I'll probably use this PR's createSnapshot in the proxyWithClass's version.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(the main change just being the ownKeys line)

Okay, it's a bit unfortunate for my goal, but yeah, I will merge it anyway.
Maybe there's another way to avoid overriding createSnapshot? don't know yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants