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
Allow for lazy registration of app components #15
Conversation
Well that's a surprisingly small change. I think the concern previously was cross-product hooks, particularly with Auth. Have you tried this out with loading the Database first, then Auth later? So 1) load DB, read some data that doesn't require auth 2) load auth lazily, load some data from the DB that does require auth. |
@mbleigh So the cross-product hooks that you mentioned are all taken care of. However I did update my JSBin to demo your use case and discovered that the stub methods that AFAICT none of the other products have this issue though as they all call into |
@jshcrowthe I was trying to figure out a way to auto inititializeApp when hosted on Firebase. I was trying this through Polymerfire but the issue @mbleigh brought up was lazy loading the elements. Would it be appropriate to add a test to your PR to see if |
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 think my main question/concern at this point is that there is now auth-specific code hanging out in FirebaseApp. Is there any way to expose hooks in FirebaseApp that are instead consumed by Firebase Auth in such a way that the same behavior happens Right now this feels like an awesome proof-of-concept but not necessarily a stable foundation.
The reason why I followed this approach is largely based on how modules are currently required to register with Firebase App. We are currently stubbing some auth-specific code that is required by certain modules (i.e. Once we have access to the Auth source in this repo, a refactoring of this code (i.e. removal of this code) and the associated database/storage code, should happen that would extract this specific module into a standalone piece. This piece would stub the API and provide each component (in this case auth) the ability to control how this functionality is "upgraded." That said, with how we currently distribute to CDNs, the "stubbing" module would be included with I will add a comment to explain the need for this and a "todo" to remove it once able? |
src/app/firebase_app.ts
Outdated
private services_: {[name: string]: | ||
{[instance: string]: FirebaseService}} = {}; | ||
public INTERNAL: FirebaseAppInternals; | ||
private _registeredServices: { |
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 doesn't seem to be used. Also, you seem to be prefixing private variables and methods with an underscore. This is not the convention here. I think we should stick to one format. In general the typescript convention is not to use leading or lagging underscores for private variables/methods.
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.
Good catch I'll remove this. It was an artifact of an old implementation.
src/app/firebase_app.ts
Outdated
*/ | ||
private getService(name: string, instanceString?: string): FirebaseService |
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.
What happened to this? By not tracking this, I think you will return the same default instance for storage. I think you could have multiple instances of storage with different buckets.
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 see what you mean! I'm surprised we don't have a test for this. I'll revert the majority of this change (it didn't seem used so it was a clean up thing) and add a test to make sure we don't break that going forward! I will need to keep the removal of the private
keyword as we need to call this outside of this classes implementation details.
src/app/firebase_app.ts
Outdated
if (appHook) { | ||
appHooks[name] = appHook; | ||
getApps().forEach(app => { | ||
appHook('create', app); |
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 will trigger every time a service is registered to the same app. Even when it was previously already triggered. Not sure what the implications are for other services listening to this.
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 two functions that currently will trigger the create
hook for services are initializeApp
and registerService
. Though they are similar they are slightly different and actual have no overlap in terms of when appHooks are fired.
initializeApp
attempts to run appHooks for each factory that exists at the time of app initialization.registerService
runs only the new appHook (if applicable) on all existing instances ofFirebaseApp
it then also registers itself as one of the available appHooks so that futureinitializeApp
calls will trigger the hook.
The create
hook will be run once, and only once, per instance of Firebase App. Because one can't register multiple services of the same name, attempting to register multiple appHooks under the same name, and trigger a duplicate function call, should fail with an error long before the call is ever attempted.
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 AM EXCITE!
However, folks more familiar with the SDK should approve before this gets merged. Let's make sure we do a minor version bump when this gets released.
There was an issue where we weren't properly limiting mulitple service instances. Added a test and refactored the code to support that use case
@bojeil-google I have addressed some of your concerns and added some tests to verify that functionality stays functioning (turns out there was an alternate way to do it that I brought into compliance with the API). |
@justweb1 I don't know if this PR is the right place to add that type of functionality. That said, I think if we were to do something explicit (i.e. something like allowing people to call However I can also see this type of functionality being a nice thing for an abstraction layer like Polymerfire, angularfire, etc. Thoughts? |
The thought was originally for it to be built into Polymerfire and I had even started work on it. After talking with @mbleigh, it seems to make more sense to run it directly in the sdk. One of the main issues I was having was due to the fact I needed lazy loading. Then, when it boils down to it placing a check and calling the init.js directly from the sdk is a much better option and will require way less code. This will also simplify the initialization and improve user experience. |
@justweb1 let's open a separate issue for this and continue discussion there. |
src/app/firebase_app.ts
Outdated
* otherwise, return a proxied reference to the same service | ||
*/ | ||
factories[name] = allowMultipleInstances ? createService : | ||
(...args) => createService.apply(this, args); |
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.
Seems like this ternary statement isn't really doing anything? Before, the if statement was always sending DEFAULT_ENTRY_NAME instead of the passed instanceString if !allowMultipleInstances
, but since you handle the multiple instance name thing further down now (inside the function assigned to FirebaseAppImpl.prototype[name]
), you should just be able to do
factories[name] = createService;
and have it work, right?
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.
Great point, I think I had meant to do this, but never actually did it. Fixed!
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.
LGTM
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.
If you make @sphippen happy, then I'm happy. Let's get this in 💥
FirebaseAppImpl.prototype[name] = function(...args) { | ||
const serviceFxn = this._getService.bind(this, name); | ||
return serviceFxn.apply(this, allowMultipleInstances ? args : []); | ||
} |
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's late, maybe I'm misreading:
So this will patch in the service factory: app.storage(instanceString);
Will this pass instanceString to createService(app, extendApp, instanceString)
?
This statement, as quoted from above, doesn't pass the instance string.
let service = this.firebase_.INTERNAL.factories[name](this, this.extendApp.bind(this));
If you update the test below where I commented to confirm this, then you are good to go.
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.
Great catch on this. I have refactored to ensure that we are passing through the instanceIdentifier
I must have missed it in adding code back.
In addition I have added an additional assertion to two of the tests below to ensure that we don't break this going forward.
const service2 = (firebase.app() as any).multiInstance(serviceIdentifier); | ||
assert.strictEqual(service2, (firebase.app() as any).multiInstance(serviceIdentifier)); | ||
|
||
// Ensure that the two services **are not equal** |
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 doesn't check that the two difference services are getting their identifier passed to the underlying service constructor.
For Storage, this is:
function factory(app: FirebaseApp, unused: any, opt_url?: string): Service {
return new Service(app, new XhrIoPool(), opt_url);
}
opt_url
needs to be passed through.
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.
Gotcha, thanks!
Fixed an issue where instance identifiers weren't being passed to their creation factories. Added some tests to validate that we don't accidentally remove that in the future.
@bojeil-google I have responded to your last two comments. Thanks sir! We should be good to go. I'll get this merged. |
Looks good. Thanks! |
* WIP: add lazy module instantiation * feat(app): allow for lazy loading of services * test(app): add integration test for webpack/browserify for lazy instantiation * fix(app): fix issue where auth listeners weren't being patched properly * docs(app): adding doc explaining Firebase Auth specific code in firebase_app.ts * fix(app): revert code refactor to _getService function * test(app): add tests and fix issue with multiple instances of a service There was an issue where we weren't properly limiting mulitple service instances. Added a test and refactored the code to support that use case * refactor(app): remove unneeded ternary * fix(app): fix issue where instanceIdentifier wasn't being passed Fixed an issue where instance identifiers weren't being passed to their creation factories. Added some tests to validate that we don't accidentally remove that in the future.
Fixes #14
Live demo available here:
http://jsbin.com/zozuqeh/edit?js,console