No Default Exports (Except Route Templates)
Use named exports instead of default exports for better tree-shaking, explicit imports, and easier refactoring. The only exception is route template files.
Incorrect (default exports):
glimmer-js
// app/components/user-card.gjs
import Component from '@glimmer/component';
export default class UserCard extends Component {
<template>
<div>{{@user.name}}</div>
</template>
}```
**Correct (named exports):**
```glimmer-js
// app/components/user-card.gjs
import Component from '@glimmer/component';
export class UserCard extends Component {
<template>
<div>{{@user.name}}</div>
</template>
}```
**Why Named Exports:**
1. **Explicit Imports**: Clear what you're importing
2. **Better Tree-shaking**: Bundlers can remove unused exports
3. **Easier Refactoring**: Rename refactoring works better
4. **No Name Confusion**: Import name must match export name
5. **Multiple Exports**: Can export multiple items from one file
**For Helpers:**
```javascript
// ❌ Wrong - default export
// app/utils/format-date.js
export default function formatDate(date) {
return new Date(date).toLocaleDateString();
}
// ✅ Correct - named export
// app/utils/format-date.js
export function formatDate(date) {
return new Date(date).toLocaleDateString();
}For Services:
javascript
// ❌ Wrong - default export
// app/services/auth.js
import Service from '@ember/service';
export default class AuthService extends Service {
// ...
}
// ✅ Correct - named export
// app/services/auth.js
import Service from '@ember/service';
export class AuthService extends Service {
// ...
}For Modifiers:
javascript
// ❌ Wrong - default export
// app/modifiers/auto-focus.js
import { modifier } from 'ember-modifier';
export default modifier((element) => {
element.focus();
});
// ✅ Correct - named export
// app/modifiers/auto-focus.js
import { modifier } from 'ember-modifier';
export const autoFocus = modifier((element) => {
element.focus();
});Exception: Route Templates
Route templates are the ONLY place where default exports are used in modern Ember:
glimmer-js
// ✅ Correct - default export for route template
// app/routes/dashboard.gjs
import Route from '@ember/routing/route';
export default class DashboardRoute extends Route {
model() {
return this.store.findAll('dashboard-item');
}
}
<template>
<div class="dashboard">
{{#each this.model as |item|}}
<DashboardCard @item={{item}} />
{{/each}}
</template>
</template>```
This is because Ember's router expects a default export from route files.
**Multiple Exports from One File:**
```javascript
// app/utils/validators.js
export function isEmail(value) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}
export function isPhoneNumber(value) {
return /^\d{3}-\d{3}-\d{4}$/.test(value);
}
export function isZipCode(value) {
return /^\d{5}(-\d{4})?$/.test(value);
}
// Import specific validators
import { isEmail, isPhoneNumber } from '../utils/validators';Import Comparison:
javascript
// Default export - can rename arbitrarily
import MyComponent from './my-component'; // Can name it anything
import WhateverIWant from './my-component'; // Still works!
// Named export - explicit and consistent
import { MyComponent } from './my-component'; // Must use exact name
import { MyComponent as MyComp } from './my-component'; // Rename explicitly if neededBenefits for Tree-shaking:
javascript
// app/utils/string-utils.js
export function capitalize(text) { /* ... */ }
export function lowercase(text) { /* ... */ }
export function uppercase(text) { /* ... */ }
// Only imports what's used - unused exports can be removed
import { capitalize } from './utils/string-utils';
// capitalize is bundled, lowercase and uppercase are tree-shakenUse named exports everywhere except route template files for better maintainability and optimization.
Reference: ES Modules Best Practices