8.2 KiB
Manganis Asset System Architecture
Manganis provides compile-time asset management through the asset!() macro, enabling type-safe asset references with automatic optimization and cache-busting.
Asset Macro Architecture
Compile-Time Expansion
asset!("/assets/image.png", AssetOptions::image())
↓
1. Path Resolution
- Resolves relative to CARGO_MANIFEST_DIR
- Validates path exists and stays within crate
2. File Hashing
- Creates DefaultHasher from path + options + span
- Produces 16-character hex hash
3. Generate Link Section
- Creates __ASSETS__{hash} symbol
- Creates __MANGANIS__{hash} for legacy CLI
- Both use CBOR serialization via const_serialize
4. Code Generation
- Emits BundledAsset const with PLACEHOLDER_HASH
- Creates Asset struct with function pointers
- Uses volatile reads to prevent optimization
Link Section Generation
Two static link sections for backward compatibility:
-
Legacy Format (
__MANGANIS__{hash}):- Uses const_serialize_07
- For older CLIs (0.7.0-0.7.1)
-
New Format (
__ASSETS__{hash}):- Uses const_serialize_08 (CBOR)
- For dx >= 0.7.2
Both start with PLACEHOLDER_HASH sentinel, replaced by CLI during build.
Asset Types and Options
Type Hierarchy
AssetOptions
├── ImageAssetOptions
│ ├── format: ImageFormat (Png, Jpg, Webp, Avif)
│ ├── size: ImageSize (Manual | Automatic)
│ └── preload: bool
│
├── CssAssetOptions
│ ├── minify: bool (default: true)
│ ├── preload: bool
│ └── static_head: bool
│
├── JsAssetOptions
│ ├── minify: bool (default: true)
│ ├── preload: bool
│ └── static_head: bool
│
├── CssModuleAssetOptions
│ ├── minify: bool
│ └── preload: bool
│
├── FolderAssetOptions
│ └── (no custom options)
│
└── Unknown (generic binary)
All options serializable at const-time via SerializeConst derive.
CSS Module Integration
css_module!(Styles = "/my.module.css", AssetOptions::css_module());
// Generates:
struct Styles {}
impl Styles {
pub const header: &str = "abc[hash]"; // Unique scoped class
pub const button: &str = "def[hash]";
}
CSS Identifier Collection:
- Scans for
.classNameand#idNamepatterns - Converts to snake_case
- Creates ConstStr values that auto-inject stylesheet on deref
Const Serialization System
CBOR Format (RFC 8949 Subset)
Supported Major Types:
- UnsignedInteger (0)
- NegativeInteger (1)
- Bytes (2)
- String (3)
- Array (4)
- Map (5)
Not Supported: Tagged values (6), floating point (7)
Const Serialization Mechanism
Layout-based binary copying at compile time:
unsafe trait SerializeConst: Sized {
const MEMORY_LAYOUT: Layout;
}
enum Layout {
Enum(EnumLayout), // repr(C, u8) required
Struct(StructLayout), // Field offsets
Array(ArrayLayout), // Fixed-size
Primitive(PrimitiveLayout),
List(ListLayout), // Variable length
}
Process:
- Calculate total size from MEMORY_LAYOUT
- Copy bytes from source following layout
- Apply transformations (endianness)
- Append to ConstVec buffer
ConstStr Implementation
pub struct ConstStr {
bytes: [MaybeUninit<u8>; 256], // Fixed 256-byte buffer
len: u32,
}
Used for asset paths and CSS identifiers. Serialized as List layout.
Asset Resolution Pipeline
Build-Time Processing (dx CLI)
Phase 1: Binary Scanning
1. Read compiled binary
2. Find __ASSETS__ symbols via objfile parser
3. Platform-specific methods:
- Native: object crate symbol tables
- Windows PE: PDB file parsing
- WASM: walrus data section parsing
- Android: NDK handling
Phase 2: Asset Deserialization
For each __ASSETS__{hash}:
1. Read serialized bytes from section
2. Deserialize BundledAsset via const_serialize_08
3. Fall back to const_serialize_07 if fails
4. Extract: absolute_source_path, bundled_path, options
Phase 3: Unique Asset Collection
- Deduplicate by (absolute_path, options) pair
- Create AssetManifest with unique set
Phase 4: Asset Optimization
| Type | Processing |
|---|---|
| Image | Resize, convert format, optimize |
| CSS | Minify if enabled |
| JS | Minify if enabled |
| Folder | Recursive copy |
| Unknown | Direct copy with optional hash |
Phase 5: Binary Patching
For each processed asset:
1. Compute final hash (content + options + version)
2. Create new BundledAsset with:
- bundled_path: "/assets/{output-filename}"
3. Serialize to CBOR
4. Locate __ASSETS__{hash} in binary
5. Overwrite bytes at symbol offset
Runtime Resolution
Development Mode (!is_bundled_app()):
Asset::resolve()
→ Returns bundled().absolute_source_path
→ Browser/native accesses original files
Production Mode (is_bundled_app()):
Asset::resolve()
→ Constructs path from:
* base_path() (e.g., "/app")
* bundled_path from BundledAsset
→ Returns "/app/assets/{output-filename}"
Platform-Specific Resolution
Web (WASM):
resolve_web_asset(): Fetches via HTTPfetch()API- Supports CORS headers
- Returns
Vec<u8>
Desktop:
resolve_asset_path_from_filesystem()- Bundle structure varies:
- macOS:
../Resources/assets/ - Linux:
../lib/{product}/assets/ - Windows:
assets/(same directory as exe)
- macOS:
Android:
to_java_load_asset(): Uses NDK AssetManager- Accesses APK's assets/ directory
- Debug fallback:
/data/local/tmp/dx/
Hash-Based Cache Busting
Hash Computation Layers
INPUT_HASH (macro generation):
= DefaultHasher(span.debug_string + options.string + asset_path)
Used for: __ASSETS__{INPUT_HASH} symbol
CONTENT_HASH (build time):
= Hash(source_contents + applied_options + manganis_version)
Used for: Final output filename
Example: image.png → image-a1b2c3d4e5f6g7h8.webp
CSS_MODULE_HASH:
= Hash(css_module_options + content_hash)
Used for: Scoped CSS identifiers
Filename Generation
IMAGE: /assets/photo.png → /assets/photo-{hash}.webp
CSS/JS: /assets/style.css → /assets/style-{hash}.css
FOLDER: /assets (folder) → /assets/ (unchanged)
Key Architectural Patterns
Const-Time Code Generation
- Uses proc_macro for compile-time expansion
- Leverages const fn exclusively (no allocators)
- Binary layout determined at compile time
Volatile Reads for Correctness
std::ptr::read_volatile()prevents optimizing away link section reads- Critical because link section gets patched post-compilation
Layout-Respecting Serialization
- Types must have well-defined memory layout
repr(C, u8)for enums- CBOR for variable-length fields
Dual Path Resolution
- Dev: Returns absolute_source_path
- Prod: Returns bundled_path with base_path
- Same Asset instance works across configurations
Symbol-Based Discovery
- No manifest file needed
- Binary symbols serve as asset registry
- Scales with multi-crate applications
Extension Points
Adding New Asset Types
- Create Options Struct:
#[derive(SerializeConst, ...)]
pub struct VideoAssetOptions {
format: VideoFormat,
preload: bool,
}
- Add AssetVariant Case:
pub enum AssetVariant {
Video(VideoAssetOptions),
}
- Implement Builder:
pub const fn video() -> AssetOptionsBuilder<VideoAssetOptions> {
AssetOptionsBuilder::variant(VideoAssetOptions::default())
}
- Register in CLI (
assets.rs):
AssetVariant::Video(opts) => {
// Video-specific processing
}
Future Concepts
IAAC (Infrastructure as Code):
const DATABASE_CONFIG: Asset = asset!("/config/db.yaml");
// CLI extracts, validates, deploys
secret!() Macro:
const API_KEY: Secret = secret!("DIOXUS_API_KEY");
// Compile-time validation
// Runtime injection from secure store
Version Compatibility
Legacy (0.7.0-0.7.1):
- const_serialize_07
- Symbol:
__MANGANIS__{hash}
Current (0.7.2+):
- const_serialize_08 (CBOR)
- Symbol:
__ASSETS__{hash}
Migration: CLI tries new format first, falls back to legacy if deserialization fails or PLACEHOLDER_HASH still present.