ember-source
Version:
A JavaScript framework for creating ambitious web applications
182 lines (166 loc) • 6.62 kB
JavaScript
import EmberObject from '../object/index.js';
import RegistryProxyMixin from '../-internals/runtime/lib/mixins/registry_proxy.js';
import ContainerProxyMixin from '../-internals/runtime/lib/mixins/container_proxy.js';
import '../-internals/runtime/lib/mixins/comparable.js';
import '../-internals/runtime/lib/mixins/action_handler.js';
import '../-internals/runtime/lib/mixins/-proxy.js';
import '../enumerable/mutable.js';
import '../-internals/runtime/lib/mixins/target_action_support.js';
import '../-internals/runtime/lib/ext/rsvp.js';
import '../debug/index.js';
import { R as Registry, p as privatize } from '../../shared-chunks/registry-B8WARvkP.js';
import { g as guidFor } from '../../shared-chunks/mandatory-setter-BiXq-dpN.js';
import { isDevelopingApp } from '@embroider/macros';
import { ENGINE_PARENT, getEngineParent, setEngineParent } from './lib/engine-parent.js';
import { isFactory } from '../-internals/owner/index.js';
import { assert } from '../debug/lib/assert.js';
import { R as RSVP } from '../../shared-chunks/rsvp-DaQAFb0W.js';
/**
@module @ember/engine
*/
class EngineInstance extends EmberObject.extend(RegistryProxyMixin, ContainerProxyMixin) {
/**
@private
@method setupRegistry
@param {Registry} registry
@param {BootOptions} options
*/
// This is effectively an "abstract" method: it defines the contract a
// subclass (e.g. `ApplicationInstance`) must follow to implement this
// behavior, but an `EngineInstance` has no behavior of its own here.
static setupRegistry(_registry, _options) {}
/**
The base `Engine` for which this is an instance.
@property {Engine} engine
@private
*/
[ENGINE_PARENT];
_booted = false;
init(properties) {
super.init(properties);
// Ensure the guid gets setup for this instance
guidFor(this);
this.base ??= this.application;
// Create a per-instance registry that will use the application's registry
// as a fallback for resolving registrations.
let registry = this.__registry__ = new Registry({
fallback: this.base.__registry__
});
// Create a per-instance container from the instance's registry
this.__container__ = registry.container({
owner: this
});
this._booted = false;
}
_bootPromise = null;
/**
Initialize the `EngineInstance` and return a promise that resolves
with the instance itself when the boot process is complete.
The primary task here is to run any registered instance initializers.
See the documentation on `BootOptions` for the options it takes.
@public
@method boot
@param options {Object}
@return {Promise<EngineInstance,Error>}
*/
boot(options) {
if (this._bootPromise) {
return this._bootPromise;
}
this._bootPromise = new RSVP.Promise(resolve => {
resolve(this._bootSync(options));
});
return this._bootPromise;
}
/**
Unfortunately, a lot of existing code assumes booting an instance is
synchronous – specifically, a lot of tests assume the last call to
`app.advanceReadiness()` or `app.reset()` will result in a new instance
being fully-booted when the current runloop completes.
We would like new code (like the `visit` API) to stop making this
assumption, so we created the asynchronous version above that returns a
promise. But until we have migrated all the code, we would have to expose
this method for use *internally* in places where we need to boot an instance
synchronously.
@private
*/
_bootSync(options) {
if (this._booted) {
return this;
}
(isDevelopingApp() && !(getEngineParent(this)) && assert("An engine instance's parent must be set via `setEngineParent(engine, parent)` prior to calling `engine.boot()`.", getEngineParent(this)));
this.cloneParentDependencies();
this.setupRegistry(options);
this.base.runInstanceInitializers(this);
this._booted = true;
return this;
}
setupRegistry(options = this.__container__.lookup('-environment:main')) {
this.constructor.setupRegistry(this.__registry__, options);
}
/**
Unregister a factory.
Overrides `RegistryProxy#unregister` in order to clear any cached instances
of the unregistered factory.
@public
@method unregister
@param {String} fullName
*/
unregister(fullName) {
this.__container__.reset(fullName);
// We overwrote this method from RegistryProxyMixin.
this.__registry__.unregister(fullName);
}
/**
Build a new `EngineInstance` that's a child of this instance.
Engines must be registered by name with their parent engine
(or application).
@private
@method buildChildEngineInstance
@param name {String} the registered name of the engine.
@param options {Object} options provided to the engine instance.
@return {EngineInstance,Error}
*/
buildChildEngineInstance(name, options = {}) {
let ChildEngine = this.lookup(`engine:${name}`);
if (!ChildEngine) {
throw new Error(`You attempted to mount the engine '${name}', but it is not registered with its parent.`);
}
let engineInstance = ChildEngine.buildInstance(options);
setEngineParent(engineInstance, this);
return engineInstance;
}
/**
Clone dependencies shared between an engine instance and its parent.
@private
@method cloneParentDependencies
*/
cloneParentDependencies() {
const parent = getEngineParent(this);
(isDevelopingApp() && !(parent) && assert('expected parent', parent));
let registrations = ['route:basic', 'service:-routing'];
registrations.forEach(key => {
let registration = parent.resolveRegistration(key);
(isDevelopingApp() && !(isFactory(registration)) && assert('expected registration to be a factory', isFactory(registration)));
this.register(key, registration);
});
let env = parent.lookup('-environment:main');
this.register('-environment:main', env, {
instantiate: false
});
// The type annotation forces TS to (a) validate that these match and (b)
// *notice* that they match, e.g. below on the `singletons.push()`.
let singletons = ['router:main', privatize`-bucket-cache:main`, '-view-registry:main', `renderer:-dom`, 'service:-document'];
if (env['isInteractive']) {
singletons.push('event_dispatcher:main');
}
singletons.forEach(key => {
// SAFETY: We already expect this to be a singleton
let singleton = parent.lookup(key);
this.register(key, singleton, {
instantiate: false
});
});
}
}
export { EngineInstance as default };