Framework internals

Explore how Aurelia's compiler turns templates into instructions and how the runtime executes them.

This deep dive assumes you're comfortable with Aurelia's binding and component model. Bookmark it for debugging and tooling work.

Aurelia's instruction system is the bridge between template compilation and runtime execution. Templates compile to instruction objects that describe exactly what bindings and components to create, then renderers interpret these instructions to build the actual DOM bindings and component instances.

From Template to Runtime

Let's follow a simple template through the complete pipeline:

Template

<my-element value.bind="name" click.trigger="doSomething()">
  <p>${message}</p>
</my-element>

Compilation Phase

The template compiler processes this into instruction arrays:

const instructions = [
  [ // Instructions for <my-element>
    new HydrateElementInstruction(
      'my-element',                    // Element name/definition
      [                               // Property instructions
        new PropertyBindingInstruction('name', 'value', BindingMode.toView),
        new ListenerBindingInstruction('doSomething()', 'click', false, null)
      ],
      null,                           // Projections
      false,                          // Containerless
      [],                             // Captures
      {}                              // Data
    )
  ],
  [ // Instructions for <p> inside my-element
    new InterpolationInstruction('message', 'textContent')
  ]
];

Runtime Phase

At runtime, renderers execute these instructions:

  1. CustomElementRenderer creates the my-element component instance

  2. PropertyBindingRenderer creates a binding from name to the value property

  3. ListenerBindingRenderer creates a click event listener

  4. InterpolationRenderer creates a text binding for ${message}

All bindings are added to the component's controller and activated during the binding lifecycle.

Instruction Types and What They Do

Component Creation

  • hydrateElement - Creates custom element instances

  • hydrateAttribute - Creates custom attribute instances

  • hydrateTemplateController - Creates template controllers (if, repeat, etc.)

Data Binding

  • propertyBinding - Property bindings (.bind, .two-way, etc.)

  • interpolation - Text interpolation ${...}

  • attributeBinding - Attribute bindings (.attr)

  • stylePropertyBinding - Style bindings (.style)

Event Handling

  • listenerBinding - Event listeners (.trigger, .delegate, .capture)

Static Values

  • setProperty - Static property values

  • setAttribute - Static attribute values

  • setClassAttribute - Static class values

Advanced Features

  • refBinding - Reference bindings (ref attribute)

  • letBinding - Let bindings (let element)

  • iteratorBinding - Iterator bindings (for repeat)

Debugging with Instructions

Inspecting Compiled Instructions

You can examine compiled instructions in the browser devtools:

Understanding Instruction Arrays

Instructions are organized as arrays where each array corresponds to a DOM target:

Debugging Binding Issues

When bindings don't work as expected:

  1. Check what instructions were generated

  2. Verify the instruction properties match your template

  3. Look for missing or incorrect binding modes

  4. Check if custom elements/attributes were resolved properly

Common Template Patterns

Basic Property Binding

Compiles to:

Event Binding

Compiles to:

String Interpolation

Compiles to:

Custom Element with Bindables

Compiles to:

Template Controller (Repeater)

Compiles to:

Extending the System

Creating Custom Instructions

For advanced scenarios, you can create custom instruction types:

Creating Custom Renderers

Custom renderers interpret your custom instructions:

Registering Custom Renderers

Register your renderer during app startup:

Resource Registration and Discovery

Before instructions can reference resources like custom elements, Aurelia needs to discover and register them. The framework supports multiple registration patterns.

Resource Registration Patterns

Decorator-Based Registration

The decorator automatically:

  1. Creates a resource definition with metadata

  2. Registers the resource in the DI container with key: "au:resource:custom-element:user-card"

  3. Stores the definition for later compilation use

Static $au Property Registration

Convention-Based Registration

With the conventions plugin, file names automatically determine resource names:

Resource Resolution During Compilation

When the template compiler encounters <user-card>, it:

  1. Looks up the resource using CustomElement.find(container, 'user-card')

  2. Resolves the definition from the DI container key "au:resource:custom-element:user-card"

  3. Creates instruction with either the resource name string or the resolved definition

  4. Caches the result to avoid repeated lookups

Resource Resolution During Runtime

At runtime, the CustomElementRenderer processes HydrateElementInstruction:

Container Hierarchy and Resource Scope

Resources are registered in DI containers, which form hierarchies:

Resource resolution follows the hierarchy:

  1. Check current container

  2. Check parent containers up to root

  3. Return null if not found

Debugging Resource Registration

Check what resources are registered:

Resource Registration Best Practices

  1. Use decorators for explicit control over resource configuration

  2. Use static $au properties when decorators aren't suitable (e.g., plain classes)

  3. Use conventions for rapid development with consistent naming

  4. Register global resources at app startup in main.ts

  5. Register page-specific resources in route components or modules

Understanding Aurelia's instruction system and resource registration gives you deeper insight into how templates become living, reactive UIs and provides the foundation for advanced framework extensions.

Last updated

Was this helpful?