Behaviour and macros for self-describing component plugins.
Plugins declare themselves declaratively, allowing Dala core to remain generic. Everything becomes a plugin: video, maps, charts, camera, ML views, custom renderers, AR, etc.
Architecture
Dala core knows almost nothing. Plugins self-describe through:
- Schema - component metadata (props, events, capabilities)
- Protocol - binary communication format (auto-generated)
- Native renderer - platform-specific implementation (iOS/Android)
This is the same pattern used by:
- React Native Fabric
- Flutter Engine
- SwiftUI internals
- Jetpack Compose runtime
- VSCode extension host
- Browser DOM
Example
defmodule MyApp.VideoPlugin do
use Dala.Plugin
component "video" do
prop "source", :string
prop "autoplay", :bool
prop "controls", :bool
prop "volume", :f32
event "progress"
event "ended"
event "ready"
native "ios", "DalaVideoView"
native "android", "com.dala.video.VideoView"
capability :gestures
capability :accessibility
capability :animation
end
endThis is NOT UI code. This is metadata.
Core Dala automatically generates:
- Protocol encoders/decoders
- Validators
- Documentation
- Registry entries
Plugin Package Structure
my_plugin/
├── lib/
│ └── my_plugin.ex # Plugin schema definitions
├── native/
│ ├── rust/ # Rust NIF extensions (optional)
│ ├── ios/ # iOS native views
│ └── android/ # Android native views
├── protocol/ # Generated binary protocol
└── assets/ # Static assetsSchema-First Architecture
Designing around schema-first (not widget-first, not native-view-first, not protocol-first) unlocks:
- Tooling and validation
- Code generation
- Compatibility guarantees
- Visual editors
- Plugin ecosystems
- AI-generated UIs
- Hot reload
- Documentation
Versioning
Plugins MUST declare versions for compatibility:
schema_version "1.0.0"
protocol_version 3
native_api_version "2.0.0"This prevents ecosystem fragmentation.
Host/Runtime Separation
Plugins should NEVER directly access:
- BEAM internals
- Scheduler state
- Raw protocol sockets
Instead:
Plugin
↓
Host API
↓
Dala RuntimeExactly like browser extensions.
Generic Node Model
Everything becomes a generic node:
%Dala.Node{
type: "video",
props: %{source: "...", autoplay: true},
children: []
}Dala core NEVER special-cases video, maps, or charts. The same generic lifecycle applies to all plugins:
- create/2
- update/2
- layout/2
- event/3
- dispose/1
Optional capabilities:
- animate/2
- focus/2
- accessibility/2
- snapshot/1
- texture/1
- gesture/2
Universal Command Stream
Dala core emits only generic operations:
- CREATE_NODE
- UPDATE_PROP
- REMOVE_NODE
- EMIT_EVENT
- RUN_ANIMATION
Plugins interpret semantics. Core stays tiny.
Summary
Functions
Macro callback - registers the plugin after module compilation.
Defines a new plugin module.
Auto-registers all plugins found in loaded applications.
Defines a new component within the plugin.
Sets the native API version.
Sets the binary protocol version.
Sets the plugin schema version.
Types
@type capability() ::
:gestures
| :accessibility
| :animation
| :textures
| :overlay
| :clipping
| :touch
| :keyboard
| :focus
@type component_name() :: String.t()
@type event_name() :: String.t()
@type plugin_name() :: atom()
@type prop_name() :: String.t()
@type prop_type() ::
:string
| :bool
| :integer
| :float
| :f32
| :f64
| :color
| :binary
| :list
| :map
Functions
Macro callback - registers the plugin after module compilation.
Defines a new plugin module.
Options
:schema_version- Plugin schema version (default: "1.0.0"):protocol_version- Binary protocol version (default: 3):native_api_version- Native API version (default: "2.0.0")
Auto-registers all plugins found in loaded applications.
Defines a new component within the plugin.
Example
component "video" do
prop "source", :string
prop "autoplay", :bool
prop "volume", :f32
event "progress"
event "ended"
native "ios", "DalaVideoView"
native "android", "com.dala.video.VideoView"
capability :gestures
capability :accessibility
end
Sets the native API version.
Sets the binary protocol version.
Sets the plugin schema version.