Extension Development Tutorial
Learn how to create Morphir extensions for new languages or targets.
Overview
Morphir extensions are WASM modules that implement the Frontend and/or Backend traits. Extensions can be:
- Builtin: Bundled with the CLI (like Gleam)
- Registry: Installed from a registry
- Local: Referenced by path in
morphir.toml
Extension Types
Frontend Extension
Converts source code → Morphir IR V4
impl Frontend for MyExtension {
fn compile(&self, request: CompileRequest) -> Result<CompileResult> {
// Parse source files
// Convert to Morphir IR V4
// Return CompileResult
}
}
Backend Extension
Converts Morphir IR V4 → Generated code
impl Backend for MyExtension {
fn generate(&self, request: GenerateRequest) -> Result<GenerateResult> {
// Read Morphir IR
// Generate target language code
// Return GenerateResult
}
}
Creating a Simple Extension
Step 1: Create Extension Crate
cargo new --lib my-morphir-extension
cd my-morphir-extension
Step 2: Configure for WASM
In Cargo.toml:
[lib]
crate-type = ["cdylib"]
[dependencies]
morphir-extension-sdk = { path = "../../morphir-extension-sdk" }
extism-pdk = "1.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Step 3: Implement Extension
In src/lib.rs:
use morphir_extension_sdk::prelude::*;
#[derive(Default)]
pub struct MyExtension;
impl Extension for MyExtension {
fn info() -> ExtensionInfo {
ExtensionInfo {
id: "my-extension".into(),
name: "My Extension".into(),
version: env!("CARGO_PKG_VERSION").into(),
types: vec![ExtensionType::Frontend],
// ...
}
}
}
impl Frontend for MyExtension {
fn compile(&self, request: CompileRequest) -> Result<CompileResult> {
// Implementation
Ok(CompileResult {
success: true,
ir: vec![],
diagnostics: vec![],
})
}
}
Step 4: Build WASM
cargo build --target wasm32-unknown-unknown --release
Step 5: Use Extension
Add to morphir.toml:
[extensions.my-extension]
path = "./extensions/my-extension.wasm"
enabled = true
Builtin Extensions
Builtin extensions (like Gleam) are:
- Compiled as part of the CLI build
- Bundled in the CLI binary or resources
- Automatically discovered
To make an extension builtin:
- Add to
morphir-design/src/extensions.rs - Bundle WASM in build process
- Update extension discovery
Extension Discovery
Extensions are discovered in this order:
- Builtin extensions (highest priority)
- Extensions from
morphir.toml - Registry extensions
Extension Configuration
Extensions can have custom configuration:
[extensions.my-extension]
path = "./extensions/my-extension.wasm"
enabled = true
[extensions.my-extension.config]
custom_setting = "value"
Access in extension:
let custom_setting = request.options
.get("custom_setting")
.and_then(|v| v.as_str());
Testing Extensions
Unit Tests
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compile() {
let ext = MyExtension;
let request = CompileRequest {
sources: vec![],
options: Default::default(),
};
let result = ext.compile(request).unwrap();
assert!(result.success);
}
}
Integration Tests
Use the CLI to test extensions:
morphir compile --language my-language --input src/
Best Practices
- Error Handling: Provide clear diagnostics with source locations
- Performance: Optimize parsing and conversion for large codebases
- Testing: Include comprehensive test coverage
- Documentation: Document language-specific features and limitations
Next Steps
- Read Extension System Design
- See Architecture Overview
- Check Development Guide