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

[FEATURE factory-for] Implement factoryFor #14360

Merged
merged 6 commits into from
Dec 18, 2016

Conversation

chadhietala
Copy link
Contributor

@chadhietala chadhietala commented Sep 26, 2016

Implements lookup.factoryFor to avoid double extending all objects.

See RFC

@rwjblue
Copy link
Member

rwjblue commented Sep 26, 2016

/cc @mixonic

@rwjblue
Copy link
Member

rwjblue commented Sep 28, 2016

Haven't had a chance to review in detail, but I did notice that this still needs feature flagging.

@chadhietala chadhietala force-pushed the double-extend branch 3 times, most recently from c89e3fd to 6d1d689 Compare September 28, 2016 05:00
@chadhietala
Copy link
Contributor Author

chadhietala commented Sep 28, 2016

@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.

@rwjblue
Copy link
Member

rwjblue commented Sep 28, 2016

Currently no feature flagging has been done because these are effectively internal changes with no user-land implications.

O_o, isn't the whole point of the RFC to expose this as public API?

@mixonic
Copy link
Member

mixonic commented Sep 28, 2016

@chadhietala

inject the deprecated container in this new public API

I'm not quite sure what this means, the container is not deprecated. Will review here.

@homu
Copy link
Contributor

homu commented Sep 30, 2016

☔ The latest upstream changes (presumably #14355) made this pull request unmergeable. Please resolve the merge conflicts.

@chadhietala chadhietala force-pushed the double-extend branch 6 times, most recently from 0d270ce to d649c52 Compare October 4, 2016 21:47
@homu
Copy link
Contributor

homu commented Oct 6, 2016

☔ The latest upstream changes (presumably #14428) made this pull request unmergeable. Please resolve the merge conflicts.

@chadhietala chadhietala force-pushed the double-extend branch 3 times, most recently from c46fd47 to f3ecc62 Compare October 15, 2016 19:44
@chadhietala chadhietala changed the title [WIP] Feature factoryFor [Feature] FactoryFor Oct 16, 2016
Copy link
Member

@mixonic mixonic left a 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;
Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually do not understand how the type is used here. It's passed around all over the place, but isn't used for any of the output.

Copy link
Member

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.

Copy link
Member

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 });
Copy link
Member

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 = {}) {
Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Member

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);
Copy link
Member

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);
Copy link
Member

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);
Copy link
Member

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.

Copy link
Member

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' });
Copy link
Member

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(() => {
Copy link
Member

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:

@@ -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() {
Copy link
Member

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);
Copy link
Member

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 or create throws an error
  • setting a property throws an error
  • calling instanceof with the return value from factoryFor throws an error

Per: https://github.com/emberjs/rfcs/blob/master/text/0000-factoryFor.md#development-mode-proxy

@homu
Copy link
Contributor

homu commented Oct 17, 2016

☔ The latest upstream changes (presumably #14487) made this pull request unmergeable. Please resolve the merge conflicts.

@chadhietala chadhietala force-pushed the double-extend branch 3 times, most recently from c993e4b to f6dfba0 Compare October 24, 2016 20:37
@chadhietala
Copy link
Contributor Author

I made some changes to this, but likely needs further guidance.

@homu
Copy link
Contributor

homu commented Oct 30, 2016

☔ The latest upstream changes (presumably #14545) made this pull request unmergeable. Please resolve the merge conflicts.

@chadhietala chadhietala force-pushed the double-extend branch 2 times, most recently from 3a63382 to 71c9815 Compare December 18, 2016 02:45
@chadhietala chadhietala changed the title [Feature] FactoryFor [FEATURE factory-for] Implement factoryFor Dec 18, 2016
@chadhietala
Copy link
Contributor Author

chadhietala commented Dec 18, 2016

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)
screen shot 2016-12-17 at 7 31 51 pm
screen shot 2016-12-17 at 7 47 57 pm

After: (Prod / Feature Flags On)
About 100ms reduction
screen shot 2016-12-17 at 7 32 51 pm
About 200ms reduction
screen shot 2016-12-17 at 7 47 01 pm

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.
@rwjblue
Copy link
Member

rwjblue commented Dec 18, 2016

@chadhietala - I assume the After: (Prod / Feature Flags On) screenshots above are with both ember-factory-for and ember-no-double-extend flags on, is that right? Can you test with only ember-factory-for (I'm mostly looking to try to figure out if we will have temporary regressions in 2.12)?

@@ -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) {
Copy link
Member

@rwjblue rwjblue Dec 18, 2016

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) {
Copy link
Member

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';
Copy link
Member

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__;
Copy link
Member

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.

@mixonic mixonic dismissed stale reviews from rwjblue and stefanpenner December 18, 2016 21:35

I've made Robert's final requested change

@mixonic
Copy link
Member

mixonic commented Dec 18, 2016

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:

screen shot 2016-12-18 at 2 19 48 pm

The ember-factory-for feature flag does not seem to have a significant impact on performance (which is expected).

However the ember-no-double-extend feature flag makes a significant improvement.

screen shot 2016-12-18 at 2 22 14 pm

I think we can continue to explore issues around backwards compatibility, performance, and the migration path to factoryFor on master. Merging this.

@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.

@mixonic mixonic merged commit ef8f46e into emberjs:master Dec 18, 2016
@homu homu mentioned this pull request Dec 18, 2016
@chadhietala chadhietala deleted the double-extend branch December 18, 2016 22:28
@stefanpenner
Copy link
Member

👍

@sandstrom
Copy link
Contributor

sandstrom commented Dec 19, 2016

@chadhietala Awesome! Nice to be able to move into a public API for this + great performance improvements! ⛵️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants