Skip to content

Deprecate initializers and instance initializers #1120

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

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
307 changes: 307 additions & 0 deletions text/1120-deprecate-initializers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
---
stage: accepted
start-date: 2025-07-02T00:00:00.000Z # In format YYYY-MM-DDT00:00:00.000Z
release-date:
release-versions:
teams: # delete teams that aren't relevant
- framework
- learning
prs:
accepted: https://github.com/emberjs/rfcs/pull/1120
project-link:
---

<!---
Directions for above:

stage: Leave as is
start-date: Fill in with today's date, 2032-12-01T00:00:00.000Z
release-date: Leave as is
release-versions: Leave as is
teams: Include only the [team(s)](README.md#relevant-teams) for which this RFC applies
prs:
accepted: Fill this in with the URL for the Proposal RFC PR
project-link: Leave as is
-->

# Deprecate Application Initializers and Instance Initializers

## Summary

This RFC proposes deprecating Ember's application initializers and instance initializers in favor of explicit application lifecycle management. The current initializer system will be replaced with direct access to application and instance objects during the boot process, enabling async/await support and providing more explicit control over application setup.

> [!NOTE]
> This RFC assumes usage of the v2 app blueprint, via `@ember/app-blueprint`

[Here is a demo app](https://github.com/NullVoxPopuli/ember-initializers-demo/commit/f57e30c0cee5e83794fa71961a8d091ededaf292)

## Motivation

The current initializer system has several limitations that make it difficult to work with in modern JavaScript environments:

1. **No async/await support**: Initializers cannot be awaited, making asynchronous setup operations difficult to coordinate and debug.

2. **Implicit execution order**: The `before` and `after` properties create implicit dependencies that are hard to reason about and can lead to subtle ordering bugs.

3. **Hidden lifecycle**: The automatic execution of initializers makes the application boot process opaque and harder to debug.

4. **Limited access to application state**: Initializers receive only the application or instance object, without access to other important lifecycle information.

The replacement functionality provides explicit control over the application lifecycle by allowing developers to write setup code directly in their application entry point, with full access to modern JavaScript async/await patterns.

## Transition Path

### Current Initializer Usage Patterns

Application initializers are currently defined in `app/initializers/` and run during application boot:

```js
// app/initializers/session.js
export default {
name: 'session',
initialize(application) {
application.inject('route', 'session', 'service:session');
}
};
```

Instance initializers are defined in `app/instance-initializers/` and run during instance creation:

```js
// app/instance-initializers/current-user.js
export default {
name: 'current-user',
initialize(applicationInstance) {
let session = applicationInstance.lookup('service:session');
return session.loadCurrentUser();
}
};
```

### New Explicit Lifecycle Pattern

This would replace the contents of the script tag in the existing `@ember/app-blueprint`'s index.html:
```html
<script type="module">
import Application from './app/app';
import environment from './app/config/environment';

Application.create(environment.APP);
</script>
```

However! We will not change the default from the above, as the simple case is the most common.

> [!IMPORTANT]
> The above 3 line entrypoint for ember applications will remain the default. This RFC is primarily about updates to documentation showing how we can achieve a new way of "initializers" while also slimming the default experience for new projects.

The new pattern provides explicit control over application lifecycle in the main application entry point (still within the index.html):
Copy link
Member

Choose a reason for hiding this comment

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

I think you'd want to propose the new pattern before deprecating initializers. Can the following pattern be done in classic build apps? (I know the RFC says to assume the v2 app blueprint but it wasn't clear if that was for examples or for the entire proposal -- if it is the later, we would need a replacement for classic builds as well in order to deprecate initializers).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the new pattern cannot be done in classic apps because Application.create() is not in userland code anywhere

Copy link
Contributor Author

Choose a reason for hiding this comment

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

need a replacement for classic builds

I don't think this is worth doing, personally

Copy link
Member

Choose a reason for hiding this comment

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

I agree -- we should instead tie this deprecation timeline to when classic builds will be deprecated


```js
import Application from '#app/app.ts';
import environment from '#app/config.ts';

let app = Application.create({
...environment.APP,
autoboot: false,
});

// anything running here with access to `app`
// is equivelent to classic "initializers"
//
// These can be awaited, which was not possible before
console.log(app);

await app.boot();
let instance = await app.buildInstance();
await instance.boot();
await instance.visit('/');

// anything running here with access to `instance`
// is equivelent to classic "instance initializers"
//
// These can be awaited, which was not possible before
console.log(instance);
```
Copy link

Choose a reason for hiding this comment

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

Where is that file located? What's the proposed filename?

Second, a thing currently missing (ok, badly located) is to programmatically configure your app (currently beforeModel() hook in application route). This seems like the place to do that config as well (when the instance is available then). When thinking about this as runtime configuration, that - to me - brings all that in one place I was hoping for years to have.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, sorry, it's the index.html' i'll clarify that

Second, yeah, perhaps i'll add an example for that


### To share initialization with tests

Developers can extract the contents of the script tag to a `boot.ts` or `boot.js`, with the same contents.

> [!DANGER]
> Since initializers would no longer be tied to the application instance itself, initializers would not normally run during tests.
Copy link
Member

Choose a reason for hiding this comment

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

This seems like a major foot-gun. It would keep me from ever using this replacement and ever suggesting it to others. I would continue to put initialization code that needs async in the beforeModel of the application route.

I think it would be bad form for us to suggest users add code that does not, by default, run in tests.

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 think we have to go with one of the alternatives then (still not available in classic), but would retain initializer code in ember, and would also remove the ability to have async initializers

-- but this proposal could propose a change to make them async as well, I suppose


To have initializers run during tests, a developer would want to edit their in-test-copy of `setupApplicationTest` to run the initializers there, for example:

```ts
function setupApplicationTest(hooks: NestedHooks, options?: SetupTestOptions) {
upstreamSetupApplicationTest(hooks, options);

hooks.beforeEach(function () {
let instance = this.owner;
runInstanceInitializer(instance)
});

// Additional setup for application tests can be done here.
//
// For example, if you need an authenticated session for each
// application test, you could do:
//
// hooks.beforeEach(async function () {
// await authenticateSession(); // ember-simple-auth
// });
//
// This is also a good place to call test setup functions coming
// from other addons:
//
// setupIntl(hooks, 'en-us'); // ember-intl
// setupMirage(hooks); // ember-cli-mirage
}
```


### Migration Steps for Common Use Cases

#### 1. Service Registration

**Before:**
```js
// app/initializers/register-websocket.js
export default {
name: 'register-websocket',
initialize(application) {
application.register('service:websocket', WebSocketService);
}
};
```

**After:**
```js
// In main.js or index.html
let app = Application.create({ ...environment.APP, autoboot: false });

app.register('service:websocket', WebSocketService);

await app.boot();
// ... rest of boot process
```

#### 2. Asynchronous Setup

**Before (not possible):**
```js
// app/initializers/async-setup.js
export default {
name: 'async-setup',
initialize(application) {
// Cannot await here - initializers don't support async
fetchConfigFromServer().then(config => {
application.register('config:remote', config);
});
}
};
```

**After:**
```js
// In main.js or index.html
let app = Application.create({ ...environment.APP, autoboot: false });

// Now we can await asynchronous setup
let config = await fetchConfigFromServer();

await app.boot();
// ... rest of boot process
```

#### 3. Instance-level Setup

**Before:**
```js
// app/instance-initializers/load-user.js
export default {
name: 'load-user',
initialize(applicationInstance) {
let session = applicationInstance.lookup('service:session');
return session.loadCurrentUser();
}
};
```

**After:**
```js
// In main.js or index.html
let app = Application.create({ ...environment.APP, autoboot: false });
await app.boot();
let instance = await app.buildInstance();

// Instance-level setup with full async/await support
let session = instance.lookup('service:session');
await session.loadCurrentUser();

await instance.boot();
await instance.visit('/');
```

### Ecosystem Implications

- **ember-load-initializers**: This addon will be README-deprecated as initializer discovery will no longer be needed -- the README will show the new way to run initializers if they are needed.
- **Blueprints**: Ember CLI blueprints should be updated to remove use of initializers
- **Addon ecosystem**: Addons that provide initializers will need to document the migration path -- which is to import the initializer from the addon, and place it in the script tag in the appropriate place.

## How We Teach This

This change represents a significant shift in how Ember applications are structured, requiring updates across multiple learning resources:

### Guides Updates

The "Applications and Instances" section of the guides will need to be rewritten to:
- Remove documentation for initializers and instance initializers
- Add comprehensive documentation for explicit application lifecycle management
- this is needed anyway because it's not described anywhere
- Provide migration examples for common initializer patterns
- Explain the benefits of explicit control and async/await support

### API Documentation

- Mark initializer-related APIs as deprecated

## Drawbacks

- Large applications with many initializers will require a little migration effort
- Addon authors will need to update their libraries and documentation -- initializers from addons would no longer run automatically

## Alternatives

### Option 1: Explicit Initializer Registration

Keep the initializer concept but require explicit registration:

```js
// In main.js
import sessionInitializer from './initializers/session';
import userInitializer from './instance-initializers/user';

let app = Application.create({ ...environment.APP, autoboot: false });
app.registerInitializer(sessionInitializer);
app.registerInstanceInitializer(userInitializer);
```

**Pros**: This is the closest to the existing behavior with the least amount of boilerplate.
**Cons**: Still maintains the complexity of the initializer system, and folks that don't use it will still have to pay for its existence.

### Option 2: Do Nothing

Continue with the current initializer system.

**Pros**: No migration effort required
**Cons**: Continues to limit developers from using modern JavaScript patterns and maintains the current complexity and debugging difficulties

## Unresolved questions

n/a

## References

- async initializers were first asked about in [2013](https://discuss.emberjs.com/t/initializers-that-return-promises/2782) (maybe earlier?)
- previous RFC was attempted, [RFC#572](https://github.com/emberjs/rfcs/pull/572)
Loading