How does Ember Boot?

Written by trojanh | Published 2017/06/03
Tech Story Tags: javascript | ember | emberjs

TLDRvia the TL;DR App

Ember has evolved from being the SproutCore 2.0 framework to a modern trusted Javascript framework used by major tech companies like Apple, LinkedIn and Vine, ever since it was born. It was developed by a Rails developer Yehuda Katz, so it believes in Convention over Configuration. Ember community not only is very supportive but also follows six week release cycle ensuring security and features which keep making it best framework to consider. It tries to stay as much backward compatible as it can so that with every new major release you don’t have to waste a lot of time learning new ground breaking things and syntax. I was working on my Ember project and it was taking a lot of time to boot which wasn’t a usual thing and before digging for its cause, this question hit me “How does Ember boot ?”. Though the reason for this extra time was a debugger on my API. It was enlightening to know how did ember boot.

I did some research, mainly looking at Ember git repository to find my answers. Knowing the Ember boot process, not only made my roots strong but also changed the way I looked at it.

It is beyond the scope of any article to cover the whole Ember boot process, to do that there needs to be a book but I can definitely brief things that made sense to me and were important mentioning. So let’s start …

The first thing that happens when you start any Ember application is the creation of Ember.Application instance, which is done by

window.App = Ember.Application.create();

The object that holds this instance is a global object and is created only once. Any method or variables in this class have global namespace as Ember.Application. While you can think of your Ember.Application as a container that holds the other classes in your application, there are several other responsibilities going on under-the-hood like Initialization, Event Delegation, and Routing.

The first method that is called when the instance is created is _bootSync() . It is called when domReady() is fired which happens after all the resources required for Ember to execute is downloaded and parsed.

Following code lists all the methods involved in Ember.Application boot process.

