amund / kiss-php
Kiss (Keep It Simply Static) is a php static site generator.
Requires
- league/commonmark: ^2.8
- symfony/filesystem: ^7
- symfony/var-dumper: ^7
- symfony/yaml: ^7
- twig/twig: ^3
Requires (Dev)
README
A PHP static site generator. Uses Twig for templating, supports YAML/JSON/PHP/XML/INI data sources, dynamic page parameters, static file sync, and a watch mode with partial rebuilds.
Prerequisites
- PHP 8.2+ (CLI)
inotifywait(Linux/WSL) oufswatch(macOS) pour le mode watch
Getting started
composer global require amund/kiss-php
kiss init my-site
cd my-site
kiss build
CLI
| Command | Description |
|---|---|
kiss init [dir] |
Creates a new site in the folder (or the current folder) |
kiss build |
Build the entire website |
kiss watch |
Build and then monitor the files (using inotifywait or fswatch) |
kiss reset [all|dist|cache] |
Remove web/ and/or tmp/ |
kiss route [list|name] |
List the routes or rebuild a specific route |
kiss copy [path] |
Sync a file from copy/ to web/ |
kiss test |
Validate the configuration (warmup without build) |
Default project structure
kiss.yml → Site configuration
├── copy/ → Static files (copied as-is to web/)
├── data/ → Global data (YAML/JSON/PHP/XML/INI/MD)
├── route/ → Route definitions (YAML/JSON/PHP/XML/INI)
├── template/ → Twig templates
└── web/ → Generated site (dist)
Configuration
File kiss.yml (also supports .yaml, .php, .json, .xml, .ini).
debug: true path: copy: copy data: data route: route template: template dist: web cache: system
Environment variables (take precedence):
KISS_DEBUG=trueKISS_VERBOSE=true
Data sources
Files in data/ are loaded automatically and available in Twig via {{ global.* }}.
Example — data/site.yml:
name: My Site tagline: Great static site
In a Twig template:
<h1>{{ global.site.name }}</h1> <p>{{ global.site.tagline }}</p>
Markdown files
Markdown files (.md) in data/ support frontmatter delimited by ---:
--- title: My Article date: 2024-01-01 --- Content **markdown** here.
The frontmatter fields are available directly, and the body is available as content. Combine with the |markdown filter in your templates:
<h1>{{ title }}</h1> <div>{{ content|markdown|raw }}</div>
Routes
Single page
route/index.yml:
path: /index.html template: page.twig data: title: Home
Pages with settings (items)
route/blog.yml:
path: /{slug}.html template: post.twig data: blog_title: "My Blog" items: - slug: hello-world title: Hello World - slug: second-article title: Second article
Each item generates a page. data contains the metadata, which is merged into each child page.
External reference ($ref)
path: /{slug}.html template: post.twig items: $ref: data/articles.yml
Archive page + list
path: /blog template: blog_list.twig data: title: "Blog" items: $ref: data/posts.yml
Access in the archive template :
<h1>{{ title }}</h1> {% for post in items %} <a href="{{ path('blog', {slug: post.slug}) }}">{{ post.title }}</a> {% endfor %}
URL Generation (path())
{{ path('about') }} → /about
{{ path('blog', {slug: 'hello'}) }} → /hello.html
{{ path('blog') }} → /blog (page 1)
{{ path('blog', {page: 2}) }} → /blog/page/2
Accessing data from another route (route())
{% set blog = route('blog') %}
<h1>{{ blog.title }}</h1>
{% for post in blog.items %}
...
{% endfor %}
Pagination
route/blog.yml:
path: /blog template: blog_list.twig paginate: 10 data: title: "Archives" items: $ref: data/posts.yml
Generates blog/index.html (page 1) and blog/page/{n}/index.html (subsequent pages).
{% for post in items %}
...
{% endfor %}
{% if prevPage %}
<a href="{{ path('blog', {page: prevPage}) }}">← Previous</a>
{% endif %}
{% if nextPage %}
<a href="{{ path('blog', {page: nextPage}) }}">Next →</a>
{% endif %}
Validation
{param}orpaginate→itemsis requireddatais reserved for metadata- The
$refshould be set toitems, notdata
How it works
- Route manifest:
tmp/kiss/route-manifest.phptracks which files each route generated; orphaned files are cleaned on rebuild. - Template deps:
tmp/kiss/template-deps.phptraces dependencies between templates (extends/include/embed) for partial rebuilds. - DataTree: caches remote and local data sources as serialized PHP files.
- Watch:
inotifywaitdetects changes and recompiles only the impacted routes.
License
MIT