Files
marco/.agents/skills/ember-best-practices/rules/exports-named-with-default-fallback.md

169 lines
4.7 KiB
Markdown

---
title: Prefer Named Exports, Fallback to Default for Implicit Template Lookup
impact: LOW
impactDescription: Clear export contracts across .hbs and template-tag codebases
tags: exports, hbs, gjs, interop, code-organization
---
## Prefer Named Exports, Fallback to Default for Implicit Template Lookup
Use named exports for shared modules imported directly in JS/TS (utilities, constants, pure functions). If a module should be invokable from `.hbs` templates via implicit lookup, provide a default export. In hybrid `.gjs`/`.hbs` projects, a practical pattern is a named export plus a default export alias.
**Incorrect (default export in a shared utility module):**
```javascript
// app/utils/format-date.js
export default function formatDate(date) {
return new Date(date).toLocaleDateString();
}
```
**Correct (named export in a shared utility module):**
```javascript
// app/utils/format-date.js
export function formatDate(date) {
return new Date(date).toLocaleDateString();
}
```
**Correct (hybrid `.gjs`/`.hbs` named export + default alias):**
```javascript
// app/helpers/format-date.js
import { helper } from '@ember/component/helper';
export const formatDate = helper(([value]) => {
return new Date(value).toLocaleDateString();
});
export default formatDate;
```
## Where Named Exports Are Preferred
Use named exports when the module is imported directly by other modules and is not resolved via implicit template lookup.
**Example (utility module with multiple named exports):**
```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);
}
```
Benefits:
1. Explicit import contracts
2. Better refactor safety (symbol rename tracking)
3. Better tree-shaking for utility modules
4. Easier multi-export module organization
## Where Default Exports Are Required
Use default exports for modules consumed through resolver/template lookup.
If your project uses `.hbs`, invokables that should be accessible from templates should provide `export default`.
In hybrid `.gjs`/`.hbs` codebases, use named exports plus a default export alias where you want both explicit imports and template compatibility.
**Service:**
```javascript
// app/services/auth.js
import Service from '@ember/service';
export default class AuthService extends Service {
// ...
}
```
**Route:**
```javascript
// app/routes/dashboard.js
import Route from '@ember/routing/route';
import { service } from '@ember/service';
export default class DashboardRoute extends Route {
@service store;
model() {
return this.store.findAll('dashboard-item');
}
}
```
**Modifier (when invoked from `.hbs`):**
```javascript
// app/modifiers/focus.js
import { modifier } from 'ember-modifier';
export default modifier((element) => {
element.focus();
});
```
**Template (`.gjs`):**
```glimmer-js
// app/templates/dashboard.gjs
<template>
<h1>Dashboard</h1>
</template>
```
**Template (`.gts`):**
```glimmer-ts
// app/templates/dashboard.gts
import type { TOC } from '@ember/component/template-only';
interface Signature {
Args: {
model: unknown;
};
}
export default <template>
<h1>Dashboard</h1>
</template> satisfies TOC<Signature>;
```
Template-tag files must resolve via a module default export in convention-based and `import.meta.glob` flows.
For `app/templates/*.gjs`, the default export is implicit after compilation.
## Strict Resolver Nuance
With `ember-strict-application-resolver`, you can register explicit module values in `App.modules`:
**Strict resolver explicit modules registration:**
```ts
modules = {
'./services/manual': { default: ManualService },
'./services/manual-shorthand': ManualService,
};
```
In that explicit shorthand case, a direct value works without a default-exported module object.
This is an explicit registration escape hatch and does not replace default-export requirements for `.hbs`-invokable modules.
## Rule of Thumb
1. If a module should be invokable from `.hbs`, provide a default export.
2. In hybrid `.gjs`/`.hbs` projects, use named export + default alias for resolver-facing modules.
3. Strict resolver explicit `modules` entries may use direct shorthand values where appropriate.
4. Plain shared modules (`app/utils`, shared constants, reusable pure functions): prefer named exports.
5. Template-tag components (`.gjs`/`.gts`): follow the component file-conventions rule and use named class exports.
## References
- [ES Modules Best Practices](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules)
- [ember-strict-application-resolver](https://github.com/ember-cli/ember-strict-application-resolver)
- [ember-resolver](https://github.com/ember-cli/ember-resolver)