Skip to content

Introduce new modify hook#217

Merged
rwjblue merged 10 commits intomasterfrom
deprecate-old-lifecycle
Mar 23, 2022
Merged

Introduce new modify hook#217
rwjblue merged 10 commits intomasterfrom
deprecate-old-lifecycle

Conversation

@chriskrycho
Copy link
Copy Markdown
Contributor

@chriskrycho chriskrycho commented Mar 22, 2022

This hook is the future of the class-based modifier API; it replaces all the non-destruction lifecycle hooks. The new API closely mirrors both the existing function-based modifier design and the design of class-based helpers in Ember:

import Modifier from 'ember-modifier';

export default class MyModifier extends Modifier {
  modify(element, positional, named) {
    // ...
  }
}

This API shift results in several significant improvements compared to the previous API:

  1. The signature of Modifier.modify matches the signature of a function-based signature. Both receive the element and both positional and named arguments directly, and in the same positions. This means that transitioning from a function- to a class-based helper is as simple as using the callback passed to modifier as the modify method on the class. Users no longer need

  2. It now matches the API of a class-based Helper, which has only the single compute method. This is important for rationalizing the mental model for using them: Modifiers and helpers are very similar, with the key difference being that helpers are intended to be pure functions which produce values, while modifiers are intended to be impure functions which mutate (modify!) a piece of DOM and do not return values.

  3. This allows us to introduce the desired semantics for modifiers without a breaking change. Unlike the existing lifecycle hooks, modify does not eagerly consume its arguments. Instead, matching the rest of the framework, it only entangles with the tracked values it actually uses. This means that users should take care when migrating their existing modifiers to use the new hook.

    To maintain exact backwards compatibility, modifier authors would need to explicitly use all of the arguments passed to their modifiers. Instead, modifier authors should generally cut a new major version which has the updated, lazy semantics.

It is a hard error to use modify along with any of the existing (non-destruction) lifecycle hooks. This allows us to guarantee that modify maintains the standard auto-tracking semantics as described above.

With modify available, deprecate the existing lifecycle hooks; users will need to begin migrating to the new design. By introducing modify this way, however, users can migrate their existing modifiers piecemeal.

@chriskrycho chriskrycho force-pushed the deprecate-old-lifecycle branch from e6fff6b to 44cfccc Compare March 22, 2022 20:23
chriskrycho added a commit that referenced this pull request Mar 22, 2022
As described in #217, all the functionality supplied by these hooks is
now available, and with the desired "lazy"/standard autotracking semantics, providing a path for gradual migration. These APIs will be
removed at 4.0.
This hook is the future of the class-based modifier API; it replaces
*all* the non-destruction lifecycle hooks. The new API closely mirrors
both the existing function-based modifier design and the design of class-based helpers in Ember:

    import Modifier from 'ember-modifier';

    export default class MyModifier extends Modifier {
      modify(element, positional, named) {
        // ...
      }
    }

This API shift results in several significant improvements compared to
the previous API:

1.  The signature of `Modifier.modify` matches the signature of a
    function-based signature. Both receive the element and both
    positional and named arguments directly, and in the same positions.
    This means that transitioning from a function- to a class-based
    helper is as simple as using the callback passed to `modifier` as
    the `modify` method on the class. Users no longer need

2.  It now matches the API of a class-based `Helper`, which has only
    the single `compute` method. This is important for rationalizing
    the mental model for using them: Modifiers and helpers are *very*
    similar, with the key difference being that helpers are intended to
    be pure functions which produce values, while modifiers are
    intended to be impure functions which mutate (modify!) a piece of
    DOM and do *not* return values.

3.  This allows us to introduce the desired semantics for modifiers
    *without a breaking change*. Unlike the existing lifecycle hooks,
    `modify` does *not* eagerly consume its arguments. Instead,
    matching the rest of the framework, it only entangles with the
    tracked values it *actually uses*. This means that users should
    take care when migrating their existing modifiers to use the new
    hook.

    To maintain exact backwards compatibility, modifier authors would
    need to explicitly use all of the arguments passed to their
    modifiers. Instead, modifier authors should generally cut a new
    major version which has the updated, lazy semantics.

