Skip to content
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

Modifier Managers should not re-consume their args on destroy #19162

Closed
NullVoxPopuli opened this issue Aug 14, 2020 · 6 comments · Fixed by #19163
Closed

Modifier Managers should not re-consume their args on destroy #19162

NullVoxPopuli opened this issue Aug 14, 2020 · 6 comments · Fixed by #19163
Labels

Comments

@NullVoxPopuli
Copy link
Contributor

According to the current modifier manager spec:

destroyModifier will be called when the modifier is no longer needed. This is intended for performing object-model level cleanup.

then, in the following code example:

 destroyModifier(instance, args) {
    if (instance.willDestroyDOM !== undefined) {
      instance.willDestroyDOM();
    }
  }

args are passed to destroyModifier.

I think this is a bug because:

  • it means that args are all re-consumed on destruction of a render / component tree
  • if guards in templates no longer can guarantee the safety of the shape of data
  • in order for modifiers' destruction to not trigger exceptions wherever args' derived data is derived, you need to add additional guards all over your code, or do foo?.bar?.bax everywhere (this generates a lot of boilerplate)

I propose we remove args from the destroyModifier hook so that

  • none of the above issues exist anymore
  • teardown is slightly faster due to not re-consuming args

For example, today, the following isn't safe:

{{#if this.data.foo}}
  <SomeComponent @data={{this.data}} />
{{/if}}
// some-component
export default setComponentTemplate(hbs`
  <span {{some-modifier this.nestedData}}>breaks</span>
`, class extends Component {
  get nestedData() {
    // you should be able to assume foo exists, because of the condition in the previous template
    // but today, you can't assume that :(
    return this.args.data.foo.dataOnFoo;
  }
});

Minimal Reproduction / Description of the Problem: https://ember-twiddle.com/5a3fa797b26e8807869340792219a5ee?openFiles=components.demo%5C.hbs%2Ccomponents.demo%5C.hbs

Relevant Discord Threads:

@NullVoxPopuli
Copy link
Contributor Author

re: discord thread: https://discordapp.com/channels/480462759797063690/608346628163633192/743822662681493504

@pzuraq says that wrapping in a proxy so that args are only consumed when used has been a potential solution.

that would solve this! :D
(and would be a great way to not remove functionality some folks may be relying on)

@mattmcmanus
Copy link

Thanks for writing this up @NullVoxPopuli. This problem is currently biting us at Yapp and had broken my brain for the last couple days.

@mydea
Copy link
Contributor

mydea commented Aug 17, 2020

I agree that the current behavior, at the very least, feels like a bug, and the resulting problems are very hard to pinpoint, and unintuitive. So any solution to this would be greatly appreciated! Thanks for writing this up!

@rwjblue
Copy link
Member

rwjblue commented Sep 28, 2020

IMHO, this is a bug. It's basically the reason that we use an args proxy for component managers, and a recent PR by @chancancode made that a bit more generic so that we could use it for our modifier managers.

I'm going to move this into an issue on the emberjs/ember.js repo, so we can work on a fix (instead of an RFCs issue).

@rwjblue rwjblue transferred this issue from emberjs/rfcs Sep 28, 2020
@rwjblue rwjblue changed the title Design Bug?: modifier manager should not re-consume their args on destroy (they currently are consuming args an destroy)) Modifier Managers should not re-consume their args on destroy Sep 28, 2020
@rwjblue rwjblue added the Bug label Sep 28, 2020
@rwjblue
Copy link
Member

rwjblue commented Sep 28, 2020

The path forward here is to make this code sharable between the various manager types (modifier and component now, but soon helpers):

let namedArgsProxyFor: (namedArgs: CapturedNamedArguments, debugName?: string) => Args['named'];
if (HAS_NATIVE_PROXY) {
namedArgsProxyFor = <NamedArgs extends CapturedNamedArguments>(
namedArgs: NamedArgs,
debugName?: string
) => {
let getTag = (key: keyof Args) => tagForNamedArg(namedArgs, key);
let handler: ProxyHandler<{}> = {
get(_target, prop) {
let ref = namedArgs[prop as string];
if (ref !== undefined) {
return valueForRef(ref);
} else if (prop === CUSTOM_TAG_FOR) {
return getTag;
}
},
has(_target, prop) {
return namedArgs[prop as string] !== undefined;
},
ownKeys(_target) {
return Object.keys(namedArgs);
},
getOwnPropertyDescriptor(_target, prop) {
assert(
'args proxies do not have real property descriptors, so you should never need to call getOwnPropertyDescriptor yourself. This code exists for enumerability, such as in for-in loops and Object.keys()',
namedArgs[prop as string] !== undefined
);
return {
enumerable: true,
configurable: true,
};
},
};
if (DEBUG) {
handler.set = function(_target, prop) {
assert(
`You attempted to set ${debugName}#${String(
prop
)} on a components arguments. Component arguments are immutable and cannot be updated directly, they always represent the values that are passed to your component. If you want to set default values, you should use a getter instead`
);
return false;
};
}
return new Proxy({}, handler);
};
} else {
namedArgsProxyFor = <NamedArgs extends CapturedNamedArguments>(namedArgs: NamedArgs) => {
let getTag = (key: keyof Args) => tagForNamedArg(namedArgs, key);
let proxy = {};
Object.defineProperty(proxy, CUSTOM_TAG_FOR, {
configurable: false,
enumerable: false,
value: getTag,
});
Object.keys(namedArgs).forEach(name => {
Object.defineProperty(proxy, name, {
enumerable: true,
configurable: true,
get() {
return valueForRef(namedArgs[name]);
},
});
});
return proxy;
};
}

  • Extract ^ to a utility (it should be "primed and ready" I think)
  • Update custom modifier manager to do something more like this:

let namedArgsProxy = namedArgsProxyFor(named);
let component = delegate.createComponent(definition.ComponentClass.class, {
named: namedArgsProxy,
positional: reifyPositional(positional),
});

Instead of always reifying:

registerDestructor(this, () => delegate.destroyModifier(modifier, reifyArgs(args)));

@rwjblue
Copy link
Member

rwjblue commented Sep 28, 2020

I've pushed up a quick stab of this over in #19163

NullVoxPopuli added a commit to NullVoxPopuli/ember-render-modifiers that referenced this issue Oct 15, 2020
BREAKING CHANGE: destruction behavior changed to use new args proxy from 3.22

Resolves the issue
  described by: emberjs/ember.js#19162
  fixed by: emberjs/ember.js#19163
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 16, 2020
BREAKING CHANGE: destruction behavior changed to use new args proxy from
3.22

Resolves the issue
  described by: emberjs/ember.js#19162
  fixed by: emberjs/ember.js#19163
NullVoxPopuli added a commit to NullVoxPopuli/ember-render-modifiers that referenced this issue Oct 16, 2020
BREAKING CHANGE: destruction behavior changed to use new args proxy from 3.22

Resolves the issue
  described by: emberjs/ember.js#19162
  fixed by: emberjs/ember.js#19163
NullVoxPopuli added a commit to NullVoxPopuli/ember-render-modifiers that referenced this issue Oct 16, 2020
BREAKING CHANGE: destruction behavior changed to use new args proxy from 3.22

Resolves the issue
  described by: emberjs/ember.js#19162
  fixed by: emberjs/ember.js#19163

Ember apps < 3.22 are unaffected.

This also adds Ember Try support for LTS 3.8 through 3.20
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 16, 2020
BREAKING CHANGE: destruction behavior changed to use new args proxy from
3.22

Resolves the issue
  described by: emberjs/ember.js#19162
  fixed by: emberjs/ember.js#19163
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 16, 2020
BREAKING CHANGE: destruction behavior changed to use new args proxy from
3.22

Resolves the issue
  described by: emberjs/ember.js#19162
  fixed by: emberjs/ember.js#19163
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 16, 2020
BREAKING CHANGE: destruction behavior changed to use new args proxy from
3.22

Resolves the issue
  described by: emberjs/ember.js#19162
  fixed by: emberjs/ember.js#19163
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 16, 2020
BREAKING CHANGE: destruction behavior changed to use new args proxy from
3.22

Resolves the issue
  described by: emberjs/ember.js#19162
  fixed by: emberjs/ember.js#19163
NullVoxPopuli added a commit to NullVoxPopuli/ember-render-modifiers that referenced this issue Oct 18, 2020
BREAKING CHANGE: destruction behavior changed to use new args proxy from 3.22

Resolves the issue
  described by: emberjs/ember.js#19162
  fixed by: emberjs/ember.js#19163

Ember apps < 3.22 are unaffected.

This also adds Ember Try support for LTS 3.8 through 3.20
NullVoxPopuli added a commit to NullVoxPopuli/ember-render-modifiers that referenced this issue Oct 18, 2020
BREAKING CHANGE: destruction behavior changed to use new args proxy from 3.22

Resolves the issue
  described by: emberjs/ember.js#19162
  fixed by: emberjs/ember.js#19163
NullVoxPopuli added a commit to NullVoxPopuli/ember-render-modifiers that referenced this issue Oct 18, 2020
NullVoxPopuli added a commit to NullVoxPopuli/ember-render-modifiers that referenced this issue Oct 18, 2020
BREAKING CHANGE: destruction behavior changed to use new args proxy from 3.22

Resolves the issue
  described by: emberjs/ember.js#19162
  fixed by: emberjs/ember.js#19163
NullVoxPopuli added a commit to NullVoxPopuli/ember-render-modifiers that referenced this issue Oct 18, 2020
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 18, 2020
BREAKING CHANGE: destruction behavior changed to use new args proxy from
3.22

Resolves the issue
  described by: emberjs/ember.js#19162
  fixed by: emberjs/ember.js#19163
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 18, 2020
BREAKING CHANGE: destruction behavior changed to use new args proxy from
3.22

Resolves the issue
  described by: emberjs/ember.js#19162
  fixed by: emberjs/ember.js#19163
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 18, 2020
BREAKING CHANGE: destruction behavior changed to use new args proxy from
3.22

Resolves the issue
  described by: emberjs/ember.js#19162
  fixed by: emberjs/ember.js#19163
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 18, 2020
BREAKING CHANGE: destruction behavior changed to use new args proxy from
3.22

Resolves the issue
  described by: emberjs/ember.js#19162
  fixed by: emberjs/ember.js#19163
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 18, 2020
BREAKING CHANGE: destruction behavior changed to use new args proxy from
3.22

Resolves the issue
  described by: emberjs/ember.js#19162
  fixed by: emberjs/ember.js#19163
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 18, 2020
NullVoxPopuli added a commit to NullVoxPopuli/ember-render-modifiers that referenced this issue Oct 19, 2020
BREAKING CHANGE: destruction behavior changed to use new args proxy from 3.22

Resolves the issue
  described by: emberjs/ember.js#19162
  fixed by: emberjs/ember.js#19163
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 19, 2020
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 19, 2020
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 19, 2020
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 20, 2020
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 20, 2020
BREAKING CHANGE: destruction behavior changed to use new args proxy from
3.22.
This is only a breaking change for Ember 3.22+. Ember < 3.22 is
  unaffected.

Resolves the issue
  described by: emberjs/ember.js#19162
  fixed by: emberjs/ember.js#19163
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 20, 2020
BREAKING CHANGE: destruction behavior changed to use new args proxy from
3.22

Resolves the issue
  described by: emberjs/ember.js#19162
  fixed by: emberjs/ember.js#19163
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 20, 2020
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 20, 2020
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 20, 2020
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 20, 2020
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 20, 2020
NullVoxPopuli added a commit to NullVoxPopuli/ember-modifier that referenced this issue Oct 20, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants