-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
[FEATURE factory-for] Implement factoryFor #14360
Conversation
fed86bf
to
ef5301e
Compare
/cc @mixonic |
Haven't had a chance to review in detail, but I did notice that this still needs feature flagging. |
c89e3fd
to
6d1d689
Compare
@stefanpenner I think this is good for first round of review. Currently no feature flagging has been done because these are effectively internal changes with no user-land implications. One outstanding thing is if we need to inject the deprecated container in this new public API. |
6d1d689
to
b732ef5
Compare
O_o, isn't the whole point of the RFC to expose this as public API? |
I'm not quite sure what this means, the container is not deprecated. Will review here. |
☔ The latest upstream changes (presumably #14355) made this pull request unmergeable. Please resolve the merge conflicts. |
0d270ce
to
d649c52
Compare
☔ The latest upstream changes (presumably #14428) made this pull request unmergeable. Please resolve the merge conflicts. |
c46fd47
to
f3ecc62
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks so much for getting this going @chadhietala. The big item is that lookup
should still be using double extend. If you want to add a second feature flag (ideally in a followup) that experimentally switches lookup
to the non-double-extended factory, that would definitely be welcome as a distinct feature flag.
@@ -163,7 +163,7 @@ export default EmberObject.extend({ | |||
|
|||
_nameToClass(type) { | |||
if (typeof type === 'string') { | |||
type = getOwner(this)._lookupFactory(`model:${type}`); | |||
type = getOwner(this).factoryFor(`model:${type}`).class; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a logic change happening here- Where the old version was returning the double-extended class now the registered class is being returned. How does that impact the logic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm seems like backwards-compat for pre-1.13 per this hook implementation. Super unfortunate that any of the class stuff is still used that way. Seems like it needs a deprecation cycle in ember-data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems there is some use of the types in Ember-Data. For example here attributes are read off the class. Removing the double-extend means the attributes
CP would be called and memoized on the base class, and thus shared between test runs etc.
This is definitely a change, described in the design section.
Some other examples of libraries this would impact are listed in the drawbacks section.
@@ -75,7 +75,8 @@ export default class Environment extends GlimmerEnvironment { | |||
|
|||
this._definitionCache = new Cache(2000, ({ name, source, owner }) => { | |||
let { component: ComponentClass, layout } = lookupComponent(owner, name, { source }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ComponentClass
seems a poor name now. componentFactory
if you intended to pass the whole thing through instead of just the class?
|
||
let manager = { | ||
class: factory, | ||
create(options = {}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I expected the implementation here to be
let factory = this._lookupFactory(name);
return factory.create(options);
for the initial pass of factoryFor
create
per https://github.com/emberjs/rfcs/blob/master/text/0000-factoryFor.md#detailed-design. Additionally https://github.com/emberjs/rfcs/blob/master/text/0000-factoryFor.md#development-mode-proxy. In practice literally implementing as _lookupFactory(name).create(props)
might not work, but it seems like it should and would preserve the double-extend behavior during instance creation. We should decide if this is really needed, will raise it in some conversations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if I follow. Can we not just transition all the internal stuff over to factoryFor
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The intent was to encourage libs/apps to use the new API and drop their current reliance on the double-extended class, then change over internals and any other small breaking changes that will happen after most apps have refactored toward factoryFor
and stopped expecting a double-extend.
if (isSingleton(container, fullName) && options.singleton !== false) { | ||
container.cache[fullName] = value; | ||
function isFactoryClass(container, fullname, { instantiate, singleton }) { | ||
return (!isSingleton(container, fullname) || singleton === false) && (!shouldInstantiate(container, fullname) && instantiate === false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
putting the faster checks first seems ideal:
function isFactoryClass(container, fullname, { instantiate, singleton }) {
return (
(singleton === false || !isSingleton(container, fullname)) &&
(instantiate === false || !shouldInstantiate(container, fullname))
);
}
thus avoiding the function calls in many cases.
if (isFeatureEnabled('container-factoryFor')) { | ||
deprecate('Using "_lookupFactory" is deprecated. Please use container.factoryFor instead.', false, { id: 'container-lookupFactory', until: '2.12.0', url: 'TODO' }); | ||
} | ||
|
||
return factoryFor(this, this.registry.normalize(fullName), options); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can this function be renamed now, since it is not the factoryFor
one would normally be looking for here.
@@ -191,15 +280,47 @@ function lookup(container, fullName, options = {}) { | |||
return container.cache[fullName]; | |||
} | |||
|
|||
let value = instantiate(container, fullName); | |||
return instantiateFactory(container, fullName, options); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this meanscontainer.lookup
is now based on the the non-double-extended factory? This seems like the behavior we didn't intend to change until _lookupFactory
is removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, we should only have 1 behavior in the system at any point in time, once we cut over it should be all no double extend.
@@ -127,9 +135,86 @@ Container.prototype = { | |||
*/ | |||
lookupFactory(fullName, options) { | |||
assert('fullName must be a proper full name', this.registry.validateFullName(fullName)); | |||
|
|||
if (isFeatureEnabled('container-factoryFor')) { | |||
deprecate('Using "_lookupFactory" is deprecated. Please use container.factoryFor instead.', false, { id: 'container-lookupFactory', until: '2.12.0', url: 'TODO' }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Must be until: '2.13'
. 2.12 is an LTS release and must contain this deprecation.
function lookupFactory(name, container, options) { | ||
let factory; | ||
if (isFeatureEnabled('container-factoryFor')) { | ||
expectDeprecation(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is also an ignoreDeprecation
which might be appropriate/useful here. Example:
ignoreDeprecation(function() { |
@@ -561,7 +581,8 @@ QUnit.test('A deprecated `container` property is appended to every object instan | |||
}, 'Using the injected `container` is deprecated. Please use the `getOwner` helper instead to access the owner of this object.'); | |||
}); | |||
|
|||
QUnit.test('A deprecated `container` property is appended to every object instantiated from a non-extendable factory, and a fake container is available during instantiation.', function() { | |||
// This is testing that container was passed as an option | |||
QUnit.skip('A deprecated `container` property is appended to every object instantiated from a non-extendable factory, and a fake container is available during instantiation.', function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test shows why we need to keep the "double extend" the default path for lookup
until at least 2.13.
throw new Error(`You attempted to set "${prop}" on a factory manager created by container#factoryFor. A factory manager is a read-only construct.`); | ||
} | ||
}; | ||
manager = new Proxy(manager, validator); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would like to have tests included that:
- getting a property that is not
class
orcreate
throws an error - setting a property throws an error
- calling
instanceof
with the return value fromfactoryFor
throws an error
☔ The latest upstream changes (presumably #14487) made this pull request unmergeable. Please resolve the merge conflicts. |
c993e4b
to
f6dfba0
Compare
I made some changes to this, but likely needs further guidance. |
☔ The latest upstream changes (presumably #14545) made this pull request unmergeable. Please resolve the merge conflicts. |
3a63382
to
71c9815
Compare
Quick profiles from Travis-Web. This is consistent across runs. Looked at the homepage and build status pages. Benefits are bound to number of objects created. Before: (Prod / Feature Flags Off) After: (Prod / Feature Flags On) |
The public factoryFor API should always return `class` as the no-double-extend version. However internal APIs already refactored to use FACTORY_FOR should use the double extended version if the no-double-extend feature flag is false.
71c9815
to
bac3a08
Compare
@chadhietala - I assume the |
@@ -194,9 +194,25 @@ const EngineInstance = EmberObject.extend(RegistryProxyMixin, ContainerProxyMixi | |||
|
|||
this.inject('view', '_environment', '-environment:main'); | |||
this.inject('route', '_environment', '-environment:main'); | |||
}, | |||
|
|||
[FACTORY_FOR](fullName, options) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets make this part of the ContainerProxyMixin
, this would simplify issues in ember-test-helpers
land and actually keep the logic in the correct place (currently .lookup
and ._lookupFactory
are defined in ContainerProxyMixin
so that seems like the logical place).
} | ||
}); | ||
|
||
if (isFeatureEnabled('ember-factory-for')) { | ||
EngineInstance.reopen({ | ||
factoryFor(fullName, options) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be moved to the mixin also...
|
||
const CONTAINER_OVERRIDE = symbol('CONTAINER_OVERRIDE'); | ||
const HAS_PROXY = typeof Proxy === 'function'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we move this into ember-utils
and export as HAS_NATIVE_PROXY
? Very similar to what was done in #14649 for packages/ember-utils/lib/weak-map-utils.js
.
@@ -1,5 +1,14 @@ | |||
module.exports = function buildOwner(Ember, resolver) { | |||
var Owner = Ember.Object.extend(Ember._RegistryProxyMixin, Ember._ContainerProxyMixin); | |||
var FACTORY_FOR = Ember.Container.__FACTORY_FOR__; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comments in #14360 (review) and emberjs/ember-test-helpers#191, we should be able to remove this.
155e807
to
aefb6ed
Compare
I've made Robert's final requested change
I've done some benchmarks against todomvc. I would expect the impact on a larger app to be much more significant (as @chadhietala's profiling shows). This PR may have a minor performance regression: The However the I think we can continue to explore issues around backwards compatibility, performance, and the migration path to @chadhietala Thanks much for your patience and push on this gnarly, complex, cross-cutting part of Ember's API. The payoff (especially in big apps) is going to be really notable. |
👍 |
@chadhietala Awesome! Nice to be able to move into a public API for this + great performance improvements! ⛵️ |
Implements
lookup.factoryFor
to avoid double extending all objects.See RFC