templa-js 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +413 -0
- package/LICENSE +21 -0
- package/PLANNER.md +285 -0
- package/README.md +326 -0
- package/bin/templa.js +549 -0
- package/examples/_partials/about-body.html +8 -0
- package/examples/_partials/common-footer.html +6 -0
- package/examples/_partials/common-head.html +4 -0
- package/examples/_partials/common-header.html +15 -0
- package/examples/_partials/common-layout.html +5 -0
- package/examples/_partials/common-subhero.html +7 -0
- package/examples/_partials/index-cta.html +8 -0
- package/examples/_partials/index-features.html +13 -0
- package/examples/_partials/index-hero.html +9 -0
- package/examples/about.html +14 -0
- package/examples/css/style.css +16 -0
- package/examples/index.html +15 -0
- package/examples/serve.json +1 -0
- package/package.json +40 -0
- package/templa.js +266 -0
package/PLANNER.md
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# PLANNER.md — AI orchestrator prompt for templa project skeletons
|
|
2
|
+
|
|
3
|
+
You are about to design the **skeleton** of a templa project from a free-form site brief. Read this entire file before doing anything. Your single deliverable is a written plan in chat — the user will save it as `plan.md` at their project root if they accept it. **You will not create or modify any files in this step.**
|
|
4
|
+
|
|
5
|
+
If you have not already, also read `AGENTS.md` (same directory). It is the source of truth for templa's two-phase workflow, syntax, and conventions. This file complements `AGENTS.md`; it does not replace it.
|
|
6
|
+
|
|
7
|
+
## Your role
|
|
8
|
+
|
|
9
|
+
You are the orchestrator. Given a free-form site brief, your job is to produce a complete, executable plan — detailed enough that downstream sub-agents (or you, in a follow-up step) can implement the skeleton without asking another question.
|
|
10
|
+
|
|
11
|
+
The plan covers the serial part of building a templa site: design tokens, layout, chrome partials, and empty page shells. Parallel content fill happens after this plan is approved and the skeleton built.
|
|
12
|
+
|
|
13
|
+
## Hard rules
|
|
14
|
+
|
|
15
|
+
1. **Do not create or modify any file.** Output the plan only. The user reviews and approves before any implementation step.
|
|
16
|
+
2. **Do not run any build, `npm`, or shell command.** This is a planning step.
|
|
17
|
+
3. **Default to the canonical `common-*` set** — `common-head`, `common-layout`, `common-header`, `common-footer`, `common-subhero`. Add a new `common-*` template only when the brief unambiguously needs one (e.g. a recurring site-wide CTA banner). Each addition is a cost, not a default.
|
|
18
|
+
4. **Honor templa conventions.** `common-layout.html` is a body fragment (no `<html>` / `<body>`). Partials receive data via plain HTML attributes; `data-*` attributes are reserved as metadata and skipped. Section files ship with co-located `<style data-merge="css/style.css">` blocks and class names matching the filename.
|
|
19
|
+
5. **If something in the brief is ambiguous, list it under "Open questions" at the end of the plan.** Do not invent and do not guess silently.
|
|
20
|
+
|
|
21
|
+
## Project layout assumed by this plan
|
|
22
|
+
|
|
23
|
+
All paths in the plan are relative to the **project root**. The expected layout:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
project-root/
|
|
27
|
+
├── plan.md ← this plan, lives here
|
|
28
|
+
├── src/ ← templa source (build input)
|
|
29
|
+
│ ├── css/style.css
|
|
30
|
+
│ ├── _partials/
|
|
31
|
+
│ ├── assets/ ← images, copied to dist/ as-is
|
|
32
|
+
│ └── *.html ← entry pages
|
|
33
|
+
└── dist/ ← build output (gitignored, not authored)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The build command is therefore `npx templa-js build -i ./src -o ./dist` (the templa CLI defaults). `plan.md` stays at root and is never inside `src/` (so it never leaks into the build output).
|
|
37
|
+
|
|
38
|
+
## Input you will receive
|
|
39
|
+
|
|
40
|
+
A free-form site brief from the user. It may include any of:
|
|
41
|
+
|
|
42
|
+
- Project name and one-line pitch
|
|
43
|
+
- Target audience
|
|
44
|
+
- Brand vibe (warm / clean / playful / minimal / dark / editorial / …)
|
|
45
|
+
- Color or typography hints
|
|
46
|
+
- Pages they want
|
|
47
|
+
- Content sections per page
|
|
48
|
+
- Specific features (FAQ, contact form, image gallery, blog, pricing table, …)
|
|
49
|
+
|
|
50
|
+
It may be a single paragraph or several pages. Read it twice. Extract intent before writing.
|
|
51
|
+
|
|
52
|
+
## Process — walk these in order
|
|
53
|
+
|
|
54
|
+
### 1. Restate the brief in 3–5 lines
|
|
55
|
+
|
|
56
|
+
Compress the user's brief to its essentials. Confirms you understood and surfaces any gap.
|
|
57
|
+
|
|
58
|
+
### 2. Decide the page set
|
|
59
|
+
|
|
60
|
+
List every entry HTML file the site will produce. Default minimum: `index.html`. Add pages named or strongly implied by the brief (`about.html`, `contact.html`, `products.html`, `pricing.html`, …). Pages are bare filenames at the source root.
|
|
61
|
+
|
|
62
|
+
### 3. Wireframe + section list per page
|
|
63
|
+
|
|
64
|
+
For each page, produce **both**:
|
|
65
|
+
|
|
66
|
+
(a) A small ASCII wireframe showing the visual structure top-to-bottom, including grid arrangements (e.g. cards in 3 columns), and labelling each section. Keep it readable — boxes drawn with `┌─┬─┐` style, ~40–60 chars wide. Show the layout chrome (header, footer) so the sub-agent sees the full page envelope.
|
|
67
|
+
|
|
68
|
+
(b) A concise section list under the wireframe with one-line notes per section (filename, purpose, `common-*` template used if any, attributes it accepts).
|
|
69
|
+
|
|
70
|
+
Example for a home page:
|
|
71
|
+
|
|
72
|
+
```text
|
|
73
|
+
┌────────────────────────────────────────┐
|
|
74
|
+
│ HEADER (brand · nav) │
|
|
75
|
+
├────────────────────────────────────────┤
|
|
76
|
+
│ │
|
|
77
|
+
│ HERO │
|
|
78
|
+
│ [bg image] │
|
|
79
|
+
│ Tea, slowly │
|
|
80
|
+
│ [Browse our teas] │
|
|
81
|
+
│ │
|
|
82
|
+
├────────────────────────────────────────┤
|
|
83
|
+
│ A few favourites │
|
|
84
|
+
│ ┌──────┐ ┌──────┐ ┌──────┐ │
|
|
85
|
+
│ │ card │ │ card │ │ card │ │
|
|
86
|
+
│ └──────┘ └──────┘ └──────┘ │
|
|
87
|
+
├────────────────────────────────────────┤
|
|
88
|
+
│ Questions │
|
|
89
|
+
│ ▶ Q1 │
|
|
90
|
+
│ ▶ Q2 │
|
|
91
|
+
│ ▶ Q3 │
|
|
92
|
+
├────────────────────────────────────────┤
|
|
93
|
+
│ FOOTER │
|
|
94
|
+
└────────────────────────────────────────┘
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Sections (top → bottom):
|
|
98
|
+
- `_partials/index-hero.html` — landing hero, attrs image / heading / subheading / ctaLabel / ctaHref. Optional fields wrapped in `<template if="ctaLabel">` inside the section.
|
|
99
|
+
- `_partials/index-favourites.html` — section with `<h2>` + grid of 3 inline `<article>` cards (no separate template; markup lives in this section file)
|
|
100
|
+
- `_partials/index-faq.html` — inline Alpine accordion (lives only on this page)
|
|
101
|
+
|
|
102
|
+
The wireframe is for both you (the orchestrator confirming structure) and the content sub-agent who will implement the page.
|
|
103
|
+
|
|
104
|
+
### 4. Decide the section list per page
|
|
105
|
+
|
|
106
|
+
For each page in §2, list the section files that will exist as `_partials/[pagename]-[sectionname].html`. The wireframe in §3 gives you the order; this step turns that into a flat file list.
|
|
107
|
+
|
|
108
|
+
Use the shared `common-subhero.html` for inner-page headers — do not introduce per-page subhero variants. Anything else gets its own `[pagename]-[sectionname].html` file.
|
|
109
|
+
|
|
110
|
+
If a non-trivial section type would recur across two or more pages (testimonials, blog post snippets, team-member cards, pricing rows), surface it as an "open question" at the end of the plan rather than silently inventing a new `common-*`. The orchestrator decides whether to extend Phase 1 with a new `common-*` template or to inline the duplication across pages.
|
|
111
|
+
|
|
112
|
+
### 5. Decide design tokens
|
|
113
|
+
|
|
114
|
+
Pick concrete values; do not stay vague. Provide all of:
|
|
115
|
+
|
|
116
|
+
- **Color** — 4–6 CSS-variable tokens with real hex codes that suit the inferred vibe:
|
|
117
|
+
`--color-bg`, `--color-surface`, `--color-text`, `--color-muted`, `--color-accent`, `--color-accent-dark`, `--color-border`.
|
|
118
|
+
- **Typography** — one serif heading family + one sans body family. Use real Google Fonts names with the matching `family=` URL.
|
|
119
|
+
- **Spacing scale** — `--space-1` … `--space-7` (`.25rem` … ~`4rem`). The default scaffold ships these; tune values to fit the brief.
|
|
120
|
+
- **Radius / shadow** — 1–2 values each.
|
|
121
|
+
- **Container widths** — `--max-w` (text column, ~720px) and optionally `--max-w-wide` (~1100px).
|
|
122
|
+
|
|
123
|
+
If the brief was vague on aesthetics, infer from the project type and brand vibe and note explicitly that the values are an inference the user can override.
|
|
124
|
+
|
|
125
|
+
### 6. Decide the `common-*` templates
|
|
126
|
+
|
|
127
|
+
(All paths below are project-root relative.)
|
|
128
|
+
|
|
129
|
+
- **`src/_partials/common-head.html`** — `<head>` chrome: charset, viewport, theme-color, font preconnect/links, `./css/style.css` link. Title comes from the page's `<template src="…common-head.html" title="…">` attribute.
|
|
130
|
+
- **`src/_partials/common-layout.html`** — body fragment: invokes `common-header`, wraps a default `<slot>` in `<main>`, invokes `common-footer`.
|
|
131
|
+
- **`src/_partials/common-header.html`** — `<header>` element: brand + navigation anchors, each with `data-nav="<page-id>"` for active styling driven by `body[data-page="..."]` selectors in `src/css/style.css`.
|
|
132
|
+
- **`src/_partials/common-footer.html`** — `<footer>` element with copyright/social, identical on every page.
|
|
133
|
+
- **`src/_partials/common-subhero.html`** — `<section>` for inner-page headers, parameterized by `title` and optionally `bg`. Used by every page except the home page.
|
|
134
|
+
|
|
135
|
+
A project may add more `common-*` (rare). If you add any, justify it in §7's open questions.
|
|
136
|
+
|
|
137
|
+
### 7. Produce the file inventory
|
|
138
|
+
|
|
139
|
+
A complete listing of every file the skeleton will create, **paths relative to the project root**. Each line: path + a one-line role comment. Group by purpose. Example skeleton for a 3-page site (home, about, contact):
|
|
140
|
+
|
|
141
|
+
```text
|
|
142
|
+
src/css/style.css // tokens + base + chrome (locked in Phase 1)
|
|
143
|
+
src/_partials/common-head.html // <head> chrome (charset, viewport, fonts, css link)
|
|
144
|
+
src/_partials/common-layout.html // body skeleton: header / main slot / footer
|
|
145
|
+
src/_partials/common-header.html // <header>: brand + nav with data-nav
|
|
146
|
+
src/_partials/common-footer.html // <footer>: copyright + socials
|
|
147
|
+
src/_partials/common-subhero.html // shared inner-page subhero (title, optional bg)
|
|
148
|
+
src/_partials/index-hero.html // index — landing hero (sub-agent owned)
|
|
149
|
+
src/_partials/index-features.html // index — features list
|
|
150
|
+
src/_partials/index-cta.html // index — closing CTA
|
|
151
|
+
src/_partials/about-body.html // about — prose / company story
|
|
152
|
+
src/_partials/contact-form.html // contact — contact form
|
|
153
|
+
src/_partials/contact-map.html // contact — embedded map
|
|
154
|
+
src/index.html // page entry — sections: hero, features, cta
|
|
155
|
+
src/about.html // page entry — sections: subhero, body
|
|
156
|
+
src/contact.html // page entry — sections: subhero, form, map
|
|
157
|
+
src/assets/ // images / fonts / etc., copied to dist/ as-is
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Every `[pagename]-[sectionname].html` listed above is a unit of Phase 2 sub-agent work. Every `common-*.html` is locked after Phase 1.
|
|
161
|
+
|
|
162
|
+
### 8. Sketch the section-fill dispatch
|
|
163
|
+
|
|
164
|
+
One sub-agent per `[pagename]-[sectionname].html` file. For each, give a 2–3-line brief: what the section contains, which design tokens to reach for, any image/asset references, any Alpine.js bits to author inline.
|
|
165
|
+
|
|
166
|
+
The dispatch is genuinely parallel — none of the section files share file content, so sub-agents never collide.
|
|
167
|
+
|
|
168
|
+
Example:
|
|
169
|
+
|
|
170
|
+
- Sub-agent A → `src/_partials/index-hero.html` — large landing hero, headline + subhead + CTA button. Use `--space-7` top padding, `--font-display` for the headline.
|
|
171
|
+
- Sub-agent B → `src/_partials/index-features.html` — 3-column grid of feature cards (inline `<article>` markup; no shared partial). Use `--space-5` between rows.
|
|
172
|
+
- Sub-agent C → `src/_partials/index-cta.html` — closing CTA strip with `--color-accent` background.
|
|
173
|
+
- Sub-agent D → `src/_partials/about-body.html` — prose-style "company story" article with one image.
|
|
174
|
+
- Sub-agent E → `src/_partials/contact-form.html` — `<form action="contact.php">` with name/email/message fields, inline Alpine validation if the brief asks.
|
|
175
|
+
- Sub-agent F → `src/_partials/contact-map.html` — Google Maps `<iframe>` embed.
|
|
176
|
+
|
|
177
|
+
### 9. State the build gate command
|
|
178
|
+
|
|
179
|
+
End the plan with this line, verbatim:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
npx templa-js build -i ./src -o ./dist
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
This is the gate that separates skeleton from content fill. Content sub-agents must not be dispatched until this command succeeds against the placeholder skeleton.
|
|
186
|
+
|
|
187
|
+
## Output format — your reply takes this shape
|
|
188
|
+
|
|
189
|
+
```markdown
|
|
190
|
+
# Plan: <project name>
|
|
191
|
+
|
|
192
|
+
## 1. Brief, restated
|
|
193
|
+
…
|
|
194
|
+
|
|
195
|
+
## 2. Page set
|
|
196
|
+
- src/index.html — …
|
|
197
|
+
- src/about.html — …
|
|
198
|
+
|
|
199
|
+
## 3. Wireframes + sections per page
|
|
200
|
+
|
|
201
|
+
### index.html
|
|
202
|
+
```text
|
|
203
|
+
┌──────────────────────────────────────┐
|
|
204
|
+
│ HEADER │
|
|
205
|
+
├──────────────────────────────────────┤
|
|
206
|
+
│ HERO │
|
|
207
|
+
├──────────────────────────────────────┤
|
|
208
|
+
│ A few favourites │
|
|
209
|
+
│ ┌────┐ ┌────┐ ┌────┐ │
|
|
210
|
+
│ └────┘ └────┘ └────┘ │
|
|
211
|
+
├──────────────────────────────────────┤
|
|
212
|
+
│ Questions (FAQ accordion) │
|
|
213
|
+
├──────────────────────────────────────┤
|
|
214
|
+
│ FOOTER │
|
|
215
|
+
└──────────────────────────────────────┘
|
|
216
|
+
```
|
|
217
|
+
- `_partials/index-hero.html` — landing hero, attrs image/heading/subheading/ctaLabel/ctaHref
|
|
218
|
+
- `_partials/index-favourites.html` — 3-column grid of inline `<article>` cards
|
|
219
|
+
- faq — inline Alpine accordion
|
|
220
|
+
|
|
221
|
+
### about.html
|
|
222
|
+
```text
|
|
223
|
+
┌──────────────────────────────────────┐
|
|
224
|
+
│ HEADER │
|
|
225
|
+
├──────────────────────────────────────┤
|
|
226
|
+
│ SUB-HERO │
|
|
227
|
+
├──────────────────────────────────────┤
|
|
228
|
+
│ PROSE │
|
|
229
|
+
│ ¶ ¶ [img] ¶ ¶ │
|
|
230
|
+
├──────────────────────────────────────┤
|
|
231
|
+
│ FOOTER │
|
|
232
|
+
└──────────────────────────────────────┘
|
|
233
|
+
```
|
|
234
|
+
- `_partials/common-subhero.html` — shared subhero, attrs title/tagline
|
|
235
|
+
- prose — `<article class="prose">` with 4 paragraphs and 1 image
|
|
236
|
+
|
|
237
|
+
## 4. Section list per page
|
|
238
|
+
- index — sections: index-hero, index-features, index-cta
|
|
239
|
+
- about — sections: common-subhero (title="About"), about-body
|
|
240
|
+
|
|
241
|
+
## 5. Design tokens
|
|
242
|
+
```css
|
|
243
|
+
:root {
|
|
244
|
+
--color-bg: #faf8f3;
|
|
245
|
+
--color-text: #1f1f1c;
|
|
246
|
+
/* … */
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
Fonts: Inter (body), Lora (headings).
|
|
250
|
+
(Note: tokens inferred from the "warm, slow" vibe; override if you want.)
|
|
251
|
+
|
|
252
|
+
## 6. common-* templates
|
|
253
|
+
- src/_partials/common-head.html — charset, viewport, theme-color, fonts, ./css/style.css
|
|
254
|
+
- src/_partials/common-layout.html — header / <main><slot/></main> / footer
|
|
255
|
+
- src/_partials/common-header.html — brand + nav with data-nav
|
|
256
|
+
- src/_partials/common-footer.html — copyright + socials
|
|
257
|
+
- src/_partials/common-subhero.html — inner-page subhero (title attribute)
|
|
258
|
+
|
|
259
|
+
## 7. File inventory
|
|
260
|
+
```text
|
|
261
|
+
… full listing per the example above …
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## 8. Section-fill dispatch
|
|
265
|
+
- Sub-agent A → src/_partials/index-hero.html — landing hero with image + CTA
|
|
266
|
+
- Sub-agent B → src/_partials/index-features.html — 3-column features grid
|
|
267
|
+
- Sub-agent C → src/_partials/index-cta.html — closing CTA strip
|
|
268
|
+
- Sub-agent D → src/_partials/about-body.html — prose "about us" article
|
|
269
|
+
|
|
270
|
+
## 9. Build gate
|
|
271
|
+
```bash
|
|
272
|
+
npx templa-js build -i ./src -o ./dist
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Open questions
|
|
276
|
+
- (only if any; otherwise omit this section)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
End the plan with one line:
|
|
280
|
+
|
|
281
|
+
> Plan complete — awaiting user approval before any file is written.
|
|
282
|
+
|
|
283
|
+
## After you finish
|
|
284
|
+
|
|
285
|
+
Stop. Wait for the next instruction. Implementation (writing files, building, dispatching content sub-agents) is a separate step driven by a different prompt or an upcoming SKILL.
|
package/README.md
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# templa
|
|
2
|
+
|
|
3
|
+
> 🍤 A tiny HTML template loader. Pronounced **"tempura"**.
|
|
4
|
+
>
|
|
5
|
+
> Wraps your data in `<template src>` like batter wraps ingredients.
|
|
6
|
+
|
|
7
|
+
`templa` is a tiny dependency-free script that lets you split HTML into reusable partials, pass parameters, and use Handlebars-like syntax — all powered by the native `<template>` element.
|
|
8
|
+
|
|
9
|
+
It works in two modes:
|
|
10
|
+
- **Runtime** (`templa.js`) — partials are fetched and inlined in the browser
|
|
11
|
+
- **Build** (`npx templa-js build`) — partials are inlined ahead of time into static HTML
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<!-- index.html -->
|
|
15
|
+
<body>
|
|
16
|
+
<template src="_partials/header.html" title="Home" loggedIn="yes"></template>
|
|
17
|
+
<main>...</main>
|
|
18
|
+
<template src="_partials/footer.html"></template>
|
|
19
|
+
|
|
20
|
+
<script src="templa.js"></script>
|
|
21
|
+
<script type="module">await templa.start();</script>
|
|
22
|
+
</body>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<!-- _partials/header.html -->
|
|
27
|
+
<header>
|
|
28
|
+
<h1>{{title}}</h1>
|
|
29
|
+
<template if="loggedIn">
|
|
30
|
+
<a href="/logout">Logout</a>
|
|
31
|
+
</template>
|
|
32
|
+
<template unless="loggedIn">
|
|
33
|
+
<a href="/login">Login</a>
|
|
34
|
+
</template>
|
|
35
|
+
</header>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Why templa?
|
|
39
|
+
|
|
40
|
+
- **No build step** — drop in a `<script>` tag
|
|
41
|
+
- **No dependencies** — pure vanilla JavaScript
|
|
42
|
+
- **Standard HTML** — uses the native `<template>` element, not custom tags
|
|
43
|
+
- **Tiny** — ~240 lines of source, ~3.5KB gzipped
|
|
44
|
+
- **HTML-native** — `{{var}}` for values; `<template if>` / `<template unless>` for conditionals
|
|
45
|
+
- **Recursive** — partials inside partials just work
|
|
46
|
+
- **Resource-aware** — waits for `<link rel="stylesheet">` and `<script src>` inside partials before resolving
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
### Via `<script>` tag (CDN)
|
|
51
|
+
|
|
52
|
+
```html
|
|
53
|
+
<!-- minified (auto-generated by jsDelivr) -->
|
|
54
|
+
<script src="https://cdn.jsdelivr.net/npm/templa-js/templa.min.js"></script>
|
|
55
|
+
|
|
56
|
+
<!-- or unminified, for debugging -->
|
|
57
|
+
<script src="https://cdn.jsdelivr.net/npm/templa-js/templa.js"></script>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
> jsDelivr serves `templa.min.js` by minifying the source on the fly, so there is no separate minified file to maintain. Pin a version with `templa-js@0.0.1` if you want immutable URLs.
|
|
61
|
+
|
|
62
|
+
### Via npm
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm install templa-js
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Init
|
|
69
|
+
|
|
70
|
+
Bootstrap a new project in the current directory:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npx templa-js init # minimal src/ tree
|
|
74
|
+
npx templa-js init --ai # also write AGENTS.md and PLANNER.md
|
|
75
|
+
npx templa-js init --force # overwrite existing files
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The result is a buildable project: run `npx templa-js build` immediately afterwards and `dist/` will be produced. The `--ai` flag adds two project-root markdown files that brief AI coding agents on templa's conventions and a planning prompt for site skeletons.
|
|
79
|
+
|
|
80
|
+
## Usage
|
|
81
|
+
|
|
82
|
+
### Basic
|
|
83
|
+
|
|
84
|
+
Mark any place you want a partial with a `<template>` element:
|
|
85
|
+
|
|
86
|
+
```html
|
|
87
|
+
<template src="_partials/nav.html"></template>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Then call `templa.start()` to expand all of them:
|
|
91
|
+
|
|
92
|
+
```html
|
|
93
|
+
<script src="templa.js"></script>
|
|
94
|
+
<script type="module">
|
|
95
|
+
await templa.start();
|
|
96
|
+
// any post-init: Alpine.initTree(document.body), AOS.init(), Swiper, …
|
|
97
|
+
</script>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`templa.start()` returns a Promise that resolves once head + body partials have been expanded. The module-script form is required for top-level `await`. Anything written after the `await` runs once the DOM has been fully assembled — perfect for plugins like Alpine.js, AOS, Swiper, etc.
|
|
101
|
+
|
|
102
|
+
### Passing data
|
|
103
|
+
|
|
104
|
+
Each attribute on the calling `<template>` becomes a string-valued data key inside the partial.
|
|
105
|
+
|
|
106
|
+
```html
|
|
107
|
+
<template src="_partials/card.html" title="Hello" body="Welcome."></template>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Conditionals (`<template if="key">`) are existence-based, so any non-empty string is truthy — `featured="yes"` is enough to enable a block.
|
|
111
|
+
|
|
112
|
+
Reserved attributes — these are not collected as data: `src` (the partial path), `slot` (slot filler name), `if` / `unless` (conditional markers). Any `data-*` attribute is also skipped, by HTML metadata convention.
|
|
113
|
+
|
|
114
|
+
### Template syntax
|
|
115
|
+
|
|
116
|
+
| Syntax | Effect |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `{{key}}` | HTML-escaped variable |
|
|
119
|
+
| `{{{key}}}` | Raw variable (no escape) — use only for trusted HTML |
|
|
120
|
+
| `<template if="key">…</template>` | Block kept when `data[key]` is truthy |
|
|
121
|
+
| `<template unless="key">…</template>` | Block kept when `data[key]` is falsy |
|
|
122
|
+
|
|
123
|
+
Conditionals can be nested. Variables fall through unchanged when the key is missing from `data`.
|
|
124
|
+
|
|
125
|
+
### Layouts and slots
|
|
126
|
+
|
|
127
|
+
A partial can declare insertion points with `<slot>`. Pages fill those slots by writing content inside the calling `<template src>`.
|
|
128
|
+
|
|
129
|
+
Keep layouts as **body fragments** (no `<!DOCTYPE>` / `<html>` / `<head>` / `<body>` wrapper) so they work in both runtime and build modes. Each page provides its own document skeleton and embeds the layout where the body content goes.
|
|
130
|
+
|
|
131
|
+
```html
|
|
132
|
+
<!-- _layouts/main.html (body fragment) -->
|
|
133
|
+
<header><slot name="nav">Default Nav</slot></header>
|
|
134
|
+
<main><slot></slot></main>
|
|
135
|
+
<footer><slot name="footer">© 2026</slot></footer>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```html
|
|
139
|
+
<!-- page.html -->
|
|
140
|
+
<!DOCTYPE html>
|
|
141
|
+
<html>
|
|
142
|
+
<head>
|
|
143
|
+
<title>Home</title>
|
|
144
|
+
</head>
|
|
145
|
+
<body>
|
|
146
|
+
<template src="_layouts/main.html">
|
|
147
|
+
<template slot="nav">
|
|
148
|
+
<a href="/">Home</a>
|
|
149
|
+
<a href="/about">About</a>
|
|
150
|
+
</template>
|
|
151
|
+
|
|
152
|
+
<h1>Welcome</h1>
|
|
153
|
+
<p>Anything outside <template slot> goes into the default slot.</p>
|
|
154
|
+
|
|
155
|
+
<!-- footer slot is omitted, so its fallback renders -->
|
|
156
|
+
</template>
|
|
157
|
+
|
|
158
|
+
<script src="https://cdn.jsdelivr.net/npm/templa-js/templa.min.js"></script>
|
|
159
|
+
<script type="module">await templa.start();</script>
|
|
160
|
+
</body>
|
|
161
|
+
</html>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Rules:
|
|
165
|
+
|
|
166
|
+
- `<slot>` (no name) receives every node from the calling `<template>` that is not wrapped in `<template slot="...">`.
|
|
167
|
+
- `<slot name="X">` receives the content of the matching `<template slot="X">` filler.
|
|
168
|
+
- A slot's own children are the **fallback** — they render when no filler is supplied.
|
|
169
|
+
- Slot fillers may themselves contain `<template src="...">` partials; they are expanded recursively in the call site's directory context.
|
|
170
|
+
|
|
171
|
+
This pattern works identically with the build CLI — `npx templa-js build` inlines the layout into the output and you can drop the script tags.
|
|
172
|
+
|
|
173
|
+
### Nested partials
|
|
174
|
+
|
|
175
|
+
Partials can include other partials. `templa` keeps expanding until no `<template src>` remains.
|
|
176
|
+
|
|
177
|
+
### Loading order
|
|
178
|
+
|
|
179
|
+
`templa.start()` kicks off head expansion **synchronously** so its fetches overlap with the rest of HTML parsing — critical when partials in `<head>` include `<link rel="stylesheet">`. Body expansion waits for `DOMContentLoaded`. The returned Promise resolves once both phases complete.
|
|
180
|
+
|
|
181
|
+
### Relative paths in nested partials
|
|
182
|
+
|
|
183
|
+
`<template src>` inside a partial is resolved relative to **that partial's URL**, not the page. So `_partials/layout.html` can reference `<template src="./header.html">` and it will resolve to `_partials/header.html` correctly.
|
|
184
|
+
|
|
185
|
+
Other resources (`<img src>`, `<link href>`, `<script src>`) still resolve against the page URL — use absolute or root-relative paths for those.
|
|
186
|
+
|
|
187
|
+
### Co-located styles
|
|
188
|
+
|
|
189
|
+
A partial can carry its own CSS in a `<style>` block tagged with `data-merge="<file>"`. The build CLI extracts it once per partial and appends it to the named output stylesheet — even if the partial is used 100 times, its rules are written exactly once.
|
|
190
|
+
|
|
191
|
+
```html
|
|
192
|
+
<!-- _partials/card.html -->
|
|
193
|
+
<style data-merge="style.css">
|
|
194
|
+
.card { background: #fff; border: 1px solid #ddd; padding: 1rem; }
|
|
195
|
+
</style>
|
|
196
|
+
<article class="card">
|
|
197
|
+
<h3>{{title}}</h3>
|
|
198
|
+
</article>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
At runtime, the same dedupe applies: the `<style>` block stays in the DOM for the first expansion of a given partial and is stripped on subsequent ones. A `<style>` without `data-merge` is treated as plain inline CSS (existing behaviour).
|
|
202
|
+
|
|
203
|
+
### Caching and recursion
|
|
204
|
+
|
|
205
|
+
- Identical `<template src>` URLs are fetched once per page (in-memory cache).
|
|
206
|
+
- A safety guard stops expansion after 50 passes to prevent infinite loops from circular includes.
|
|
207
|
+
|
|
208
|
+
## API
|
|
209
|
+
|
|
210
|
+
### `templa.start()`
|
|
211
|
+
|
|
212
|
+
Loads all `<template src>` elements (head first, then body). Returns a Promise that resolves once everything is mounted. Use with `await` from a `<script type="module">`.
|
|
213
|
+
|
|
214
|
+
### `templa.run(selector?)`
|
|
215
|
+
|
|
216
|
+
Lower-level: runs a single expansion pass against `selector` (default: `'template[src]'`). Returns a Promise. Useful if you load partials dynamically.
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
await templa.run('#my-region template[src]');
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Content Security Policy
|
|
223
|
+
|
|
224
|
+
The runtime never calls `eval` or `new Function`. There is no `'unsafe-eval'` requirement; a plain `script-src 'self'` works.
|
|
225
|
+
|
|
226
|
+
For pages that don't need a runtime at all, build with `npx templa-js build` — the output is plain HTML with no template syntax left.
|
|
227
|
+
|
|
228
|
+
## Caveats
|
|
229
|
+
|
|
230
|
+
- `{{key}}` HTML-escapes its value. If you need to inject HTML, use `{{{key}}}` and make sure the value is trusted.
|
|
231
|
+
- Original `<template src>` elements are removed from the DOM after expansion.
|
|
232
|
+
- This project is in early development (0.0.x). API may change.
|
|
233
|
+
|
|
234
|
+
## Build (CLI)
|
|
235
|
+
|
|
236
|
+
For static deployment, expand all `<template src>` ahead of time:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
npx templa-js build -i ./src -o ./dist
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The CLI reads every `.html` file under the source directory, recursively inlines its partials with the same syntax as the runtime, and writes the result to the output directory.
|
|
243
|
+
|
|
244
|
+
### File convention
|
|
245
|
+
|
|
246
|
+
Files and directories whose names start with `_` are treated as partials and are **not** copied to the output:
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
src/
|
|
250
|
+
├── index.html ← entry, written to dist/
|
|
251
|
+
├── about.html ← entry, written to dist/
|
|
252
|
+
└── _partials/ ← skipped (partials only)
|
|
253
|
+
├── header.html
|
|
254
|
+
└── footer.html
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Reference partials with a relative path:
|
|
258
|
+
|
|
259
|
+
```html
|
|
260
|
+
<template src="_partials/header.html" title="Home"></template>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Options
|
|
264
|
+
|
|
265
|
+
| Flag | Default | Description |
|
|
266
|
+
|---|---|---|
|
|
267
|
+
| `-i <dir>` | `./src` | Source directory |
|
|
268
|
+
| `-o <dir>` | `./dist` | Output directory (cleared before each build) |
|
|
269
|
+
| `--version` | | Print version |
|
|
270
|
+
| `--help` | | Print usage |
|
|
271
|
+
|
|
272
|
+
### Build vs runtime
|
|
273
|
+
|
|
274
|
+
Both modes share the same template syntax, so a partial works in either:
|
|
275
|
+
|
|
276
|
+
| | Runtime (`templa.js`) | Build (`npx templa-js`) |
|
|
277
|
+
|---|---|---|
|
|
278
|
+
| When partials are inlined | At page load, in the browser | Once, ahead of time |
|
|
279
|
+
| Dependencies | none | none |
|
|
280
|
+
| Output | dynamic DOM | static HTML files |
|
|
281
|
+
| Use case | quick prototypes, dev | production deploy, SEO, static hosting |
|
|
282
|
+
|
|
283
|
+
You can also use both: ship the static HTML for first paint and keep `templa.js` for any partials you want to load dynamically later.
|
|
284
|
+
|
|
285
|
+
## Recommended companion: Alpine.js
|
|
286
|
+
|
|
287
|
+
`templa` handles **composition** (partials, layouts, variables, conditionals at build or load time). For **interactive behaviour** (modals, dropdowns, counters, form state), pair it with [Alpine.js](https://alpinejs.dev/) — also HTML-first, no build step, ~15 KB.
|
|
288
|
+
|
|
289
|
+
```html
|
|
290
|
+
<!-- somewhere in your <head> -->
|
|
291
|
+
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js" defer></script>
|
|
292
|
+
|
|
293
|
+
<!-- in any partial -->
|
|
294
|
+
<section x-data="{ open: false }">
|
|
295
|
+
<button @click="open = !open">Toggle</button>
|
|
296
|
+
<div x-show="open">Hidden until clicked.</div>
|
|
297
|
+
</section>
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Decision rule: templa for everything that can be resolved before the user clicks; Alpine for everything that depends on user interaction.
|
|
301
|
+
|
|
302
|
+
## Working with AI agents
|
|
303
|
+
|
|
304
|
+
If you use Claude Code, Cursor, Aider, Copilot, or similar AI tools, two files do most of the work:
|
|
305
|
+
|
|
306
|
+
- [`AGENTS.md`](./AGENTS.md) — file conventions, two-phase workflow, syntax, common pitfalls. Drop it into a templa project root and the agent reads it as the source of truth.
|
|
307
|
+
- [`PLANNER.md`](./PLANNER.md) — an instruction prompt that turns a free-form site brief into a concrete `plan.md` (page set, wireframes, section list, design tokens, file inventory) before any file is written.
|
|
308
|
+
|
|
309
|
+
## Philosophy
|
|
310
|
+
|
|
311
|
+
templa is not trying to replace HTML. It exists because HTML does not yet have a native way to include partials, compose layouts, and pass small pieces of data between templates.
|
|
312
|
+
|
|
313
|
+
**The biggest competitor is native HTML. That is also the goal.** If one day HTML supports this natively, templa has done its job. Until then, templa is a tiny bridge.
|
|
314
|
+
|
|
315
|
+
Concrete consequences of that stance:
|
|
316
|
+
|
|
317
|
+
- Attribute names follow the platform: `<template src>` mirrors `<img src>` / `<script src>` / `<iframe src>`. We deliberately don't use `data-src`. The bare `src` lets editors and IDEs treat it like a real file reference (path completion, jump-to-file, refactor-rename).
|
|
318
|
+
- Conditionals are written as `<template if="key">…</template>` and `<template unless="key">…</template>` — no `{{#if}}` Mustache block, no expressions, no helpers. They are existence-based only.
|
|
319
|
+
- Co-located styles use `data-merge="style.css"` — a `data-*` attribute, because that is HTML's documented hook for component-private metadata.
|
|
320
|
+
- Anything beyond block-level conditionals (conditional attributes, dynamic class lists, loops) is out of scope for the core and lives in plugins.
|
|
321
|
+
|
|
322
|
+
The core stays small enough to read in one sitting. Everything else is a plugin.
|
|
323
|
+
|
|
324
|
+
## License
|
|
325
|
+
|
|
326
|
+
[MIT](./LICENSE)
|