Genearte .zig verions of templates to use in production.
This commit is contained in:
89
examples/demo/README.md
Normal file
89
examples/demo/README.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Pugz Demo App
|
||||
|
||||
A comprehensive e-commerce demo showcasing Pugz template engine capabilities.
|
||||
|
||||
## Features
|
||||
|
||||
- Template inheritance (extends/block)
|
||||
- Partial includes (header, footer)
|
||||
- Mixins with parameters (product-card, rating, forms)
|
||||
- Conditionals and loops
|
||||
- Data binding
|
||||
- Pretty printing
|
||||
|
||||
## Running the Demo
|
||||
|
||||
### Option 1: Runtime Templates (Default)
|
||||
|
||||
```bash
|
||||
cd examples/demo
|
||||
zig build run
|
||||
```
|
||||
|
||||
Then visit `http://localhost:5882` in your browser.
|
||||
|
||||
### Option 2: Compiled Templates (Experimental)
|
||||
|
||||
Compiled templates offer maximum performance by pre-compiling templates to Zig functions at build time.
|
||||
|
||||
**Note:** Compiled templates currently have some code generation issues and are disabled by default.
|
||||
|
||||
To try compiled templates:
|
||||
|
||||
1. **Compile templates**:
|
||||
```bash
|
||||
# From demo directory
|
||||
./compile_templates.sh
|
||||
|
||||
# Or manually from project root
|
||||
cd ../..
|
||||
zig build demo-compile-templates
|
||||
```
|
||||
|
||||
This generates compiled templates in `generated/root.zig`
|
||||
|
||||
2. **Enable in code**:
|
||||
- Open `src/main.zig`
|
||||
- Set `USE_COMPILED_TEMPLATES = true`
|
||||
|
||||
3. **Build and run**:
|
||||
```bash
|
||||
zig build run
|
||||
```
|
||||
|
||||
The `build.zig` automatically detects if `generated/` exists and includes the templates module.
|
||||
|
||||
## Template Structure
|
||||
|
||||
```
|
||||
views/
|
||||
├── layouts/ # Layout templates
|
||||
│ └── base.pug
|
||||
├── pages/ # Page templates
|
||||
│ ├── home.pug
|
||||
│ ├── products.pug
|
||||
│ ├── cart.pug
|
||||
│ └── ...
|
||||
├── partials/ # Reusable partials
|
||||
│ ├── header.pug
|
||||
│ ├── footer.pug
|
||||
│ └── head.pug
|
||||
├── mixins/ # Reusable components
|
||||
│ ├── product-card.pug
|
||||
│ ├── buttons.pug
|
||||
│ ├── forms.pug
|
||||
│ └── ...
|
||||
└── includes/ # Other includes
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Known Issues with Compiled Templates
|
||||
|
||||
The template code generation (`src/tpl_compiler/zig_codegen.zig`) has some bugs:
|
||||
|
||||
1. `helpers.zig` import paths need to be relative
|
||||
2. Double quotes being escaped incorrectly in string literals
|
||||
3. Field names with dots causing syntax errors
|
||||
4. Some undefined variables in generated code
|
||||
|
||||
These will be fixed in a future update. For now, runtime templates work perfectly!
|
||||
@@ -1,4 +1,5 @@
|
||||
const std = @import("std");
|
||||
const pugz = @import("pugz");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
@@ -14,22 +15,65 @@ pub fn build(b: *std.Build) void {
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
// Main executable
|
||||
const pugz_mod = pugz_dep.module("pugz");
|
||||
|
||||
// ===========================================================================
|
||||
// Template Compilation Step - OPTIONAL
|
||||
// ===========================================================================
|
||||
// This creates a "compile-templates" build step that users can run manually:
|
||||
// zig build compile-templates
|
||||
//
|
||||
// Templates are compiled to generated/ and automatically used if they exist
|
||||
const compile_templates = pugz.compile_tpls.addCompileStep(b, .{
|
||||
.name = "compile-templates",
|
||||
.source_dirs = &.{
|
||||
"views/pages",
|
||||
"views/partials",
|
||||
},
|
||||
.output_dir = "generated",
|
||||
});
|
||||
|
||||
const compile_step = b.step("compile-templates", "Compile Pug templates");
|
||||
compile_step.dependOn(&compile_templates.step);
|
||||
|
||||
// ===========================================================================
|
||||
// Main Executable
|
||||
// ===========================================================================
|
||||
// Check if compiled templates exist
|
||||
const has_templates = blk: {
|
||||
var dir = std.fs.cwd().openDir("generated", .{}) catch break :blk false;
|
||||
dir.close();
|
||||
break :blk true;
|
||||
};
|
||||
|
||||
// Build imports list
|
||||
var imports: std.ArrayListUnmanaged(std.Build.Module.Import) = .{};
|
||||
defer imports.deinit(b.allocator);
|
||||
|
||||
imports.append(b.allocator, .{ .name = "pugz", .module = pugz_mod }) catch @panic("OOM");
|
||||
imports.append(b.allocator, .{ .name = "httpz", .module = httpz_dep.module("httpz") }) catch @panic("OOM");
|
||||
|
||||
// Only add templates module if they exist
|
||||
if (has_templates) {
|
||||
const templates_mod = b.createModule(.{
|
||||
.root_source_file = b.path("generated/root.zig"),
|
||||
});
|
||||
imports.append(b.allocator, .{ .name = "templates", .module = templates_mod }) catch @panic("OOM");
|
||||
}
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "demo",
|
||||
.root_module = b.createModule(.{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.imports = &.{
|
||||
.{ .name = "pugz", .module = pugz_dep.module("pugz") },
|
||||
.{ .name = "httpz", .module = httpz_dep.module("httpz") },
|
||||
},
|
||||
.imports = imports.items,
|
||||
}),
|
||||
});
|
||||
|
||||
b.installArtifact(exe);
|
||||
|
||||
// Run step
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ const pugz = @import("pugz");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
// Mode selection: set to true to use compiled templates
|
||||
// Run `zig build compile-templates` to generate templates first
|
||||
const USE_COMPILED_TEMPLATES = true;
|
||||
|
||||
// ============================================================================
|
||||
// Data Types
|
||||
// ============================================================================
|
||||
@@ -163,6 +167,15 @@ const sample_cart_items = [_]CartItem{
|
||||
.quantity = "1",
|
||||
.total = "79.99",
|
||||
},
|
||||
.{
|
||||
.id = "2",
|
||||
.name = "Laptop",
|
||||
.price = "500.00",
|
||||
.image = "/images/keyboard.jpg",
|
||||
.variant = "BLK",
|
||||
.quantity = "1",
|
||||
.total = "500.00",
|
||||
},
|
||||
.{
|
||||
.id = "5",
|
||||
.name = "Mechanical Keyboard",
|
||||
@@ -224,11 +237,19 @@ const App = struct {
|
||||
// ============================================================================
|
||||
|
||||
fn home(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
|
||||
const html = app.view.render(res.arena, "pages/home", .{
|
||||
const html = if (USE_COMPILED_TEMPLATES) blk: {
|
||||
const templates = @import("templates");
|
||||
break :blk try templates.pages_home.render(res.arena, .{
|
||||
.title = "Home",
|
||||
.cartCount = "2",
|
||||
.authenticated = true,
|
||||
.items = sample_products,
|
||||
});
|
||||
} else app.view.render(res.arena, "pages/home", .{
|
||||
.title = "Home",
|
||||
.cartCount = "2",
|
||||
.authenticated = true,
|
||||
.items = &[_][]const u8{ "Wireless Headphones", "Smart Watch", "Laptop Stand", "USB-C Hub" },
|
||||
.items = sample_products,
|
||||
}) catch |err| {
|
||||
return renderError(res, err);
|
||||
};
|
||||
@@ -238,7 +259,14 @@ fn home(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
|
||||
}
|
||||
|
||||
fn products(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
|
||||
const html = app.view.render(res.arena, "pages/products", .{
|
||||
const html = if (USE_COMPILED_TEMPLATES) blk: {
|
||||
const templates = @import("templates");
|
||||
break :blk try templates.pages_products.render(res.arena, .{
|
||||
.title = "All Products",
|
||||
.cartCount = "2",
|
||||
.productCount = "6",
|
||||
});
|
||||
} else app.view.render(res.arena, "pages/products", .{
|
||||
.title = "All Products",
|
||||
.cartCount = "2",
|
||||
.productCount = "6",
|
||||
@@ -254,7 +282,17 @@ fn productDetail(app: *App, req: *httpz.Request, res: *httpz.Response) !void {
|
||||
const id = req.param("id") orelse "1";
|
||||
_ = id;
|
||||
|
||||
const html = app.view.render(res.arena, "pages/product-detail", .{
|
||||
const html = if (USE_COMPILED_TEMPLATES) blk: {
|
||||
const templates = @import("templates");
|
||||
break :blk try templates.pages_product_detail.render(res.arena, .{
|
||||
.cartCount = "2",
|
||||
.productName = "Wireless Headphones",
|
||||
.category = "Electronics",
|
||||
.price = "79.99",
|
||||
.description = "Premium wireless headphones with active noise cancellation. Experience crystal-clear audio whether you're working, traveling, or relaxing at home.",
|
||||
.sku = "WH-001-BLK",
|
||||
});
|
||||
} else app.view.render(res.arena, "pages/product-detail", .{
|
||||
.cartCount = "2",
|
||||
.productName = "Wireless Headphones",
|
||||
.category = "Electronics",
|
||||
@@ -270,7 +308,16 @@ fn productDetail(app: *App, req: *httpz.Request, res: *httpz.Response) !void {
|
||||
}
|
||||
|
||||
fn cart(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
|
||||
const html = app.view.render(res.arena, "pages/cart", .{
|
||||
const html = if (USE_COMPILED_TEMPLATES) blk: {
|
||||
const templates = @import("templates");
|
||||
break :blk try templates.pages_cart.render(res.arena, .{
|
||||
.cartCount = "2",
|
||||
.subtotal = sample_cart.subtotal,
|
||||
.tax = sample_cart.tax,
|
||||
.total = sample_cart.total,
|
||||
.cartItems = &sample_cart_items,
|
||||
});
|
||||
} else app.view.render(res.arena, "pages/cart", .{
|
||||
.title = "Shopping Cart",
|
||||
.cartCount = "2",
|
||||
.cartItems = &sample_cart_items,
|
||||
@@ -287,7 +334,13 @@ fn cart(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
|
||||
}
|
||||
|
||||
fn about(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
|
||||
const html = app.view.render(res.arena, "pages/about", .{
|
||||
const html = if (USE_COMPILED_TEMPLATES) blk: {
|
||||
const templates = @import("templates");
|
||||
break :blk try templates.pages_about.render(res.arena, .{
|
||||
.title = "About",
|
||||
.cartCount = "2",
|
||||
});
|
||||
} else app.view.render(res.arena, "pages/about", .{
|
||||
.title = "About",
|
||||
.cartCount = "2",
|
||||
}) catch |err| {
|
||||
@@ -299,7 +352,12 @@ fn about(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
|
||||
}
|
||||
|
||||
fn includeDemo(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
|
||||
const html = app.view.render(res.arena, "pages/include-demo", .{
|
||||
const html = if (USE_COMPILED_TEMPLATES) blk: {
|
||||
const templates = @import("templates");
|
||||
break :blk try templates.pages_include_demo.render(res.arena, .{
|
||||
.cartCount = "2",
|
||||
});
|
||||
} else app.view.render(res.arena, "pages/include-demo", .{
|
||||
.title = "Include Demo",
|
||||
.cartCount = "2",
|
||||
}) catch |err| {
|
||||
@@ -310,10 +368,39 @@ fn includeDemo(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
|
||||
res.body = html;
|
||||
}
|
||||
|
||||
fn simpleCompiled(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
|
||||
if (USE_COMPILED_TEMPLATES) {
|
||||
const templates = @import("templates");
|
||||
const html = try templates.pages_simple.render(res.arena, .{
|
||||
.title = "Compiled Template Demo",
|
||||
.heading = "Hello from Compiled Templates!",
|
||||
.siteName = "Pugz Demo",
|
||||
});
|
||||
res.content_type = .HTML;
|
||||
res.body = html;
|
||||
} else {
|
||||
const html = app.view.render(res.arena, "pages/simple", .{
|
||||
.title = "Simple Page",
|
||||
.heading = "Hello from Runtime Templates!",
|
||||
.siteName = "Pugz Demo",
|
||||
}) catch |err| {
|
||||
return renderError(res, err);
|
||||
};
|
||||
res.content_type = .HTML;
|
||||
res.body = html;
|
||||
}
|
||||
}
|
||||
|
||||
fn notFound(app: *App, _: *httpz.Request, res: *httpz.Response) !void {
|
||||
res.status = 404;
|
||||
|
||||
const html = app.view.render(res.arena, "pages/404", .{
|
||||
const html = if (USE_COMPILED_TEMPLATES) blk: {
|
||||
const templates = @import("templates");
|
||||
break :blk try templates.pages_404.render(res.arena, .{
|
||||
.title = "Page Not Found",
|
||||
.cartCount = "2",
|
||||
});
|
||||
} else app.view.render(res.arena, "pages/404", .{
|
||||
.title = "Page Not Found",
|
||||
.cartCount = "2",
|
||||
}) catch |err| {
|
||||
@@ -399,6 +486,7 @@ pub fn main() !void {
|
||||
router.get("/cart", cart, .{});
|
||||
router.get("/about", about, .{});
|
||||
router.get("/include-demo", includeDemo, .{});
|
||||
router.get("/simple", simpleCompiled, .{});
|
||||
|
||||
// Static files
|
||||
router.get("/css/*", serveStatic, .{});
|
||||
@@ -421,6 +509,7 @@ pub fn main() !void {
|
||||
\\ GET /cart - Shopping cart
|
||||
\\ GET /about - About page
|
||||
\\ GET /include-demo - Include directive demo
|
||||
\\ GET /simple - Simple compiled template demo
|
||||
\\
|
||||
\\ Press Ctrl+C to stop.
|
||||
\\
|
||||
|
||||
8
examples/demo/views/pages/simple.pug
Normal file
8
examples/demo/views/pages/simple.pug
Normal file
@@ -0,0 +1,8 @@
|
||||
doctype html
|
||||
html
|
||||
head
|
||||
title #{title}
|
||||
body
|
||||
h1 #{heading}
|
||||
p Welcome to #{siteName}!
|
||||
p This page was rendered using compiled Pug templates.
|
||||
Reference in New Issue
Block a user