Building plugins
Aurelia makes it easy to create your plugins. Learn how to create individual plugins, register them, and work with tasks to run code during certain parts of the lifecycle process.
Aurelia plugins allow you to encapsulate functionality that can be reused across multiple applications. They can include custom elements, value converters, binding behaviors, and other resources. The goal is to create packaged, easily shared, ready-to-use functionalities that integrate seamlessly with Aurelia applications.
Understanding Plugin Architecture
At its core, an Aurelia plugin is an object with a register method that configures dependencies and sets up your functionality for use in an Aurelia application. This follows the dependency injection pattern that powers the entire Aurelia framework.
Minimal Plugin Example
Basic Plugin Structure
// my-simple-plugin.ts
import { IContainer } from '@aurelia/kernel';
export const MySimplePlugin = {
register(container: IContainer): void {
// Register your plugin resources here
console.log('Plugin registered!');
}
};Registering the Plugin
Adding Components and Resources
Plugins typically provide custom elements, attributes, value converters, or other resources:
Creating Configurable Plugins
Modern Configuration Pattern with .customize()
.customize()The modern approach uses a .customize() method that follows the same pattern as Aurelia's built-in plugins:
Using the Configurable Plugin
Consuming Configuration in Components
Working with App Tasks (Lifecycle Hooks)
App tasks allow you to run code at specific points during the application lifecycle. This is useful for initialization, cleanup, or integration with external libraries.
Available Lifecycle Phases
Async App Tasks with DI
App tasks can be asynchronous and can inject dependencies:
Real-World Plugin Examples
Router-like Plugin Structure
Here's how a plugin similar to Aurelia's router might be structured:
Validation-like Plugin Structure
Here's how a validation plugin might be structured:
Template and Style Handling in Plugins
When building plugins with custom elements, you need to decide how to handle templates and styles. Aurelia provides multiple approaches, each suited for different scenarios.
Convention-Based Approach
The convention-based approach relies on file naming and automatic bundler processing. This is ideal for application development but requires specific bundler configuration.
File Structure
Component Definition
Template File
Styles File
Explicit Import Approach
The explicit import approach gives you full control over dependencies and is ideal for UI libraries and distribution packages.
Styling Strategies
Light DOM (Global Styles)
Regular CSS injection into the document head:
Shadow DOM with CSS
Encapsulated styles using Shadow DOM:
CSS Modules
Scoped class names for style isolation:
Multiple Template Support
For components with different template variants:
Inline Templates and Styles
For simple components, you can define templates and styles inline:
Or with CSS-in-JS approach:
When to Use Each Approach
Use Convention-Based When:
Building application-specific plugins
Working in a controlled bundler environment
Want minimal boilerplate code
Following standard Aurelia project structure
Use Explicit Imports When:
Building UI libraries for distribution
Need precise control over dependencies
Supporting multiple bundler configurations
Want explicit dependency management
Building plugins for npm distribution
TypeScript Support
For explicit imports, provide proper TypeScript declarations:
Plugin Template Recommendations
For UI library plugins, structure your components like this:
This approach provides maximum flexibility for consumers while maintaining clean plugin architecture.
Advanced Plugin Patterns
Conditional Resource Registration
Plugin with Dynamic Imports
Plugin Composition
Best Practices
Naming Conventions
Plugin Names: Use descriptive names ending with "Plugin" or "Configuration"
Interfaces: Prefix with "I" and use
DI.createInterface()Resources: Prefix with your plugin name to avoid collisions
Error Handling
TypeScript Integration
Testing Plugins
Unit Testing Plugin Registration
Packaging and Distribution
Package Structure
Package.json Configuration
Mono-Repository Setup
For complex plugins with multiple packages, consider using a workspace:
Directory structure:
This setup allows you to:
Share common dependencies
Cross-reference packages easily
Build and test everything together
Publish individual packages as needed
Extending the Rendering Pipeline
Public exports from @aurelia/runtime-html make it possible to hook into Aurelia’s renderer without forking the framework. This is essential for plugins that add new template syntax, hydrate components into foreign DOM environments, or need to precompile custom elements on the fly.
Register Custom Instruction Renderers
IRenderer implementations are discovered through the @renderer decorator. Each renderer receives the instruction at hydration time, so you can translate custom compiler output into DOM mutations.
Once registered (for example via Aurelia.register(TranslateRenderer) or StandardConfiguration.customize({ resources: [TranslateRenderer] })), any instruction whose type matches TranslateRenderer.target is routed through your plugin. Combine this with a binding command or template compiler hook to emit translate instructions from HTML like <span t="nav.home"></span>.
Use IRendering for Dynamic Definitions
IRendering for Dynamic DefinitionsPlugins that generate PartialCustomElementDefinition objects at runtime can inject IRendering to compile them, create node sequences, or materialize ViewFactory instances on demand.
IRendering.createNodes() returns reusable FragmentNodeSequence instances, letting you build headless renderers that project Aurelia content into other host libraries without going through standard component bootstrapping.
Provide Host Nodes via registerHostNode
registerHostNodeIf you hydrate Aurelia components into DOM nodes created by another framework (for example, inside a CMS widget or micro-frontend shell), call registerHostNode(container, host) so that DI can resolve HTMLElement, Element, or INode within that scope.
This mirrors what the runtime does for the default app host and keeps resource lookups such as resolve(IEventTarget) or resolve(IPlatform).HTMLElement working even when the DOM comes from an embedded document or shadow root.
Summary
Building Aurelia plugins involves:
Basic Structure: Implement the
registermethod patternConfiguration: Use
DI.createInterface()and the.customize()patternLifecycle Integration: Leverage
AppTaskfor initialization and cleanupRendering Extensions: Reach for
IRenderer,IRendering, andregisterHostNodewhen you need custom instructions or alternate hostsBest Practices: Follow naming conventions, handle errors gracefully, and provide TypeScript support
Testing: Write comprehensive tests for registration and functionality
Distribution: Package properly for npm distribution
By following these patterns and practices, you can create robust, reusable plugins that integrate seamlessly with the Aurelia ecosystem.
Last updated
Was this helpful?