It is a hard error to use `modify` along with any of the existing
(non-destruction) lifecycle hooks. This allows us to guarantee that
`modify` maintains the standard auto-tracking semantics as described
above.

A future PR will deprecate the existing lifecycle hooks; users will
need to begin migrating to the new design. By introducing `modify` this
way, however, users can migrate their existing modifiers piecemeal.
@chriskrycho chriskrycho force-pushed the deprecate-old-lifecycle branch from 44cfccc to 525ec5c Compare March 22, 2022 21:46
@chriskrycho chriskrycho marked this pull request as ready for review March 22, 2022 21:47
chriskrycho added a commit that referenced this pull request Mar 22, 2022
As described in #217, all the functionality supplied by these hooks is
now available, and with the desired "lazy"/standard autotracking semantics, providing a path for gradual migration. These APIs will be
removed at 4.0.
@chriskrycho chriskrycho mentioned this pull request Mar 22, 2022
19 tasks
The implementation is currently "too smart" for TS to understand on
versions earlier than 4.6.
Previously, defining `NamedArgs` and `PositionalArgs` was complicated
and error-prone because of its repetition. This little type helper
makes the actual intent of these two types much clearer and easier to
read and understand.
chriskrycho added a commit that referenced this pull request Mar 22, 2022
As described in #217, all the functionality supplied by these hooks is
now available, and with the desired "lazy"/standard autotracking semantics, providing a path for gradual migration. These APIs will be
removed at 4.0.
chriskrycho added a commit that referenced this pull request Mar 23, 2022
The introduction of the `modify` hook on class-based modifiers allows
users to use the `element` and both `positional` and `named` argumetns
directly, as arguments to `modify`, rather than having them as the
class properties `this.element` and `this.args` (see #217).
Accordingly, deprecate access to both `this.element` and `this.args`

(Users who want to stash the `element` on their modifier class can
continue to do so directly, by assigning it from the `modify` call.)
@rwjblue
Copy link
Copy Markdown
Member

rwjblue commented Mar 23, 2022

@chriskrycho - This is intentionally not introducing the deprecation, right? That will come in a follow up?

@chriskrycho
Copy link
Copy Markdown
Contributor Author

Thanks for the review! Will update tomorrow, as this is all good. And yeah, I’m going to add the deprecations separately for the sake of clearer changelog and commit log.

- `destroyModifier` receives an `InstalledState<S>`, and this means we
  can properly call `destroy(state.instance)`, eliminating the need to
  register a destructor on the `state` bucket itself (it's just a POJO,
  after all!) and to call `associateDestroyableChild` on the state
  bucket destructor so it also tears down the modifier instance.

- Document on `CreatedState<S>` that it is used in `destroyModifier` as
  well as in `updateModifier`.

- Clarify that `installModifier` receives a `CreatedState<S>` and add a
  long comment about the state transition we manage internally with
  unsafe casts.
@chriskrycho chriskrycho force-pushed the deprecate-old-lifecycle branch from e8d6bc1 to 8dbae8b Compare March 23, 2022 14:13
- Introduce `implementsModify` into the manager `State` bucket, so that
  we only have to check the target class once.
- Update all the checks for `gte('3.22')` to only both checking it when
  the modifier *doesn't* implement `modify`, making it easier to avoid
  accidentally breaking the condition between the two. This way we will
  *never* call `consumeArgs` if the modifier implements `modify`.
@chriskrycho chriskrycho added the enhancement New feature or request label Mar 23, 2022
chriskrycho added a commit that referenced this pull request Mar 23, 2022
The introduction of the `modify` hook on class-based modifiers allows
users to use the `element` and both `positional` and `named` argumetns
directly, as arguments to `modify`, rather than having them as the
class properties `this.element` and `this.args` (see #217).
Accordingly, deprecate access to both `this.element` and `this.args`

(Users who want to stash the `element` on their modifier class can
continue to do so directly, by assigning it from the `modify` call.)
chriskrycho added a commit that referenced this pull request Mar 23, 2022
As described in #217, all the functionality supplied by these hooks is
now available, and with the desired "lazy"/standard autotracking semantics, providing a path for gradual migration. These APIs will be
removed at 4.0.
@rwjblue rwjblue merged commit bfc828e into master Mar 23, 2022
@rwjblue rwjblue deleted the deprecate-old-lifecycle branch March 23, 2022 17:54
chriskrycho added a commit that referenced this pull request Mar 23, 2022
As described in #217, all the functionality supplied by these hooks is
now available, and with the desired "lazy"/standard autotracking semantics, providing a path for gradual migration. These APIs will be
removed at 4.0.
chriskrycho added a commit that referenced this pull request Mar 23, 2022
The introduction of the `modify` hook on class-based modifiers allows
users to use the `element` and both `positional` and `named` argumetns
directly, as arguments to `modify`, rather than having them as the
class properties `this.element` and `this.args` (see #217).
Accordingly, deprecate access to both `this.element` and `this.args`

(Users who want to stash the `element` on their modifier class can
continue to do so directly, by assigning it from the `modify` call.)
chriskrycho added a commit that referenced this pull request Mar 23, 2022
As described in #217, all the functionality supplied by these hooks is
now available, and with the desired "lazy"/standard autotracking
semantics, providing a path for gradual migration. These APIs will be
removed at 4.0.
chriskrycho added a commit that referenced this pull request Mar 23, 2022
The introduction of the `modify` hook on class-based modifiers allows
users to use the `element` and both `positional` and `named` arguments
directly, as arguments to `modify`, rather than having them as the
class properties `this.element` and `this.args` (see #217).
Accordingly, deprecate access to both `this.element` and `this.args`.

(Users who want to stash the `element` on their modifier class can
continue to do so directly, by assigning it from the `modify` call.)
chriskrycho added a commit that referenced this pull request Mar 23, 2022
The introduction of the `modify` hook on class-based modifiers allows
users to use the `element` and both `positional` and `named` arguments
directly, as arguments to `modify`, rather than having them as the
class properties `this.element` and `this.args` (see #217).
Accordingly, deprecate access to both `this.element` and `this.args`.

(Users who want to stash the `element` on their modifier class can
continue to do so directly, by assigning it from the `modify` call.)
chriskrycho added a commit that referenced this pull request Mar 23, 2022
The introduction of the `modify` hook on class-based modifiers allows
users to use the `element` and both `positional` and `named` arguments
directly, as arguments to `modify`, rather than having them as the
class properties `this.element` and `this.args` (see #217).
Accordingly, deprecate access to both `this.element` and `this.args`.

(Users who want to stash the `element` on their modifier class can
continue to do so directly, by assigning it from the `modify` call.)
chriskrycho added a commit that referenced this pull request Mar 24, 2022
The introduction of the `modify` hook on class-based modifiers allows
users to use the `element` and both `positional` and `named` arguments
directly, as arguments to `modify`, rather than having them as the
class properties `this.element` and `this.args` (see #217).
Accordingly, deprecate access to both `this.element` and `this.args`.

(Users who want to stash the `element` on their modifier class can
continue to do so directly, by assigning it from the `modify` call.)
chriskrycho added a commit that referenced this pull request Mar 25, 2022
As described in #217, all the functionality supplied by these hooks is
now available, and with the desired "lazy"/standard autotracking
semantics, providing a path for gradual migration. These APIs will be
removed at 4.0.
chriskrycho added a commit that referenced this pull request Mar 25, 2022
The introduction of the `modify` hook on class-based modifiers allows
users to use the `element` and both `positional` and `named` arguments
directly, as arguments to `modify`, rather than having them as the
class properties `this.element` and `this.args` (see #217).
Accordingly, deprecate access to both `this.element` and `this.args`.

(Users who want to stash the `element` on their modifier class can
continue to do so directly, by assigning it from the `modify` call.)
evoactivity added a commit to evoactivity/ember-autoresize-modifier that referenced this pull request Oct 12, 2022
jelhan pushed a commit to jelhan/ember-autoresize-modifier that referenced this pull request Oct 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants