Reopens within Engines
See original GitHub issueCurrently, there’s no clear path around the use of reopen
or reopenClass
that originates from an Engine. As it stands, one can implement an initializer inside an Engine that performs a reopen
, and the effect of that initializer can be the modification of globals.
Furthermore, if Engines are loaded lazily, we get into further problems where instances have already been created from classes that are ultimately reopened when an engine loads. At that point, the behavior of the app will be unknown. Additionally, modifying globals at some future point but maybe not at all seems like it’s behavior we don’t want.
One concrete example of the need to reopen
is modifying link-to
. We currently need to reopen
this component to enable data attribute bindings. Reopening is preferable to extending as we want to, as best as possible, retain the ability to use well-known, public components rather than require that our Engineers use custom components.
One possibility that our team has discussed is having an explicit root level initializer that runs all reopen
s. This will ensure that we have all modifications to global state in one place, that they run at a point-in-time that’s relatively predictable, and that they’re remove the potential for child engines either modifying global state themselves, or overwriting another engine’s modification of global state.
This initializer could look like this:
// global-reopens-initializer.js
import Ember from 'ember';
// import specific reopen functionality
import LinkToReopen from 'global-reopens/link-to-reopen';
function initialize() {
LinkToReopen.patch();
}
...
// global-reopens/link-to-reopen.js
import Ember from 'ember';
const { LinkComponent } = Ember;
export default {
patch() {
LinkComponent.reopen({
// do reopen stuff here
});
}
}
Thoughts on this approach?
Issue Analytics
- State:
- Created 7 years ago
- Comments:5 (4 by maintainers)
Top GitHub Comments
Looks like this issue/discussion is still opened, so I thought I would add an use-case I found within our app at Ally, that had an unexpected outcome.
We have a large, legacy section of our codebase that I am refactoring to an engine. The consuming app has a global mechanism to track metrics by
reopen
ingEmber.ActionHandler#send
, so throughout the app a component can simply callthis.send('trackSomeCustomEvent')
. The legacy section being converted to an engine, unfortunately, has an older metrics implementation that alsoreopen
sEmber.ActionHandler#send
:What I found was both initializers ran and reopened the
ActionHandler
as expected with no errors, but in the engine when tracking events (i.e.this.send('trackSomeEvent')
), ONLY the consuming appssend
was ran. Eventually an error would be thrown stating nothing handled the action. I thought at least bothsend
methods would run in the reverse order they were added (as long as callingsuper()
).This is mostly tech debt within our app and obviously something you would not want to do, but thought I would share as it illustrates some usage/need to reopen the same class within the same application cross engines.
As a “doctor, it hurts when I do this” “then stop doing it!” fix I would propose that we lock the objects after
initializer
s have been run. This means that anything lazy-loaded or inside of a component can’t blow us up. This would make it much safer, but I don’t know if it actually is compatible with the Ember ecosystem.Something like
#freeze
could be interesting too.