feat: add template inheritance (extends/block) support

- ViewEngine now supports extends and named blocks
- Each route gets exclusive cached AST (no shared parent layouts)
- Fix iteration over struct arrays in each loops
- Add demo app with full e-commerce layout using extends
- Serve static files from public folder
- Bump version to 0.3.0
This commit is contained in:
2026-01-25 15:23:57 +05:30
parent 776f8a68f5
commit 1b2da224be
52 changed files with 2962 additions and 728 deletions

View File

@@ -1,5 +0,0 @@
mixin alert(message)
div.alert(role="alert" class!=attributes.class)
svg(xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24")
path(stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z")
span= message

View File

@@ -1,2 +0,0 @@
mixin alert_error(message)
+alert(message)(class="alert-error")

View File

@@ -0,0 +1,12 @@
//- Alert/notification mixins
mixin alert(message, type)
- var alertClass = type ? "alert alert-" + type : "alert alert-info"
.alert(class=alertClass)
p= message
mixin alert-dismissible(message, type)
- var alertClass = type ? "alert alert-" + type : "alert alert-info"
.alert.alert-dismissible(class=alertClass)
p= message
button.alert-close(type="button" aria-label="Close") x

View File

@@ -1,5 +1,15 @@
mixin btn(text, type="primary")
button(class="btn btn-" + type)= text
//- Button mixins with various styles
mixin btn-link(href, text)
a.btn.btn-link(href=href)= text
mixin btn(text, type)
- var btnClass = type ? "btn btn-" + type : "btn btn-primary"
button(class=btnClass)= text
mixin btn-link(href, text, type)
- var btnClass = type ? "btn btn-" + type : "btn btn-primary"
a(href=href class=btnClass)= text
mixin btn-icon(icon, text, type)
- var btnClass = type ? "btn btn-" + type : "btn btn-primary"
button(class=btnClass)
span.icon= icon
span= text

View File

@@ -1,11 +0,0 @@
mixin card(title)
.card
.card-header
h3= title
.card-body
block
mixin card-simple(title, body)
.card
h3= title
p= body

View File

@@ -0,0 +1,17 @@
//- Cart item display
mixin cart-item(item)
.cart-item
.cart-item-image
img(src=item.image alt=item.name)
.cart-item-details
h4.cart-item-name #{item.name}
p.cart-item-variant #{item.variant}
span.cart-item-price $#{item.price}
.cart-item-quantity
button.qty-btn.qty-minus -
input.qty-input(type="number" value=item.quantity min="1")
button.qty-btn.qty-plus +
.cart-item-total
span $#{item.total}
button.cart-item-remove(aria-label="Remove item") x

View File

@@ -0,0 +1,25 @@
//- Form input mixins
mixin input(name, label, type, placeholder)
.form-group
label(for=name)= label
input.form-control(type=type id=name name=name placeholder=placeholder)
mixin input-required(name, label, type, placeholder)
.form-group
label(for=name)
= label
span.required *
input.form-control(type=type id=name name=name placeholder=placeholder required)
mixin select(name, label, options)
.form-group
label(for=name)= label
select.form-control(id=name name=name)
each opt in options
option(value=opt.value)= opt.label
mixin textarea(name, label, placeholder, rows)
.form-group
label(for=name)= label
textarea.form-control(id=name name=name placeholder=placeholder rows=rows)

View File

@@ -1,4 +0,0 @@
mixin input_text(name, label, placeholder)
fieldset.fieldset
legend.fieldset-legend= label
input(type="text" name=name class="input" placeholder=placeholder)

View File

@@ -0,0 +1,38 @@
//- Product card mixin - displays a product in grid/list view
//- Parameters:
//- product: { id, name, price, image, rating, category }
mixin product-card(product)
article.product-card
a.product-image(href="/products/" + product.id)
img(src=product.image alt=product.name)
if product.sale
span.badge.badge-sale Sale
.product-info
span.product-category #{product.category}
h3.product-name
a(href="/products/" + product.id) #{product.name}
.product-rating
+rating(product.rating)
.product-footer
span.product-price $#{product.price}
button.btn.btn-primary.btn-sm(data-product=product.id) Add to Cart
//- Featured product card with larger display
mixin product-featured(product)
article.product-card.product-featured
.product-image-large
img(src=product.image alt=product.name)
if product.sale
span.badge.badge-sale Sale
.product-details
span.product-category #{product.category}
h2.product-name #{product.name}
p.product-description #{product.description}
.product-rating
+rating(product.rating)
span.review-count (#{product.reviewCount} reviews)
.product-price-large $#{product.price}
.product-actions
button.btn.btn-primary.btn-lg Add to Cart
button.btn.btn-outline Wishlist

View File

@@ -0,0 +1,13 @@
//- Star rating display
//- Parameters:
//- stars: number of stars (1-5)
mixin rating(stars)
.stars
- var i = 1
while i <= 5
if i <= stars
span.star.star-filled
else
span.star.star-empty
- i = i + 1