Modular feature definitions with metadata, version requirements, and optional callback functions for codemods. Features are automatically discovered from the features directory and provide the foundation for the optional features system.
Each feature is defined as a JavaScript module exporting a feature definition object with standardized properties.
/**
* Feature definition object structure
*/
interface FeatureDefinition {
/** Human readable description of what the feature does */
description: string;
/** RFC or documentation URL with more information */
url: string;
/** Default enabled state for the feature */
default: boolean;
/** Minimum Ember version required (e.g. "3.1.0") */
since: string;
/** Optional callback function for handling feature toggle logic */
callback?: (
project: EmberProject,
value: boolean,
shouldRunCodemod?: boolean
) => Promise<void>;
}The complete collection of available features, automatically loaded from the features directory.
/**
* Registry of all available features (frozen for immutability)
*/
const FEATURES: Readonly<Record<string, Readonly<FeatureDefinition>>>;Features are loaded by:
features/ directory for .js filesControls whether the application template is wrapped with a div element.
/**
* Application template wrapper feature
*/
const applicationTemplateWrapper: FeatureDefinition = {
description: 'Wrap the top-level application template (application.hbs) with a `<div class="ember-view">` element.',
url: 'https://github.com/emberjs/rfcs/pull/280',
default: true,
since: '3.1.0',
callback: async (project, value, shouldRunCodemod) => { /* Implementation */ }
};Callback Behavior:
value: false): Offers to add <div class="ember-view"> wrapper to application.hbs templateshouldRunCodemod is explicitly setControls whether observers are asynchronous by default.
/**
* Default async observers feature
*/
const defaultAsyncObservers: FeatureDefinition = {
description: 'Makes all observers asynchronous (unless manually indicated as synchronous when calling addObserver/observes).',
url: 'https://github.com/emberjs/rfcs/pull/494',
default: false,
since: '3.13.0'
// No callback - this is a pure configuration feature
};Controls whether jQuery is included in the Ember application.
/**
* jQuery integration feature
*/
const jqueryIntegration: FeatureDefinition = {
description: 'Adds jQuery to the Ember application.',
url: 'https://github.com/emberjs/rfcs/pull/294',
default: true,
since: '3.0.0'
// No callback - this is a pure configuration feature
};Controls automatic record loading behavior in routes.
/**
* No implicit route model feature
*/
const noImplicitRouteModel: FeatureDefinition = {
description: `Removes the default record loading behaviour on Ember's Route.`,
url: 'https://rfcs.emberjs.com/id/0774-implicit-record-route-loading',
default: true,
since: '5.7.0'
// No callback - this is a pure configuration feature
};Controls component semantics for template-only components.
/**
* Template-only Glimmer components feature
*/
const templateOnlyGlimmerComponents: FeatureDefinition = {
description: 'Use Glimmer Components semantics for template-only components (component templates with no corresponding .js file).',
url: 'https://github.com/emberjs/rfcs/pull/278',
default: false,
since: '3.1.0',
callback: async (project, value, shouldRunCodemod) => { /* Implementation */ }
};Callback Behavior:
value: true): Scans for template-only components and offers to create corresponding .js filesshouldRunCodemod is explicitly setAdvanced features can provide callback functions that execute during feature toggle operations.
/**
* Feature callback function for handling complex toggle logic
* @param project - Ember project instance
* @param value - New feature value (true = enabled, false = disabled)
* @param shouldRunCodemod - Whether to run codemods automatically
*/
type FeatureCallback = (
project: EmberProject,
value: boolean,
shouldRunCodemod?: boolean
) => Promise<void>;Callbacks have access to:
File System Operations:
// Reading and writing files
const content = await fs.readFile(templatePath, { encoding: 'UTF-8' });
await fs.writeFile(templatePath, modifiedContent, { encoding: 'UTF-8' });
// Directory creation
await mkdirp(path.dirname(absolutePath));Interactive Prompts:
if (shouldRunCodemod === undefined) {
let response = await inquirer.prompt({
type: 'confirm',
name: 'shouldGenerate',
message: 'Would you like me to generate these files?',
default: true,
});
shouldRunCodemod = response.shouldGenerate;
}Project Structure Detection:
// Check for pod-based structure
let { modulePrefix, podModulePrefix } = project.config();
let isPod = !!podModulePrefix;
// Scan for template files
let templates = await glob('**/*.hbs', { cwd: templatesRoot });Features are automatically discovered and loaded during addon initialization:
*.js files in the features/ directoryFeatures specify minimum Ember versions using semantic versioning:
// Version checking logic
let checker = new VersionChecker(project).for('ember-source');
let isAvailable = checker.gte(`${feature.since}-beta.1`);Version Formats:
"3.1.0" - Requires Ember 3.1.0 or higher"3.13.0" - Requires Ember 3.13.0 or higher"5.7.0" - Requires Ember 5.7.0 or higherThe feature system handles various error conditions gracefully: