Deprecate Ember Utils & dependent Computed Properties
Summary
This RFC proposes the deprecation of the following methods:
compare
isBlank
isEmpty
isEqual
isNone
isPresent
typeOf
As well as computed properties that rely on these methods:
notEmpty
empty
none
Once deprecated, these methods will move to an external addon to allow users to still take advantage of the succinctness these utility methods provide.
Motivation
To further Ember's goal of a svelte core and moving functionality to separate packages, this RFC attempts to deprecate a few utility methods and extract them to their own addon. A part from the aforementioned goal, in many cases, these utility methods do not add a whole lot of value over writing plain old JavaScript. Moreover, these methods generally have a negative cognitive impact on the code in question. In order to reduce code liability and advocate for users to write plain JavaScript, these methods could be removed from Ember core and extracted to an addon.
A simple example of this cognitive load would be checking for the presence of a variable of type String that can only have four states - undefined
, null
, ''
or 'MYSTRING'
. One might check if the variable is truthy with !!myVariable
. However, a utility method such as isPresent(myVariable)
can increase the cognitive load of the truthy check in question as it checks for many different types.
In addition, when checking whether a primitive value is truthy, a user can choose from isPresent
, isBlank
, isNone
, or isEmpty
. This can can lead to some confusion for users as there are many options to do the same thing.
Lastly, isEmpty
documented here will return false for an empty object. Defining what an empty object is can be a bit tricky and isEmpty
provided specific semantics that may not fit a users definition. This problem of specific semantics not fitting a users definition also applies to the other utility methods as well.
Detailed design
compare
, isBlank
, isEmpty
, isEqual
, isNone
, isPresent
, typeOf
, notEmpty
, empty
, and none
will be deprecated at first with an addon that extracts these methods for users that still find value in these utility methods. Any instantiation of one of these functions imported from @ember/utils
will log a deprecation warning, notifying the user that this method is deprecated and provide a link to the supported addon.
Addons that previously relied on these utility methods would also be expected to install this package. Moreover, to ease adoption of the external addon, a codemod will become available for the community.
How we teach this
ES5/ES6 brings improved language semantics. Moreover, using Ember in IE9, IE10, and PhantomJS is not supported as of Ember 3.0. As a result, we as a community can guarantee results across browsers when using just JavaScript. Some of the benefits from these utility methods eminated from a need to guarantee results with ES3 browsers. Today, we can nudge users to use JavaScript where needed and install an addon in cases where they feel it is necessary.
This would require an update to the guides to indicate that the use of any of these utility methods are now available in an addon. Moreover, in the README of the addon, we can provide specific examples of when using JavaScript is easier than reaching for one of these utility methods.
// Current state
import { isEmpty } from '@ember/utils';
function isTomster(attributes) {
if (isEmpty(attributes)) {
return false;
}
}
If you prefer to install the addon directly, we will provide a codemod to update the import path.
// With an addon
import { isEmpty } from 'ember-utils';
function isTomster(attributes: any): boolean {
if (isEmpty(attributes)) {
return false;
}
}
However, depending on the source data, this logic can be migrated to the following. Let us assume the shape of attributes is an object:
function isTomster(attributes?: Record<string, any>): boolean {
if (!attributes || Object.keys(attributes).length === 0) {
return false;
}
}
If it is an array:
function isTomster(attributes?: Array<string>): boolean {
if (!attributes || attributes.length === 0) {
return false;
}
}
Lastly, the guides can also point users to a utility library like lodash.
For many existing codebases, it should be as simple as installing the extracted addon and running the codemod. However, users not utilizing these methods will not have these methods bundled by default.
Deprecation Guide
compare
Install and import ember-util
to compare
two types.
until: 5.0.0
id: use-ember-util-package--compare
import { compare } from '@ember/util';
compare(['code'], ['code', 'is', 'lovely']); // -1
compare(['code'], ['code']); // 0
compare(['code'], []); // 1
To avoid the compare
deprecation, you can install the ember-util
package and do the following
import { compare } from 'ember-util';
compare(['code'], ['code', 'is', 'lovely']); // -1
compare(['code'], ['code']); // 0
compare(['code'], []); // 1
isBlank
Install and import ember-util
to check isBlank
status.
until: 5.0.0
id: use-ember-util-package--isBlank
import { isBlank } from '@ember/util';
isBlank(['code']); // false
isBlank([]); // true
To avoid the isBlank
deprecation, you can install the ember-util
package and do the following
import { isBlank } from 'ember-util';
isBlank(['code']); // false
isBlank([]); // true
isEmpty
Install and import ember-util
to check isEmpty
status.
until: 5.0.0
id: use-ember-util-package--isEmpty
import { isEmpty } from '@ember/util';
isEmpty(['code']); // false
isEmpty([]); // true
To avoid the isEmpty
deprecation, you can install the ember-util
package and do the following
import { isEmpty } from 'ember-util';
isEmpty(['code']); // false
isEmpty([]); // true
isEqual
Install and import ember-util
to check isEqual
status.
until: 5.0.0
id: use-ember-util-package--isEqual
import { isEqual } from '@ember/util';
isEqual({}, { a: 'key' }); // false
isEqual({ a: 'key' }, { a: 'key' }); // true
isEqual(['code'], ['code']); // false
To avoid the isEqual
deprecation, you can install the ember-util
package and do the following
import { isEqual } from 'ember-util';
isEqual({}, { a: 'key' }); // false
isEqual({ a: 'key' }, { a: 'key' }); // true
isEqual(['code'], ['code']); // false
isNone
Install and import ember-util
to check isNone
status.
until: 5.0.0
id: use-ember-util-package--isNone
import { isNone } from '@ember/util';
isNone(undefined); // true
isNone([]); // false
isNone(''); // false
To avoid the isNone
deprecation, you can install the ember-util
package and do the following
import { isNone } from 'ember-util';
isNone(undefined); // true
isNone([]); // false
isNone(''); // false
isPresent
Install and import ember-util
to check isPresent
status.
until: 5.0.0
id: use-ember-util-package--isPresent
import { isPresent } from '@ember/util';
isPresent(undefined); // false
isPresent([]); // false
isPresent('hi der'); // true
To avoid the isPresent
deprecation, you can install the ember-util
package and do the following
import { isPresent } from 'ember-util';
isPresent(undefined); // false
isPresent([]); // false
isPresent('hi der'); // true
typeOf
Install and import ember-util
to check typeOf
on an input.
until: 5.0.0
id: use-ember-util-package--typeOf
import { typeOf } from '@ember/util';
let noop = () => {};
typeOf(noop); // 'function'
To avoid the typeOf
deprecation, you can install the ember-util
package and do the following
import { typeOf } from 'ember-util';
let noop = () => {};
typeOf(noop); // 'function'
notEmpty
Install and import ember-util
to check notEmpty
on a property.
until: 5.0.0
id: use-ember-util-package--notEmpty
import { notEmpty } from '@ember/object/computed';
class ToDoList {
constructor(todos) {
set(this, 'todos', todos);
}
@notEmpty('args.todos') isDone;
}
To avoid the notEmpty
deprecation, you can install the ember-util
package and do the following
import { notEmpty } from 'ember-util';
class ToDoList {
constructor(todos) {
set(this, 'todos', todos);
}
@notEmpty('todos') isDone;
}
empty
Install and import ember-util
to check empty
on a property.
until: 5.0.0
id: use-ember-util-package--empty
import { empty } from '@ember/object/computed';
class ToDoList {
constructor(todos) {
set(this, 'todos', todos);
}
@empty('todos') isDone;
}
To avoid the empty
deprecation, you can install the ember-util
package and do the following
import { empty } from 'ember-util';
class ToDoList {
constructor(todos) {
set(this, 'todos', todos);
}
@empty('todos') isDone;
}
none
Install and import ember-util
to check none
on a property.
until: 5.0.0
id: use-ember-util-package--none
import { none } from '@ember/object/computed';
class ToDoList {
@none('args.todos') isDone;
}
To avoid the none
deprecation, you can install the ember-util
package and do the following
import { none } from 'ember-util';
class ToDoList {
@none('args.todos') isDone;
}
Drawbacks
This change may impact quite a few codebases. Moreover, users who heavily rely on these utility methods will have to install another addon. Some users may desire this functionality out of the box.
Given the example above, some users see a lot of value for the safety checks isPresent(myVariable)
provides. The type of variable could be of type String or of type Object and wiring this up to check manually is laborious.
Alternatives
Another option would be to improve existing utility cases such as isEmpty({}) === true
. Moreover, we could also consider scoping existing utility methods to specific types. For example, isEmpty
would be specific to checking objects, collections, maps or sets to reduce confusion of when these utility methods should be used.
Instead of deprecating the notEmpty
, empty
, none
computed macros one-by-one, it would probably be good to deprecate the entire @ember/object/computed
module because everything else in there is not aligned with current idioms.