CWeb Templating
1. What the system actually is
This is not a runtime template loader. .cweb files are translated into normal C by the cweb CLI (cweb build) before the actual app build, and are then compiled together like ordinary C source files.
Concrete flow:
cweb buildrecursively scansapp/templatesfor.cwebfiles.- Each file is translated into
build/<basename>.candbuild/<basename>.h. - In addition,
build/auto_routes.c,build/build.h, andbuild/sources.confare generated.
The .cweb file itself is no longer opened at runtime. The runtime path only uses the generated C functions.
Minimal workflow for new templates
- Create the file
app/templates/<name>.template.cweb. - In
<%header ... %>, declare your structs/includes andchar* <name>_template(...);. - Write the template body using HTML,
{...}output,<% ... %>,<cif>,<cloop>, or<cstyle>. - Run
cweb build. - Include the generated header file from
build/in the route. - Populate the data structure in C.
- Call the generated function directly.
- If using external
<cstyle>, additionally expose<name>_template_css(...)and wire it into the HTML usingcweb_setAssetLink(...).
Important details:
- Only
app/templatesis scanned, not arbitrary other template directories. - The output name is derived from the filename without the last extension.
home.template.cwebtherefore becomesbuild/home.template.candbuild/home.template.h.- Depending on your workflow, you may need to run
cweb buildmanually again after template changes.
2. Structure of a .cweb file
The github repo app example app/templates/home.template.cweb shows the full pattern.
2.1 The header block is practically required
At the top there is a special header block:
<%header
typedef struct {
const char *page_title;
...
} page_data_t;
char* home_template(page_data_t *data);
%>
This block is evaluated separately by the generator.
It has two roles:
- It provides types, includes,
typedefs, and declarations for the generated header file. - It provides the signature of the actual render function.
Important:
- The render function must return
char*. - The signature must be inside the header block.
- If the parser cannot find a matching
char*function there, generation of the.cfile fails.
2.2 What gets copied into the generated header
The content of the header block is copied almost directly into the generated .h. In addition, the generator injects standard includes like stdio.h, stdlib.h, string.h, time.h, and stdbool.h.
If the template contains external CSS via <cstyle>, the generator also automatically adds an extra declaration char* <template>_css(<params>); unless you already declared it yourself in the header.
3. How data is passed into the template
The system is not data-driven like JSON- or dictionary-based templates. It is purely C-typed. in Future version there will be also json option.
The app example does it like this:
- A
typedef structis defined in the.cwebheader. - The render function takes a pointer to exactly that type.
- The route builds a real C struct.
- The route calls the generated function directly.
Example from home.page.c:
page_data_t data = {0};
data.page_title = "CWeb Template Engine Demo";
...
char *html = home_template(&data);
this means:
- If the struct field does not exist, C compilation fails.
- If you write
data->fooin the template even thoughfoois not part of the struct, the generated.cfails to compile. - IDE support works well precisely because you are working with real C types.
4. How the route gets access to the generated files
The route includes the generated header file directly:
#include "../../build/home.template.h"
After that, page_data_t, home_template(...), and for external CSS also home_template_css(...) are known.
5. Variables
The variable syntax is:
{expression}
{expression:type}
## 6. Code Code inside .cweb template files
Inside `<% ... %>` you write C code directly. That code is copied almost unchanged into the generated function.
Examples:
```c
<% if (data->is_admin) { %>
<p>you are admin</p>
...
<% } %>
or:
<%
for (size_t i = 0; i < data->menu_count; i++) {
cweb_output_raw("<li>");
cweb_output_html(data->menu_items[i]);
cweb_output_raw("</li>");
}
%>
Important:
- Inside such blocks, you can directly use
cweb_output_raw(...)andcweb_output_html(...). - This is the lowest-level and most flexible part of the system.
c code or template hhtml elements
you can choose if you want to write c code or use template html elements like <cif> or <cloop>
7 Security filter for C blocks
<% ... %> is not completely unrestricted. The lexer rejects certain patterns, for example:
system(exec(popen(fork(unlink(remove(rmdir(#include#define#pragma
Practical consequence:
- Includes and type definitions belong in the
<%header ... %>block. - The body is intended for render logic, not preprocessor directives.
8. Why {...} inside CSS and JavaScript is not treated as a variable
The lexer deliberately suppresses variable detection inside <style>, <cstyle>, and <script> so that normal CSS and JS syntax with curly braces does not break.
Practical consequence:
{data->...}interpolation does not work inside CSS and JavaScript the same way it does in the HTML body.- For dynamic CSS/JS, you must use
<% ... %>there.