a workspace with explicit app boundaries:
npm install -g @tosiiko/mdl
mdl init --app analytics --port 4002
Each application maintains isolated configuration, pages, stylesheets, and scripts while sharing the global MDL runtime. This prevents configuration drift and enables independent deployment pipelines.
Step 2: Authoring Structure with Indentation-Based Syntax
MDL uses whitespace to define DOM hierarchy. Sections map directly to semantic HTML elements. Inline components use dot notation, and attributes are attached via @ prefixes.
page:
header:
.logo@src(assets/brand.svg)@alt(Company Brand)
nav:
.nav-link@href(/dashboard) Dashboard
.nav-link@href(/reports) Reports
.nav-link@href(/settings) Settings
main:
section@id(metricsPanel):
h2: Real-time Analytics
p: Monitor system performance and user engagement.
grid@cols(3):
.card:
h3: Active Users
.metric@id(activeCount) 1,240
.card:
h3: API Latency
.metric@id(apiLatency) 42ms
.card:
h3: Error Rate
.metric@id(errorRate) 0.03%
form@id(configForm)@submit(savePreferences):
field:
label: Notification Frequency
.select@id(freqSelect)@required
option: Hourly
option: Daily
option: Weekly
actions:
.btn-primary@id(saveBtn) Save Configuration
.btn-secondary@click(resetDefaults) Reset
Compilation Rationale: The parser constructs an Abstract Syntax Tree (AST) from indentation levels. page: becomes <main>, nav: becomes <nav>, form: becomes <form>. Dot-prefixed tokens generate class-attached elements. @ attributes map to standard DOM properties or event listeners. The compiler enforces strict hierarchy, preventing unclosed tags or mismatched nesting.
Step 3: Source Map Generation and Traceability
Every compilation produces a JSON sidecar that maps generated DOM nodes to source coordinates:
{
"version": 1,
"source": "pages/analytics.mdl",
"output": "analytics.html",
"mappings": [
{
"generated": { "line": 18, "column": 4, "end_line": 24, "end_column": 12 },
"source": { "line": 12, "column": 6, "end_line": 12, "end_column": 45 },
"kind": "section",
"name": "grid"
},
{
"generated": { "line": 28, "column": 2, "end_line": 35, "end_column": 8 },
"source": { "line": 18, "column": 4, "end_line": 18, "end_column": 52 },
"kind": "element",
"name": "form"
}
]
}
A project-level manifest (dist/mdl-manifest.json) aggregates route-to-source mappings:
[
{
"route": "/analytics",
"source": "pages/analytics.mdl",
"output": "analytics.html",
"source_map": "analytics.html.mdlmap.json"
}
]
Why this architecture: Sidecar maps enable IDE go-to-definition, precise error overlays, and CI traceability. The manifest provides a single source of truth for routing, simplifying server configuration and static hosting deployments.
Document metadata is centralized in mdl.json. The compiler injects <meta> tags, validates local assets, and generates Open Graph/Twitter card markup automatically:
{
"lang": "en",
"title": "Analytics Dashboard",
"description": "Real-time system monitoring and performance metrics.",
"canonical": "https://app.example.com/analytics",
"favicon": "assets/favicon.svg",
"social": {
"enabled": true,
"image": "assets/og-preview.png",
"twitter_site": "@platformteam"
}
}
The compiler resolves social presets into:
<meta property="og:title" content="Analytics Dashboard">
<meta property="og:description" content="Real-time system monitoring and performance metrics.">
<meta property="og:image" content="assets/og-preview.png">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@platformteam">
Architectural decision: Centralizing metadata eliminates duplication across pages, ensures consistent SEO signals, and enables build-time validation of image paths and canonical URLs.
Step 5: Diagnostic Pipeline and Machine-Readable Output
The mdl check command runs structural, accessibility, form, and asset validation. Output can be formatted for human review or consumed by CI/linters:
mdl check --json
Diagnostic categories include:
- Accessibility: Missing
alt/title, duplicate IDs, unlabeled controls, invalid aria-* references, landmark ordering
- Forms: Label/input mismatches, ungrouped radio buttons, missing
autocomplete hints
- Assets: Broken
href/link targets, missing media, recursive CSS url()/@import validation, font references
Why machine-readable output matters: CI pipelines can fail builds on accessibility violations or broken assets before deployment. Language servers consume the JSON to provide inline editor warnings, shifting quality gates left.
Step 6: Advanced Primitives and JS Island Hydration
MDL provides first-class sections for complex browser APIs. The @mount attribute wires initialization handlers without inline scripts:
canvas@id(renderSurface)@mount(initWebGL):
WebGL rendering surface for 3D metrics.
island@id(chartIsland)@mount(hydrateCharts):
Interactive data visualization container.
Compiles to:
<canvas id="renderSurface"></canvas>
<script>
document.addEventListener('DOMContentLoaded', () => {
const el = document.getElementById('renderSurface');
if (el) initWebGL(el);
});
</script>
Rationale: @mount decouples initialization logic from markup, prevents hydration mismatches, and enables selective JS loading. Islands remain inert until explicitly hydrated, improving Time to Interactive (TTI).
Pitfall Guide
1. Indentation Inconsistency Breaks AST
Explanation: MDL relies on whitespace for hierarchy. Mixing tabs and spaces or misaligning nested blocks causes parser failures or incorrect DOM nesting.
Fix: Configure your editor to use 2-space indentation. Run mdl format before commits to normalize whitespace automatically.
2. Overloading @mount Handlers
Explanation: Attaching heavy initialization logic to multiple islands on the same page can block the main thread and cause hydration race conditions.
Fix: Scope handlers to specific containers. Use requestIdleCallback or IntersectionObserver to defer non-critical hydration until the element enters the viewport.
3. Skipping mdl check --json in CI
Explanation: Relying on manual QA for accessibility or asset validation allows regressions to reach production.
Fix: Integrate mdl check --json into your CI pipeline. Configure thresholds to fail builds on error severity diagnostics. Parse the JSON output to generate compliance reports.
4. Mixing Inline Styles with CSS Bundling
Explanation: Adding inline style attributes defeats the compiler's CSS bundling pipeline and increases payload size.
Fix: Define all visual rules in .css files. Use MDL's dot notation for class assignment. Let the compiler merge and minify stylesheets during build.
Explanation: Relative paths in mdl.json that don't match the actual asset directory cause broken social cards and missing favicons.
Fix: Use paths relative to the project root. Run mdl check to validate all local references before deployment. Maintain a consistent assets/ directory structure.
6. Assuming Runtime Framework Behavior
Explanation: MDL is a compile-time tool. Expecting client-side routing, state management, or reactive updates will lead to architectural mismatches.
Fix: Treat MDL as a structure generator. Handle interactivity with vanilla JS, lightweight frameworks, or web components. Use @click and @submit for event wiring, not state synchronization.
7. Workspace Port and Config Collisions
Explanation: Running multiple apps without explicit port assignment causes dev server conflicts and configuration overwrites.
Fix: Always specify --port during mdl init. Maintain separate mdl.json files per app. Use environment variables to toggle build targets in CI.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Marketing / Landing Pages | MDL + CSS bundling | Zero runtime overhead, fast builds, SEO-friendly output | Low (static hosting) |
| Admin Dashboards / Internal Tools | MDL + JS Islands | Structured markup, selective hydration, clear separation of concerns | Medium (CDN + minimal JS) |
| Component-Heavy SaaS Apps | MDL for shell + Framework for islands | Compiler handles structure, framework manages state/interactivity | Medium-High (hybrid stack) |
| Legacy HTML Migration | MDL incremental adoption | Replace static templates first, wire JS islands gradually | Low-Medium (phased rollout) |
Configuration Template
{
"lang": "en",
"title": "Platform Dashboard",
"description": "Centralized monitoring and configuration interface.",
"canonical": "https://app.example.com/dashboard",
"favicon": "assets/favicon.svg",
"social": {
"enabled": true,
"image": "assets/og-dashboard.png",
"twitter_site": "@platformteam"
},
"build": {
"source_maps": true,
"css_bundle": "app.css",
"js_entry": "main.js",
"diagnostics": {
"fail_on": ["error"],
"output_format": "json"
}
},
"workspace": {
"apps": [
{
"name": "public",
"port": 3999,
"root": "apps/public"
},
{
"name": "admin",
"port": 4001,
"root": "apps/admin"
}
]
}
}
Quick Start Guide
- Install the compiler:
npm install -g @tosiiko/mdl
- Scaffold a project:
mdl new my-platform
- Start the dev server:
cd my-platform && mdl serve (runs at localhost:3999 with live reload)
- Validate structure:
mdl check --json (review diagnostics or pipe to CI)
- Build for production:
mdl build (generates optimized HTML, bundled CSS, and source map sidecars)