Table of Contents
Generated Code
The compiler turns every .kmx file into one Kotlin file that calls the kmx-runtime builders. Reading it explains the rules on the Expressions page and helps with kotlinc errors, which point at this file.
Example
src/main/kmx/shop/order_card.kmx:
import com.example.Order
props: Order
val total = items.sumOf { it.price * it.qty }
<div class="order" data-id="{id}">
<p>Order #{id}: {items.size} items, {total}</p>
@for (item in items) {
<span title={item.note}>{item.name}</span>
} separator {
{", "}
}
{coupon?.let { <b>{it.code}</b> }}
</div>
build/generated/kmx/main/kotlin/shop/OrderCard.kt, exactly as the compiler writes it:
// Generated by KMX from shop/order_card.kmx. Do not edit.
@file:Suppress("RedundantVisibilityModifier", "RemoveRedundantQualifierName", "unused")
package shop
import com.example.Order
public object OrderCard : xyz.nillpoe.kmx.KmxTemplate<Order> {
override val source: String get() = "shop/order_card.kmx"
override fun render(props: Order, scope: xyz.nillpoe.kmx.RenderScope): xyz.nillpoe.kmx.Node =
kotlin.with(scope) {
kotlin.with(props) {
val total = items.sumOf { it.price * it.qty } // shop/order_card.kmx:5
xyz.nillpoe.kmx.markup {
element("div") {
attribute("class", "order")
attribute("data-id", (xyz.nillpoe.kmx.interpolate(id))) // shop/order_card.kmx:7
element("p") {
text("Order #")
insert(id) // shop/order_card.kmx:8
text(": ")
insert(items.size) // shop/order_card.kmx:8
text(" items, ")
insert(total) // shop/order_card.kmx:8
}
kotlin.run {
var kmxFirst0 = true
for (item in items) { // shop/order_card.kmx:9
val kmxItem0 = xyz.nillpoe.kmx.markup {
element("span") {
attribute("title", item.note) // shop/order_card.kmx:10
insert(item.name) // shop/order_card.kmx:10
}
}
if (!kmxFirst0) {
insert(", ") // shop/order_card.kmx:12
}
kmxFirst0 = false
insert(kmxItem0)
}
}
insert(coupon?.let { xyz.nillpoe.kmx.markup {
element("b") {
insert(it.code) // shop/order_card.kmx:14
}
} }) // shop/order_card.kmx:14
}
}
}
}
}
What maps to what
| Template | Generated |
|---|---|
file shop/order_card.kmx |
package shop, object OrderCard (PascalCase file name) |
import ... |
copied, plus the build's default imports |
props: Order |
KmxTemplate<Order>, and kotlin.with(props) so members read directly |
props(a: A, b: B) |
nested class Props(val a: A, val b: B) with toString, and render(a, b, scope) |
no props |
KmxTemplate<kotlin.Unit> and render(scope) |
component Card(...) |
fun xyz.nillpoe.kmx.RenderScope.Card(...): xyz.nillpoe.kmx.Node |
| header code | copied before markup { } |
<tag> |
element("tag") { ... }, or element("tag") when empty |
| literal text | text("..."), with whitespace and entities already resolved |
{expr} in text |
insert(expr) |
name="..." |
attribute("name", "...") |
name={expr} |
attribute("name", expr) |
name="a{expr}" |
attribute("name", ("a" + xyz.nillpoe.kmx.interpolate(expr))) |
name |
attribute("name", true) |
{...map} |
attributes(map) |
<Card title="x">body</Card> |
insert(Card(title = "x", children = xyz.nillpoe.kmx.markup { ... })) |
<!-- c --> |
comment(" c ") |
<>...</> |
the children, inline |
| markup inside Kotlin | xyz.nillpoe.kmx.markup { ... } |
@if / else if / else |
if / else if / else |
@for |
for; with separator or empty, wrapped in kotlin.run with a kmxFirstN flag. With a separator, each item is built into kmxItemN first and inserted after the separator, so the code keeps the template's order |
@when |
when, each branch a block |
@{ ... } |
the statements |
The // file.kmx:line comment on a statement names the template line it came from. KmxOutput.mappings lists every range of template Kotlin copied into the code, in the same order in both; editors use it to move diagnostics and positions between the two, see Editor Support. Runtime and stdlib calls are fully qualified (kotlin.with, kotlin.run, xyz.nillpoe.kmx.markup) so names in the template cannot capture them. The builder calls (element, text, insert, attribute) are members of the innermost builder.
Visibility
Declarations are public by default. The Gradle plugin's kmx { internal = true }, kmxc --internal, or KmxOptions(visibility = KmxVisibility.INTERNAL) make them internal.