or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

build-time-integration.mdcli-commands.mdfeature-system.mdindex.mdutilities.md
tile.json

feature-system.mddocs/

Feature System

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.

Capabilities

Feature Definition Structure

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>;
}

Feature Registry

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:

  1. Scanning the features/ directory for .js files
  2. Requiring each file and freezing the exported definition
  3. Using the filename (without extension) as the feature key
  4. Creating a frozen registry object

Available Features

application-template-wrapper

Controls 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:

  • When disabling (value: false): Offers to add <div class="ember-view"> wrapper to application.hbs template
  • Handles both standard and pod-based application structures
  • Interactive prompts unless shouldRunCodemod is explicitly set
  • Modifies template files with proper indentation preservation

default-async-observers

Controls 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
};

jquery-integration

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
};

no-implicit-route-model

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
};

template-only-glimmer-components

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:

  • When enabling (value: true): Scans for template-only components and offers to create corresponding .js files
  • Handles multiple project structures: classic, pods, and pods with prefix
  • Generates basic component files to maintain backward compatibility
  • Interactive prompts for file generation unless shouldRunCodemod is explicitly set

Feature Callback System

Advanced features can provide callback functions that execute during feature toggle operations.

Callback Function Interface

/**
 * 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>;

Callback Execution Context

Callbacks have access to:

  • Project instance: File system access, configuration, and project metadata
  • Feature value: The new enabled/disabled state
  • Codemod preference: User's choice about running automated transformations

Common Callback Patterns

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 });

Feature Discovery Process

Features are automatically discovered and loaded during addon initialization:

  1. Directory Scan: Glob for *.js files in the features/ directory
  2. Module Loading: Require each feature module and extract the exported definition
  3. Validation: Ensure required properties are present and valid
  4. Registration: Add to the frozen FEATURES registry with filename as key
  5. Freezing: Make feature definitions immutable to prevent modification

Version Compatibility

Features 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 higher

Error Handling

The feature system handles various error conditions gracefully:

  • Invalid feature definitions: Missing required properties cause load-time errors
  • Callback failures: Async callback errors are caught and reported to users
  • Version incompatibility: Features unavailable in current Ember version are hidden
  • File system errors: I/O errors during callback execution are handled with appropriate messages