Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions text/0573-app-boot-hooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
- Start Date: 2020-01-09
- Relevant Team(s): Ember.js
- RFC PR: https://github.com/emberjs/rfcs/pull/573
- Tracking:

# App Boot Hooks

## Summary

This RFC proposes adding two hooks in the `Application` class in `app.js` that, by default, load
and run initializers and instance-initializers. Here's an example of what this could look like:

```js
import { loadInitializers, loadInstanceInitializers } from 'ember-load-initializers';
import Application from '@ember/application';
import config from './config/environment';

export default class App extends Application {
onBoot() {
loadInitializers(this, config.modulePrefix);
this.runInitializers();
}
onInstanceBoot(appInstance) {
loadInstanceInitializers(this, config.modulePrefix)
this.runInstanceInitializers();
}
}
```

This implementation would be backwards compatible and would give users complete control of
*how* and *when* they want to run their initializers and it would allow the beginner experience to
be progressively enhanced.

## Motivation

- **Moving away from initializers**. The existing convention of initializer code comes from
Ruby on Rails (which has similar `config/initializers/*.rb` files). This technique works
and can be nice for organizing code, but it also has several downsides:

- bookkeeping code to determine the order of execution
- confusing `before`/`after` API to change order of execution
- implicit injections from addons by way of merging directories
- lack of support for blocking asynchronous behavior for instance-initializers (`{defer|advance}Readiness` exists for initializers)
- a poor onboarding experience and misses out on a teaching opportunity about how Ember Applications work.

By providing a way to hook into the existing, async boot sequence more directly,
application developers can write declarative, explicit code about how they want to
customize their application.
- **Blocking async code before routing**. Currently, the earliest place an application can write
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this currently achieved by deferReadiness? Can you describe what would the new alternative look like?

Copy link
Author

Choose a reason for hiding this comment

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

deferReadiness is not available for instance initializers and it's an awkward API to begin with when we could just use promises.

asynchronous blocking code is in the `beforeModel` hook of the `ApplicationRoute`.
In some cases, this is too late. For example, if an application needs to wait for an event
from its host environment e.g. an iframe or an electron shell, and use it to determine
the starting URL, it cannot do so.
- **First Transition**: Similarly, if one wants to add an event listener to the
Copy link
Contributor

Choose a reason for hiding this comment

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

There's discussion first transition should be handled by an instance-initializer
#680 (comment)

Will there be any recommendation change after this RFC?

Copy link
Author

Choose a reason for hiding this comment

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

It can be handled by a function that runs in boot() instead. Note that this RFC doesn't fundamentally change the fact that you can run code during application boot. It just changes the developer ergonomics to enable new features.

`routeWillChange` event, it is too late to do that in the beforeModel hook, because the
event has already been dispatched for the first route transition.

## Detailed design

- Add the `onBoot` and `onBootInstance` methods and move the code that runs initializers into these
hooks.
- Expose the hooks and their default implementations into the app blueprint so users can change them
as desired.
- Export a `loadInstanceInitializers` function from the `ember-load-initializers` addon and separate
move the code from `loadInitializers` related to instance initializers into it.

## How we teach this

The Ember Guides currently reference initializers mainly on one page:
[https://guides.emberjs.com/release/applications/initializers/][1].
This page would need to be updated to cover the methods proposed above.

## Drawbacks

Good reasons for separate files per initializer are:

- keeping "concerns separated"
- this is a valid point for large applications, as it can be useful to encourage team members
to write new functions rather than writing long `onBoot` functions. It's also reasonable to say
that choosing the order of initializers should not be a developer concern. However, it is both
easy to write a messy initializer function in one file, and possible to have tooling that
prevents a long `onBoot` function. I believe the benefits of `onBoot` outweigh these concerns.
- treeshaking
- This is also a valid concern, but in a large enough application, it is possible to continue
using the existing implementation. It's also possible to design the initializer running code
such that only ONE of the methodologies is possible (functions exported from a directory
vs `onBoot`).
- container registration
- Files in the appropriate app directories are automatically registered into the application
container. Removing code from this directory location and allowing it to sit inline in app.js
means that initialization functions can no longer be looked up. I have never heard of this
functionality being used intentionally in production, so I don't think it's a problem.

## Alternatives

- It is already possible to override the `boot` method in `Application` to get this feature,
but it would require re-implementing a large part of the interaction between Application and
ApplicationInstance, it is easily possible for an application to dig itself into a maintenance
nightmare.
- Make initializers more robust and allow them to return promises. This was detailed in [RFC #572],
but the cost of initializers (both in bundle size and maintenance) is already high and it would be
better to drop them entirely. One benefit of initializers that would we would lose is the ability
for addons to inject initialization behavior into their host apps implicitly. While this is useful,
it is also harder to debug this pattern and ultimately makes it harder to maintain Ember apps.

## Unresolved questions

- What constraints, if any, do we want to apply to this free-for-all method?
- Would it be better if the `onBoot` method implemented `loadInitializers` upstream, and then
the user `App` is required to call `super` instead?
- Bundle size improvements? Should we include the deprecation path for initializers?


[1]: https://guides.emberjs.com/release/applications/initializers/