From 53b64e05ee5eda972a0938ddc451c5b0a1ddcfe2 Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Tue, 8 Nov 2022 13:28:30 -0800 Subject: [PATCH 1/6] Deprecate Ember.A --- text/0864-deprecate-ember-a.md | 94 ++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 text/0864-deprecate-ember-a.md diff --git a/text/0864-deprecate-ember-a.md b/text/0864-deprecate-ember-a.md new file mode 100644 index 0000000000..5d6f9bd3ec --- /dev/null +++ b/text/0864-deprecate-ember-a.md @@ -0,0 +1,94 @@ +--- +stage: accepted +start-date: 2022-11-22T00:00:00.000Z +release-date: +release-versions: +teams: + - data + - framework + - learning + - typescript +prs: + accepted: https://github.com/emberjs/rfcs/pull/864 +project-link: +--- + +# Deprecate Ember.A() + +## Summary + +Remove `Ember.A()` as the functionality it provides is no longer needed in the post octane reactivity system and with the removal of array prototype extensions in #848. + +## Motivation + +`Ember.A()` provides a way for addon authors or application owners who disable array prototype extensions to add reactivity to arrays (through `pushObject()`, `removeObject()`) and to access prototype extension convenience methods such as `filterBy()` on an any array-like object. Both of these paradigms have been deprecated in Ember and `Ember.A()` should follow suit as it can break things in unexpected ways and interferes with progress in [Ember](https://github.com/emberjs/ember.js/blob/4339725976299b24c69fb9dfbf13d18bf9917130/packages/@ember/-internals/utils/lib/ember-array.ts) and [Ember Data](https://github.com/emberjs/data/blob/47a71ca1538ba9e2d7dfa01bf048a2db897bdf5f/packages/store/addon/-private/record-arrays/identifier-array.ts#L381-L401) where special care must be taken to prevent or compensate for usage. + +Along with it's usefulness `Ember.A()` has a significant API gotcha in that it doesn't return a new instance of the array-like object it is passed, it modifies that object and returns is such that `A(array) === array` this can create the dreaded spooky-action-at-a-distance effect where suddenly an object which was `Array` can become an `Ember.NativeArray`. An example of this is using `{{includes "Zoey" this.mascots}}` from [ember-composable-helpers](https://github.com/DockYard/ember-composable-helpers#includes) will modify `this.mascots` and add `lastObject` to the API. Re-ordering the template code at a later date will result in a mysterious failure that can be extremely frustrating to understand. + +```js +export default class WeirdArray extends Component { + mascots = ['Tomster', 'Zoey']; + + get lastMascot() { + return this.mascots.lastObject; + } + + +} +``` + +Deprecating `Ember.A()` will provide a signal to the addon community to move away from both array prototype extensions and array reactivity which requires using the Emberism `pushObject`/`removeObject` to track updates. + +Now that array prototype extensions have been deprecated it is quite possible that usage of `Ember.A()` will increase significantly as a defensive coding strategy to maintain access to the family of methods Ember adds to all arrays. This should be immediately discouraged as `Ember.A()` is not a long term viable solution and other better alternatives exist. + +## Transition Path + +### Replacement APIs + +As discussed in #848 better alternatives exist for the APIs provided by `Ember.A()`: + +> For convenient methods like `filterBy`, `compact`, `sortBy` etc., the replacement functionalities already exist either through native array methods or utility libraries like [lodash](https://lodash.com), [Ramda](https://ramdajs.com), etc. + +>For mutation methods (like `pushObject`, `removeObject`) or observable properties (like `firstObject`, `lastObject`) participating in the Ember classic reactivity system, the replacement functionalities also already exist in the form of immutable update style with tracked properties like `@tracked someArray = []`, or through utilizing `TrackedArray` from `tracked-built-ins`. + +### Clearly Define Idiomatic Path + +Deprecating `Ember.A()` will provide a clear signal to move away from this construct and rely on these more robust modern alternatives. + +### Transitional Feature Flags + +Transition away from `Ember.A()` should be aided by a pair of feature flags: + +#### Wrap Passed Value + +Instead of returning the passed value `Ember.A()` would return a native proxy to wrap the original object without modifying it. This would remove the most confusing part of the API and allow apps to opt in early and clear any unexpected issues before `Ember.A()` is fully removed. + +#### Noop Ember.A + +Currently if array prototype extensions is enabled `Ember.A()` is a `noop` and returns the passed value unmodified. It should be possible for application developers to turn off array prototype extensions and maintain this behavior of `Ember.A()`. This will make bugs easier to find and allow applications to more quickly prevent prototype pollution of the `Array` API before `Ember.A()` is fully removed. + + +## How We Teach This + +`Ember.A()` is not covered in the guides, but it is present in [many](https://emberobserver.com/code-search?codeQuery=import%20%7B%20A%20%7D%20from%20%27%40ember%2Farray%27%3B) [many](https://emberobserver.com/code-search?codeQuery=Ember.A) addons. + +Usage of convenience APIs can be easily replaced using the [no-array-prototype-extensions](https://github.com/ember-cli/eslint-plugin-ember/blob/4820f0fb286e40872a77d687618be56999f23704/docs/rules/no-array-prototype-extensions.md) codemod. + +For reactivity addon authors will need to rely forcing tracking as `this.mascots = [...this.mascots, 'Rock & Roll']` or through utilizing `TrackedArray` from [tracked-built-ins](https://github.com/tracked-tools/tracked-built-ins). + +## Drawbacks + +`Ember.A()` has been a huge part of building Ember Addons for a long time and it is present in many popular addons. Removing this feature will cause a significant amount of churn in otherwise stable addons and require a significant community effort to replace. + +## Alternatives + +### Change Confusing API + +Instead of modifying the passed value `Ember.A()` could return a new object which contains a reference to the original value. This would avoid the `A(array) === array` issue and make it easier to detect if an object had been passed through `A()` and to work with the original value if necessary. This would still be a breaking change as many apps (often unknowingly) rely on the behavior of an object having been passed through `A()`. + +## Unresolved questions From 796c13b304ce88dae08010512c759bb41abc1def Mon Sep 17 00:00:00 2001 From: Jon Johnson Date: Tue, 22 Nov 2022 15:54:31 -0800 Subject: [PATCH 2/6] Octane gets the proper name treatment Co-authored-by: Peter Wagenet --- text/0864-deprecate-ember-a.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0864-deprecate-ember-a.md b/text/0864-deprecate-ember-a.md index 5d6f9bd3ec..7b30a852d8 100644 --- a/text/0864-deprecate-ember-a.md +++ b/text/0864-deprecate-ember-a.md @@ -17,7 +17,7 @@ project-link: ## Summary -Remove `Ember.A()` as the functionality it provides is no longer needed in the post octane reactivity system and with the removal of array prototype extensions in #848. +Remove `Ember.A()` as the functionality it provides is no longer needed in the post Octane reactivity system and with the removal of array prototype extensions in #848. ## Motivation From 1f27a5647f8f8e73fbbae1f0809a9bc0c9c7c7e1 Mon Sep 17 00:00:00 2001 From: Jon Johnson Date: Mon, 28 Nov 2022 11:48:50 -0800 Subject: [PATCH 3/6] Apply suggestions from code review Co-authored-by: MrChocolatine <47531779+MrChocolatine@users.noreply.github.com> --- text/0864-deprecate-ember-a.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0864-deprecate-ember-a.md b/text/0864-deprecate-ember-a.md index 7b30a852d8..ee938b332b 100644 --- a/text/0864-deprecate-ember-a.md +++ b/text/0864-deprecate-ember-a.md @@ -23,7 +23,7 @@ Remove `Ember.A()` as the functionality it provides is no longer needed in the p `Ember.A()` provides a way for addon authors or application owners who disable array prototype extensions to add reactivity to arrays (through `pushObject()`, `removeObject()`) and to access prototype extension convenience methods such as `filterBy()` on an any array-like object. Both of these paradigms have been deprecated in Ember and `Ember.A()` should follow suit as it can break things in unexpected ways and interferes with progress in [Ember](https://github.com/emberjs/ember.js/blob/4339725976299b24c69fb9dfbf13d18bf9917130/packages/@ember/-internals/utils/lib/ember-array.ts) and [Ember Data](https://github.com/emberjs/data/blob/47a71ca1538ba9e2d7dfa01bf048a2db897bdf5f/packages/store/addon/-private/record-arrays/identifier-array.ts#L381-L401) where special care must be taken to prevent or compensate for usage. -Along with it's usefulness `Ember.A()` has a significant API gotcha in that it doesn't return a new instance of the array-like object it is passed, it modifies that object and returns is such that `A(array) === array` this can create the dreaded spooky-action-at-a-distance effect where suddenly an object which was `Array` can become an `Ember.NativeArray`. An example of this is using `{{includes "Zoey" this.mascots}}` from [ember-composable-helpers](https://github.com/DockYard/ember-composable-helpers#includes) will modify `this.mascots` and add `lastObject` to the API. Re-ordering the template code at a later date will result in a mysterious failure that can be extremely frustrating to understand. +Along with its usefulness `Ember.A()` has a significant API gotcha in that it doesn't return a new instance of the array-like object it is passed, it modifies that object and returns it such that `A(array) === array`, this can create the dreaded spooky-action-at-a-distance effect where suddenly an object which was `Array` can become an `Ember.NativeArray`. An example of this is using `{{includes "Zoey" this.mascots}}` from [ember-composable-helpers](https://github.com/DockYard/ember-composable-helpers#includes), which will modify `this.mascots` and add `lastObject` to the API. Re-ordering the template code at a later date will result in a mysterious failure that can be extremely frustrating to understand. ```js export default class WeirdArray extends Component { @@ -54,7 +54,7 @@ As discussed in #848 better alternatives exist for the APIs provided by `Ember.A > For convenient methods like `filterBy`, `compact`, `sortBy` etc., the replacement functionalities already exist either through native array methods or utility libraries like [lodash](https://lodash.com), [Ramda](https://ramdajs.com), etc. ->For mutation methods (like `pushObject`, `removeObject`) or observable properties (like `firstObject`, `lastObject`) participating in the Ember classic reactivity system, the replacement functionalities also already exist in the form of immutable update style with tracked properties like `@tracked someArray = []`, or through utilizing `TrackedArray` from `tracked-built-ins`. +> For mutation methods (like `pushObject`, `removeObject`) or observable properties (like `firstObject`, `lastObject`) participating in the Ember classic reactivity system, the replacement functionalities also already exist in the form of immutable update style with tracked properties like `@tracked someArray = []`, or through utilizing `TrackedArray` from [tracked-built-ins](https://github.com/tracked-tools/tracked-built-ins). ### Clearly Define Idiomatic Path @@ -83,7 +83,7 @@ For reactivity addon authors will need to rely forcing tracking as `this.mascots ## Drawbacks -`Ember.A()` has been a huge part of building Ember Addons for a long time and it is present in many popular addons. Removing this feature will cause a significant amount of churn in otherwise stable addons and require a significant community effort to replace. +`Ember.A()` has been a huge part of building Ember addons for a long time and it is present in many popular addons. Removing this feature will cause a significant amount of churn in otherwise stable addons and require a significant community effort to replace. ## Alternatives From 12bbd649c41f10d6321632eda01d500d1061428a Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Fri, 2 Dec 2022 12:49:07 -0800 Subject: [PATCH 4/6] Clarify motivation section --- text/0864-deprecate-ember-a.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0864-deprecate-ember-a.md b/text/0864-deprecate-ember-a.md index ee938b332b..7477dd6e63 100644 --- a/text/0864-deprecate-ember-a.md +++ b/text/0864-deprecate-ember-a.md @@ -21,7 +21,7 @@ Remove `Ember.A()` as the functionality it provides is no longer needed in the p ## Motivation -`Ember.A()` provides a way for addon authors or application owners who disable array prototype extensions to add reactivity to arrays (through `pushObject()`, `removeObject()`) and to access prototype extension convenience methods such as `filterBy()` on an any array-like object. Both of these paradigms have been deprecated in Ember and `Ember.A()` should follow suit as it can break things in unexpected ways and interferes with progress in [Ember](https://github.com/emberjs/ember.js/blob/4339725976299b24c69fb9dfbf13d18bf9917130/packages/@ember/-internals/utils/lib/ember-array.ts) and [Ember Data](https://github.com/emberjs/data/blob/47a71ca1538ba9e2d7dfa01bf048a2db897bdf5f/packages/store/addon/-private/record-arrays/identifier-array.ts#L381-L401) where special care must be taken to prevent or compensate for usage. +`Ember.A()` provides a way for addon authors or application owners who disable array prototype extensions to add reactivity to arrays (through `pushObject()`, `removeObject()`) and to access prototype extension convenience methods such as `filterBy()` on an any array-like object. Both of these paradigms have been deprecated in Ember and `Ember.A()` should follow suit as maintaing this behavior of an array like object that differs from a true native array in important ways is a significant burdeon without clear benifits. Along with its usefulness `Ember.A()` has a significant API gotcha in that it doesn't return a new instance of the array-like object it is passed, it modifies that object and returns it such that `A(array) === array`, this can create the dreaded spooky-action-at-a-distance effect where suddenly an object which was `Array` can become an `Ember.NativeArray`. An example of this is using `{{includes "Zoey" this.mascots}}` from [ember-composable-helpers](https://github.com/DockYard/ember-composable-helpers#includes), which will modify `this.mascots` and add `lastObject` to the API. Re-ordering the template code at a later date will result in a mysterious failure that can be extremely frustrating to understand. From 360302b8d139208f7ee3ae242c83fb3846ed951e Mon Sep 17 00:00:00 2001 From: Jon Johnson Date: Sat, 3 Dec 2022 12:12:01 -0800 Subject: [PATCH 5/6] Update text/0864-deprecate-ember-a.md Co-authored-by: MrChocolatine <47531779+MrChocolatine@users.noreply.github.com> --- text/0864-deprecate-ember-a.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0864-deprecate-ember-a.md b/text/0864-deprecate-ember-a.md index 7477dd6e63..b81cf28030 100644 --- a/text/0864-deprecate-ember-a.md +++ b/text/0864-deprecate-ember-a.md @@ -21,7 +21,7 @@ Remove `Ember.A()` as the functionality it provides is no longer needed in the p ## Motivation -`Ember.A()` provides a way for addon authors or application owners who disable array prototype extensions to add reactivity to arrays (through `pushObject()`, `removeObject()`) and to access prototype extension convenience methods such as `filterBy()` on an any array-like object. Both of these paradigms have been deprecated in Ember and `Ember.A()` should follow suit as maintaing this behavior of an array like object that differs from a true native array in important ways is a significant burdeon without clear benifits. +`Ember.A()` provides a way for addon authors or application owners who disable array prototype extensions to add reactivity to arrays (through `pushObject()`, `removeObject()`) and to access prototype extension convenience methods such as `filterBy()` on an any array-like object. Both of these paradigms have been deprecated in Ember and `Ember.A()` should follow suit as maintaining this behavior of an array like object that differs from a true native array in important ways is a significant burden without clear benefits. Along with its usefulness `Ember.A()` has a significant API gotcha in that it doesn't return a new instance of the array-like object it is passed, it modifies that object and returns it such that `A(array) === array`, this can create the dreaded spooky-action-at-a-distance effect where suddenly an object which was `Array` can become an `Ember.NativeArray`. An example of this is using `{{includes "Zoey" this.mascots}}` from [ember-composable-helpers](https://github.com/DockYard/ember-composable-helpers#includes), which will modify `this.mascots` and add `lastObject` to the API. Re-ordering the template code at a later date will result in a mysterious failure that can be extremely frustrating to understand. From e5c1ed918ecf1653d0fc3cce97ac79a0a3cd2ae5 Mon Sep 17 00:00:00 2001 From: Jon Johnson Date: Wed, 25 Jan 2023 16:21:58 -0800 Subject: [PATCH 6/6] Improve text and link for codemod Co-authored-by: Bryan Mishkin <698306+bmish@users.noreply.github.com> --- text/0864-deprecate-ember-a.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0864-deprecate-ember-a.md b/text/0864-deprecate-ember-a.md index b81cf28030..53e41c7645 100644 --- a/text/0864-deprecate-ember-a.md +++ b/text/0864-deprecate-ember-a.md @@ -77,7 +77,7 @@ Currently if array prototype extensions is enabled `Ember.A()` is a `noop` and r `Ember.A()` is not covered in the guides, but it is present in [many](https://emberobserver.com/code-search?codeQuery=import%20%7B%20A%20%7D%20from%20%27%40ember%2Farray%27%3B) [many](https://emberobserver.com/code-search?codeQuery=Ember.A) addons. -Usage of convenience APIs can be easily replaced using the [no-array-prototype-extensions](https://github.com/ember-cli/eslint-plugin-ember/blob/4820f0fb286e40872a77d687618be56999f23704/docs/rules/no-array-prototype-extensions.md) codemod. +Usage of convenience APIs can be linted against and largely autofixed/codemodded to native JavaScript by the [ember/no-array-prototype-extensions](https://github.com/ember-cli/eslint-plugin-ember/blob/0d8a4711ec461208562a9a1dc6b45fe65885f53a/docs/rules/no-array-prototype-extensions.md) lint rule. For reactivity addon authors will need to rely forcing tracking as `this.mascots = [...this.mascots, 'Rock & Roll']` or through utilizing `TrackedArray` from [tracked-built-ins](https://github.com/tracked-tools/tracked-built-ins).