Conversation
BREAKING CHANGE: The addon no longer supports Babel 6 Closes ember-decorators#75
688c93e to
44f78b6
Compare
44f78b6 to
722ade0
Compare
722ade0 to
da90b9b
Compare
config/ember-try.js
Outdated
| devDependencies: { | ||
| '@ember/jquery': '^0.5.1', | ||
| 'ember-source': '~2.18.0' | ||
| 'ember-source': '~3.4.0' |
There was a problem hiding this comment.
3.4.0 selected as the minimum Ember version, because the new constructor/init behavior can be polyfilled back to that version
| // Skipped because the native getter/setter in the parent class is currently | ||
| // clobbered by the definition of the instance property in the constructor | ||
| // of the base class | ||
| skip('typed value can be provided by getter/setter', function(assert) { |
There was a problem hiding this comment.
I added the context above, but want to call it out explicitly here, too:
I wrestled with this for a few hours to try to understand what's going on and why it didn't work. I think it comes down to this sequence of events:
@argument('number') prophas the behavior of runningObject.definePropertyat the end of the constructor- The
Object.definePropertydoes not invoke the parent classgetterorsetter; the property is defined, not set. Because of this, the nativegetterandsetterare entirely ignored propis interpreted, during initial validation, to beundefinedand thus fails the check
This has nothing to do with the code in this library; I validated all of this behavior in a fresh Babel project to understand what is happening.
I think the best course of action is just to not support this use-case at all, because anyone that has a getter and a property initializer is going to run into this same problem, @argument or not. I'm not sure what we could do to attempt to avoid the problem, but would love to re-enable this test at some point if we can figure out the right way to go about it. I left the test as a skip as an explicit reminder of this behavior.
- Convert `@argument` decorator to perform type validation - Remove all other arguments - Remove implicit injection of validation logic into base Ember classes - Remove all configuration options
566e7fa to
b5583f9
Compare
|
Test failures seem to be because of a difference between how my local version of Chrome and the version on Travis read the class name after extending it with the validation logic. I'll look into making the assertions more flexible (or more consistently reporting the class name). |
I don't think it makes sense to consider this part of the public, importable API. We can always choose to make it "public" later on if users seem to have a need for it
bebde3f to
ab8252b
Compare
Rather than installing the polyfill for everyone, I think it's better to treat it like a peer dependency and just document that it needs to be present for Ember versions below 3.6.
acf8857 to
d0230af
Compare
pzuraq
left a comment
There was a problem hiding this comment.
Overall this is looking great! I added some general comments. My last question is, do we want to allow users who don't want to deal with types to use @argument just to declare what argument values exist?
class FooComponent extends Component {
@argument foo;
}I think this would be nice for folks who want simpler validations. This could definitely be added after v1 though.
|
|
||
| // Note: order is important to ensure that a subclass cannot override | ||
| // the type of an argument | ||
| return { ...klassValidations, ...prototypeValidations }; |
There was a problem hiding this comment.
Should subclasses be able to narrow types?
class Foo {
@argument('any') foo;
}
class Bar extends Foo {
@argument('string') foo;
}There was a problem hiding this comment.
Hmm... that's interesting. We'd need to introduce a kind of hierarchy between types, right?
'object' => shapeOf({ what: 'ever' })'array' => arrayOf('string')'any' => literally anything else
I wonder what the real-world use-cases around something like that are. Have you seen a lot of people subclassing components? I personally haven't seen, since there isn't a good inheritance story around templates.
In a more general sense, I think they probably should be able to narrow types, but maybe that would be something to explore after the 1.0.0 release.
There was a problem hiding this comment.
The way it originally worked was that we validated against all validators in the inheritance tree, if I remember correctly. So, in the above case, you would validate against both any and string. This would prevent incoherent validations:
class Foo {
@argument('number') foo;
}
class Bar extends Foo {
@argument('string') foo;
}Would always fail one or the other, so users would never be able to pass in foo. Hard to add a good error message here though, so maybe this is too complicated and we should just either allow children to override parents, or keep the current behavior.
There was a problem hiding this comment.
Ah, that's interesting. If you'd rather have parents be able to override the type, we can definitely do that instead.
| } | ||
|
|
||
| class ValidatedProperty { | ||
| constructor({ originalValue, klass, keyName, typeValidators }) { |
There was a problem hiding this comment.
I was still getting used to native classes at the time of writing this. I think there are still benefits to passing an object of "named params" through, but I have a feeling it'll be problematic for TS if we ever switch. Either way, not a big deal at the moment, just adding context here
| const validations = getValidationsFor(this.constructor); | ||
|
|
||
| for (let key in validations) { | ||
| wrapField(this.constructor, this, validations, key); |
There was a problem hiding this comment.
I'm a bit hesitant about this, since we're adding a wrapper to each instance of the class. Definitely think this could affect perf at scale. Even though this is not done in prod, at some point it'll become problematic even in dev.
We can iterate on this, but I think ideally we would decorate the getters themselves in place so we only need to define the properties once.
There was a problem hiding this comment.
That's a good point -- would you be OK with that being work that's done after this PR lands? It's massive as is, and I'd love to land it with a passing test suite and then work on refactoring a few things that won't break the public API, but can reduce the amount of code or improve performance.
Happy to do those things before we cut a 1.0.0 too
There was a problem hiding this comment.
Yeah, totally cool to work on it after this PR, as long as it gets done before 1.0.0 I think. That would be the last chunk of really necessary work before release.
There was a problem hiding this comment.
Sure thing! Maybe we can cut a beta release for people that are ready to start moving to this before that work happens.
| import { unionOf } from '@ember-decorators/argument/-debug'; | ||
| import unionOf from './-private/types/union-of'; | ||
|
|
||
| export { default as arrayOf } from './-private/types/array-of'; |
|
WRT allowing
At least in our codebase, we've gotten in the habit of annotating the type for every argument to components, and it's been a huge help when refactoring. I like the idea of gently encouraging people to at least be a little specific about what their arguments are supposed to be. Another thought: if we bring back the If we allow the decorator without a type, then all of these become valid: Which isn't necessarily bad, but is just an avenue for inconsistency that we can opt to steer away from if we'd prefer. |
|
👍 Yeah that's fair, let's continue with it for now being explicit and we can see how people use it in the wild. To be fair, if anyone really wanted that behavior, they could just do: import argument as originalArgument from '@ember-decorators/argument';
export const argument = argument('any');Somewhere locally and they have it |
1cd4c35 to
1e6010b
Compare
|
Awesome refactor here. Question, for someone using the option |
|
I'm pretty sure that that behavior is just how the decorators work now -- if you provide a default value, it will be used unless the argument is passed in. @josemarluedke have you tried upgrading to the beta version and hit an issue? @pzuraq was there more that |
|
Yes,
In this case, you either need to define logic to handle the case where an undefined value is passed to
I think there's room for these behaviors to be their own set of decorators in a separate library, leaving |
|
I see, thank you for that clarification! We might want to do something about that first use-case... that feels pretty important. We can always go back to allowing a set of options to @argument('string', { defaultIfUndefined = true })@josemarluedke which of those features were you relying on? Asking to better understand the use-case and how we want to proceed here. |
|
I came across this option while trying to upgrade ember-table to use the beta version: Addepar/ember-table#646 |
|
Awesome, that's great context to have. Since the idea with this package is that it's completely removed in production builds, I think that the @pzuraq: are you interested in creating an additional I'm imagining something like this import { defaultValue } from '@ember-decorators/default-value';
class Whatever extends Component {
@defaultValue({ ifUndefined = true, ifNull = true })
prop = 'false'
}I don't really like the specific names I use there, but an API similar to that that includes the old runtime behavior from this package. You could then apply the type validation and default behavior decorators independently. |
Sorry to drop a massive PR bomb, but I've been talking to @pzuraq about these changes and wanted to deliver it all at once since it's all pretty tightly related. This encompasses a lot:
ember-cliblueprintprettierfor consistent formattingember-decoratorsecosystem)A few other bits I'd like to call out that I'm a little proud of
reopenClassanywhere, in favor of the supported ability to extend return a new class from the decoratorfinisher. This library could easily be totally decoupled from Ember with just a little tweaking to the timing of the validationsWhat is not included
TypeScript Support. This is built on top of the Stage 2 decorator proposal, like the other decorators, so I think it would be better to wait until TypeScript also supports the Stage 2 decorators (from what I can tell, it currently does not). I am planning to convert this addon to TypeScript itself once the decorator compatibility story is figured out.
The
immutableconcept.requiredis handled by more specific types, butimmutableis not. I am interested in re-introducing it through as a feature in the future, though, but as part of additional options to@argumentrather than additional decorators to apply.Todo (Maybe)
greps both thevendoranddummyfiles for any references to@ember-decorators/argumentwould probably be a good smoke test to ensure that that all works correctly going forward.Closes #75
Closes #83 (Sort of)