-
Notifications
You must be signed in to change notification settings - Fork 192
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
[BUGFIX release] Ensure hash
properties are reified lazily
#1298
Conversation
`(hash)` previously had two different types of behavior, one where it was used as a reference and could get individual properties lazily, and one where it was fully reified and exposed in JS. The lazy nature of hash was very useful as it prevented having to realize the full cost of each reference immediately, and it also meant that some values could be accessed in conditionals and run assertions just in time. For example, if someone was using a component that was loaded lazily via an engine, you could render the component on a hash within a conditional and it would not cause an error (because the component did not exist): ```hbs <SomeComponent as |api|> <api.eagerComponent/> {{#if this.displayLazy}} <api.lazyComponent/> {{/if}} </SomeComponent> ``` This PR restores this ability by having `(hash)` return a proxy rather than an object, which allows us to access each individual value without reifying the references immediately. For backwards compatibility, we also allow users to set values on the proxy, so long as they are not setting any of the values defined on the proxy initially, since those are read-only references.
5ce55ac
to
4732b3f
Compare
The assertion added here is causing an addon test to fail, when using Ember canary where this change here just landed. I don't really understand the reasoning here, at least from a user's perspective. Why shouldn't I be able to change a property of the hash even when it existed before? In my mental model using Even if there is some good reason to do this, this behavior must be documented, and changing it feels very much like a breaking change, which the failing test basically confirms. Not? |
@simonihmig it would be very, very difficult to make this behavior work the way you described. It's really non-trivial, and introduces a lot of issues with timing and syncing the local state of the hash with your changes, to the state of the incoming references that defined the hash in the first place. We wanted to avoid these extra complexities, which would likely introduce a host of their own small bugs and inconsistencies in our opinion. The hope was that users were treating If we do make the change, it is likely we will immediately deprecate the ability to do this, since it'll be much easier and simpler to maintain a |
I see, thanks for explaining. So practically I myself am not really affected by this, as we don't have used patterns like this. But this case was added to our test suite because of a real use case by @jelhan IIRC, right? |
FWIW, if this is failing tests already in canary then I think we should at least re-evaluate our original decision. I'm going to discuss it with folks next week, some are currently out, we'll see how that goes before we do any backports or anything like that. |
hash
properties are reified lazilyhash
properties are reified lazily
My mental modal about // app/helpers/hash.js
import { helper } from '@ember/component/helper';
function hash(_, namedArgs) {
return { ...namedArgs };
}
export default helper(hash); I have used it whenever it improved readability to construct an object in the template rather than in the JavaScript file. Basically I considered that either constructing the object in a backing JavaScript class or using // app/components/my-component.js
import Component from '@glimmer/component';
export default class MyComponent extends Component {
get formModel() {
return {};
}
} <PostForm @model={{this.formModel}} /> vs <PostForm @model={{hash}} /> I often preferred the My mental modal about Speaking in the past here because this issue and the discussion changed my mental model. I wasn't aware that The impact on the apps I'm working on may not be that big though. Often the It feels like a breaking change for me. Especially as the API documentation does not mention that mutating the returned object is discouraged. To be honest it sounds for more like a chance, which deserves a RFC. |
With set on hash it should throw error: https://ember-twiddle.com/7ede4a6b8d3ce50698acc9d7fe57ebaa?openFiles=helpers.h%5C.js%2C (replace
with
|
As we have no control of making this test pass (it's either not supported in Ember, or a bug that needs to be fixed upstream, depending on the outcome of glimmerjs/glimmer-vm#1298), we skip this test for now to make CI green again.
As we have no control of making this test pass (it's either not supported in Ember, or a bug that needs to be fixed upstream, depending on the outcome of glimmerjs/glimmer-vm#1298), we skip this test for now to make CI green again.
@jelhan to be fair, we were also not aware that I think it's likely we'll have to keep the old behavior since things are failing, but in general I think you should reconsider the strategy with using
Given that, I think this pattern will have to be re-evaluated either way. If this pattern were to continue in the future, I think we'd have to have |
(hash)
previously had two different types of behavior, one where itwas used as a reference and could get individual properties lazily, and
one where it was fully reified and exposed in JS. The lazy nature of
hash was very useful as it prevented having to realize the full cost of
each reference immediately, and it also meant that some values could
be accessed in conditionals and run assertions just in time. For
example, if someone was using a component that was loaded lazily via
an engine, you could render the component on a hash within a conditional
and it would not cause an error (because the component did not exist):
This PR restores this ability by having
(hash)
return a proxy ratherthan an object, which allows us to access each individual value without
reifying the references immediately. For backwards compatibility, we
also allow users to set values on the proxy, so long as they are not
setting any of the values defined on the proxy initially, since those
are read-only references.