https://github.com/nonk123/sanity
The only sane static site generator in existence
https://github.com/nonk123/sanity
jinja2 lua rust sane sanity sass scss static-site-generator
Last synced: about 1 month ago
JSON representation
The only sane static site generator in existence
- Host: GitHub
- URL: https://github.com/nonk123/sanity
- Owner: nonk123
- License: unlicense
- Created: 2025-04-01T17:26:00.000Z (about 1 year ago)
- Default Branch: master
- Last Pushed: 2026-05-24T17:30:24.000Z (about 1 month ago)
- Last Synced: 2026-05-24T19:24:00.735Z (about 1 month ago)
- Topics: jinja2, lua, rust, sane, sanity, sass, scss, static-site-generator
- Language: Rust
- Homepage:
- Size: 437 KB
- Stars: 2
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# sanity
> [!TIP]
> You can now install the [Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=nonk123.vscode-sanity-liveserver) for a more pleasant experience! See other supported IDEs in [the integrations section](#integrations)!
The only sane static site generator in existence. Refer to the [examples directory](sanity/examples) for a quickstart.
Here's what it does for you:
- Process [SCSS](https://sass-lang.com/documentation/syntax) to CSS using [grass](https://github.com/connorskees/grass).
- Render [Jinja2](https://jinja.palletsprojects.com/en/stable/templates) templates with [minijinja](https://github.com/mitsuhiko/minijinja).
- Run [Lua scripts](#basic-scripting) with [mlua](https://github.com/mlua-rs/mlua), using [LuaJIT](https://luajit.org/) for the backend. Useful for rendering a template with different sets of inputs.
- Minify HTML/JS/CSS resulting in the build process.
- Leave other files alone and copy them as-is.
Directories are walked recursively depth-first, with files processed and directories read in an alphanumeric order. Files are processed as soon as they are encountered, with the exception being Jinja2 templates, the rendering of which happens in a separate step that runs _after_ everything else has been processed.
Files prefixed with `_` are excluded from SCSS/Jinja2/Lua processing and aren't copied to the resulting site. This is useful for:
- Base templates that are meant to be inherited rather than rendered on their own.
- Templates rendered programmatically, such as blog articles, product pages, project descriptions.
- Reused template partials residing as separate files.
- SCSS `@use` modules.
- Lua `require()` imports.
Here are some of the sites powered by `sanity`:
- [nonk.dev](https://nonk.dev) ([repo](https://github.com/nonk123/nonk.dev))
- [schwung.us](https://schwung.us) ([repo](https://github.com/Schwungus/schwung.us))
- [cantsleep.cc](https://cantsleep.cc) ([repo](https://github.com/LocalInsomniac/LocalInsomniac.github.io))
## Integrations
You can use `sanity` without ever touching the command-line by installing one of our IDE extensions:
- [for Visual Studio Code](https://github.com/nonk123/vscode-sanity).
- [for GNU/Emacs](https://github.com/nonk123/sanity-emacs).
## Basic Usage
> [!NOTE]
> Make sure to add the `dist` folder to your `.gitignore`. It doesn't (usually) make sense to version auto-generated files.
Place your Jinja2 templates, SCSS sheets, and [Lua scripts](#basic-scripting) inside the `www` folder. Run sanity from [the command line](#command-line-usage) or through [one of the integration packages](#integrations). You should get a fully processed site inside the `dist` folder right next to `www`.
You can either upload the contents of `dist` to a free website-hosting such as [Neocities](https://neocities.org) or [GitHub Pages](https://pages.github.com), or you can serve them locally using the built-in `sanity` live-server before pushing the site to production. The details of the latter scenario depend on the integration you're using. If you're unsure, just use [VSCode](https://code.visualstudio.com) and [our integration](https://github.com/nonk123/vscode-sanity): this combo runs the live-server automatically once you open your project folder.
## Command-Line Usage
Download a binary from [available releases](https://github.com/nonk123/sanity/releases#latest). Run without arguments for a one-off build. Run with `server` to serve your site using the built-in development server; it rebuilds the site whenever the contents of `www` change. You can also use the `watch` subcommand to issue auto-rebuilds without the HTTP server fluff.
Discover more options by running `sanity` with `--help`.
## Basic Scripting
There isn't much to scripting sanity besides the custom `render` function. It lets you send a template to the render queue programmatically rather than forcing you to use one whole file per page. Take a look at this static blog example:
```lua
local blog = {
["nice-day"] = {
date = "today",
contents = "I had a nice day today."
}
};
for id, post in pairs(blog) do
render("_article.html", "blog/" .. id .. ".html", {
id = id,
date = post.date,
contents = post.contents,
});
end
```
`render` accepts:
- A template name (path relative to `www`, without the `.j2` extension) to add to the _render queue_.
- Output file path relative to `dist`.
- A dictionary to supply to the renderer.
Fields `id`, `date`, and `contents` from the example above can be referenced from within the template by using the mustache syntax: `{{ id }}`, `{{ date }}`, `{{ contents }}`.
> [!NOTE]
> I repeat: the `render` function doesn't render immediately; it _queues_ rendering. Lua scripts are processed _before_ the rendering happens.
## Advanced Scripting
### JSON Parsing
You can read JSON files inside `www` by using the `json` function:
```lua
local blog = json("blog/db.json");
-- the rest is the same as the example above...
```
### Reading Text Files
`read` can be used to store a text file's contents in a string:
```lua
local id = "nice-day";
local contents = read("blog/" .. id .. ".md");
-- simile
```
### Adding Global Variables
`inject` can be used to add/modify globals available inside _all_ templates:
```lua
inject("last_updated", os.date("%Y-%m-%d"));
```
## Misc. usage
### Schema Validation
Let's say you're loading a list of blog articles to render from a really long JSON file, and you want all articles to have a short description field. To ensure each article has such a `description` field by spitting out an error otherwise, you can use the `required` filter in your templates:
```html
{% for article in articles %}
{{ article.description | required("All articles need a description") }}
{% endfor }
```
This won't help with figuring out which article is missing a description, but at least you'll be sure all of them have it once you find the culprit.
### Exclude Analytics from Dev Builds
You can check for the `__prod` boolean in your templates to exclude analytics & trackers from dev builds:
```html
{% if __prod %}
{% endif }
```
### LuaLS Definitions
Use the `lua-lib` subcommand to add a LuaLS definitions file to your project folder. This should hide the 999 warnings about undefined functions you've been getting. Make sure to point your IDE to this file, e.g. in VSCode `settings.json`:
```json
{
"Lua.workspace.library": ["_sanity.lua"]
}
```