Ember.Application{//other methods and variables...._bootSync(){this.runInitializers();this.advanceReadiness(){this.didBecomeReady();}}....//other methods and variables....didBecomeReady(){if(this.autoboot){//instantiate Ember.ApplicationInstancelet instance = this.buildInstance();instance._bootSync();intance.startRouting();//or instance.handleURL(url)}}....//other methods and variables}

Ember.Application has following responsibilities :

  1. Initialization

The goal of initializers is to register dependencies and injections. They provide access to the internal registry, which organizes the different components of an Ember application.Additionally they provide a chance to access the instantiated application.

It runs only once. Because these initializers may load code, they are allowed to defer application readiness and advance it. When some asynchronous task needs to be performed before some initializer then deferReadiness() can be used which is used to control advanceReadiness() which is called only if deferReadiness() counter is set to zero. When we call deferReadiness(),the counter is set to decremented and becomes -1. Routing halts till deferReadiness() counter becomes zero. Once the asynchronous task finishes execution it, makes counter increment by 1 and if counter =0 then advanceReadiness() is called.

It is possible to add custom initializers on top of Ember , like so:

Ember.Application.initializer({name: ‘api-adapter’,initialize: function(application) {application.register(‘api-adapter:main’, ApiAdapter);}});

2. Event Delegation

Ember uses a technique called _event delegation_. This allows the framework to set up a global, shared event listener instead of requiring each view to do it manually.

For example, instead of each view registering its own mousedown listener on its associated element, Ember sets up a mousedown listener on the <body>. If a mousedown event occurs, Ember will look at the target of the event and start walking up the DOM node tree, finding corresponding views and invoking their mouseDown method as it goes.

Ember.Application has a number of default events that it listens for, as well as a mapping from lowercase events to camel-cased view method names. For example, the keypress event causes the keyPress method on the view to be called, the dblclick event causes doubleClick to be called, and so on.

If there is a bubbling browser event that Ember does not listen for by default, you can specify custom events and their corresponding view method names by setting the application’s customEvents property:

let App = Ember.Application.create({customEvents: {// add support for the paste eventpaste: 'paste'}});

To prevent Ember from setting up a listener for a default event, specify the event name with a `null` value in the `customEvents` property:

let App = Ember.Application.create({customEvents: {// prevent listeners for mouseenter/mouseleave eventsmouseenter: null,mouseleave: null}});

By default, the application sets up these event listeners on the document body. However, in cases where you are embedding an Ember application inside an existing page, you may want it to set up the listeners on an element inside the body.

For example, if only events inside a DOM element with the ID of ember-app should be delegated, set your application’s rootElement property:

let App = Ember.Application.create({rootElement: '#ember-app'});

The rootElement can be either a DOM element or a jQuery-compatible selector string. The Ember.EventDispatcher is responsible for delegating events to application’s views. The event dispatcher is created by the application at initialization time and sets up event listeners on the DOM element described by the application’s rootElement property.

Note that views appended to the DOM outside the root element will not receive events. If you specify a custom root element, make sure you only append views inside it!

3. Routing

Ember.Application not only creates your application routers but it also controls routing. By default, the router will begin trying to translate the current URL into application state once the browser emits the DOMContentReady event. If you need to defer routing, you can call the application’s deferReadiness() method. Once routing can begin, call the advanceReadiness() method. If there is any setup required before routing begins, you can implement a ready() method on your app that will be invoked immediately before routing begins.

These are the major tasks performed by Ember.Application initialization.

Once this is done and our application is in advanceReadiness() state , the next method which is called is didBecomeReady() . This leads us to next important thing which is Ember.ApplicationInstance initialization.

When we are talking about Ember.Application initialization, we are mostly dealing with registry of Ember , which happens before the container is created. Once that is done, Ember.ApplicationInstance is initialized. It encapsulates all of the stateful aspects of a running Application.

At a high-level, we break application boot into two distinct phases:  * Definition time, where all of the classes, templates, and other dependencies are loaded (typically in the browser). * Run time, where we begin executing the application once everything has loaded.

Registries are like class-level(static) members of the application which needs to be processed prior to instance creation. On the other hand, singletons reside in the containers. Registry is used to hold the stateless or static part while container is used to hold the stateful or dynamic part of the application.

Do note that each app instance maintains their own registry/container, so they will run in complete isolation by default.

Now that we know how _bootSync() works its time deal with this.didBecomeReady().

Ember.Application{//other methods and variables....didBecomeReady(){if(this.autoboot){//instantiate Ember.ApplicationInstancelet instance = this.buildInstance();instance._bootSync();intance.startRouting();//or instance.handleURL(url)}}....//other methods and variables}

In didBecomeReady(), we check this.autoboot . It is used to check whether the application should automatically start routing and render templates to the `rootElement` on DOM ready. While default by true, other environments such as FastBoot or a testing harness can set this property to `false` and control the precise timing and behavior of the boot process.

FastBoot is the special environment that is usually used for Server-Side Rendering. This setup allows you to run your Ember app in a server environment using Node.js and render its content into static HTML for SEO purposes.

1. Autoboot

this.autoboot is also handy when we want to wait for some resource to fetch before the rendering process starts. Just as advanceReadiness() is used to control the routing , in the same way this.autoboot is used to control rendering.

2. **Ember.ApplicationInstance** initialization

Once this.autoboot is set to true the next thing that happens is this.buildInstance() . It is used to initialize Ember.ApplicationInstance . Unlike Ember.Application which run only once during the boot process, Ember.AppplicationInstance is created multiple times. Just as we have constructors which gets called every time we create an instance, in the same way Ember.ApplicationInstance gets called many times. It ensures that all the registries have been loaded and all the instance variables are initialized. They are created and destroyed on per request basis. So this is the place where we might want to keep the sensitive data associated with our application as it might get exposed if kept in registry and will be accessible to all the instances unnecessarily.

3. **instance._bootSync()**

Just like it Ember.Application , we have _bootSync() method to setup the instance of Ember.ApplicationInstance. It has following code:

Ember.ApplicationInstance{//other methods and variables...._bootSync(){this.setupRegistry();//define root element//define locationthis.runInstanceIntializers(){if(isInteractive){this.setupEventDispatcher();}}}....//other methods and variables....startRouting() {let initialURL = get(this, 'initialURL');if (this.setupRouter()) {if (initialURL === undefined) {initialURL = get(this, 'location').getURL();}let initialTransition = this.handleURL(initialURL);}}....//other methods and variables}

this.setupRegistry() ensures that all the factories required by the instance is properly registered. If any of the required factories aren’t registered then it gets registered in this process.

this.runInstanceInitializers() is used to define root element and where the instance will be rendered. We can also specify what type of events to listen to detect navigation. Once the location of render and root element is known, instance initializers are used to initialize the instance.

isInteractive is a phase where the application is ready to listen to events mentioned in Event Delegation. If the application is still working on some fetching some resources its isInteractive is set to false thus no event dispatcher is working. When our application is ready to listen to events its isInteractive is set to true and event dispatcher is setup using setupEventDispatcher() . It sets up global event dispatcher which is capable of listening to all the events.

With this _bootSycn() is complete and the next thing that happens is routing.

4. **startRouting()**

Here, the routing of urls starts. The things that were set up for Routing during Ember.Application initialization is actually visible on the browser in this phase. Application is able to change the urls based on the application states (router.js).

It initializes the current router instance and sets up the change handling event listeners used by the instances `location` implementation. A property named initialURL will be used to determine the initial URL. If no value is found `/` will be used. On any state change, get(this, ‘location’).getURL() fetches the new url to which the browser needs to point and this.handleURL(initialURL) updates the url.

That’s all folks, hope this made understanding of Ember boot better.

Thanks for reading.

Source: Ember github page and Journey through Ember.js Glue: Booting Up by Mike North


Published by HackerNoon on 2017/06/03