Expand description
§rs-web - Static Site Generator
A fast, opinionated static site generator built in Rust with support for:
- Markdown processing with syntax highlighting, external link handling
- Content encryption via Lua (
rs.cryptmodule) - Link graph with backlinks and visualization (Obsidian-style)
- RSS feed generation with section filtering
- Parallel processing for fast builds
- Live reload with automatic browser refresh during watch mode
- Asset minification for CSS, JS (with dead code elimination), and HTML
§Quick Start
# Build the site
rs-web build
# Build to custom output directory
rs-web build --output public
# Watch for changes and rebuild incrementally with live reload
rs-web build --watch
# Watch mode with custom port
rs-web build --watch --port 8080
# Enable debug logging
rs-web --debug build
# Set specific log level (trace, debug, info, warning, error)
rs-web --log-level trace build
# Or use environment variable
RS_WEB_LOG_LEVEL=debug rs-web build§Configuration
Configure via config.lua:
return {
site = {
title = "My Site",
description = "Site description",
base_url = "https://example.com",
author = "Your Name",
},
build = {
output_dir = "dist",
},
-- Generate pages via Lua
pages = function()
return {
{ path = "/", template = "home.html", title = "Home" },
{ path = "/about/", template = "page.html", title = "About", minify = true },
}
end,
-- Build hooks
hooks = {
before_build = function() print("Starting...") end,
after_build = function() print("Done!") end,
},
}§Configuration Sections
site- Required: title, description, base_url, authorseo- twitter_handle, default_og_imagebuild- output_dirpaths- templates
§Lua Sandbox
By default, file operations are sandboxed to the project directory. To disable (use with caution):
return {
lua = { sandbox = false },
site = { ... },
}§Lua API Functions
File Operations:
read_file(path)- Read file contentswrite_file(path, content)- Write content to filecopy_file(src, dest)- Copy file (binary-safe)file_exists(path)- Check if file existslist_files(path, pattern?)- List files matching patternlist_dirs(path)- List subdirectoriesload_json(path)- Load and parse JSON fileload_yaml(path)- Load and parse YAML fileload_toml(path)- Load and parse TOML fileread_frontmatter(path)- Extract frontmatter and content from markdown
Content Processing:
html_to_text(html)- Convert HTML to plain text
Date & Time (rs.date):
now()- Get current Unix timestampfrom_timestamp(ts)- Convert Unix timestamp to DateTime tableto_timestamp(date)- Convert date to Unix timestampformat(date, format)- Format date using strftimeparse(str, format?)- Parse date string (auto-detects or custom format)rss_format(date)- Format for RSS (RFC 2822)iso_format(date)- Format as ISO 8601add(date, delta)- Add time to datediff(date1, date2)- Difference in seconds
Markdown (rs.markdown):
render(content, opts?)- Render markdown to HTML with optional pluginsplugins(...)- Combine/flatten pluginsplugins.default(opts?)- Get default plugins (lazy_images, heading_anchors, external_links)plugins.lazy_images(opts?)- Plugin: add loading=“lazy” decoding=“async” to imagesplugins.heading_anchors(opts?)- Plugin: add id=“slug” to headingsplugins.external_links(opts?)- Plugin: add target=“_blank” rel=“noopener” to external links
Image Processing:
image_dimensions(path)- Get image width and heightimage_resize(input, output, options)- Resize imageimage_convert(input, output, options?)- Convert image formatimage_optimize(input, output, options?)- Optimize/compress image
Fonts (rs.fonts):
download_google_font(family, options)- Download Google Font with optional CSS minification (async)
JS Module (rs.js):
concat(paths, output, options?)- Concatenate JS files with optional minification (async)bundle(entry, output, options?)- Bundle JS with imports via Rolldown (async)bundle_many(entries, output_dir, options?)- Bundle multiple JS entries to separate files (async)
CSS Module (rs.css):
concat(paths, output, options?)- Concatenate CSS files with optional minification (async)bundle(paths, output, options?)- Bundle CSS with @import resolution via LightningCSS (async)bundle_many(paths, output_dir, options?)- Bundle multiple CSS entries to separate files (async)purge(css_path, options?)- Remove unused CSS rules based on HTML/JS output (async, call in after_build)critical(html, css_path, options?)- Extract critical CSS for a specific HTML page (async)inline_critical(html_path, css_path, options?)- Inline critical CSS into HTML with async loading (async)
Asset Hashing (rs.assets):
hash(content, length?)- Compute SHA256 hash of content (async)hash_sync(content, length?)- Compute hash synchronouslywrite_hashed(content, path, options?)- Write file with hashed filename (async)register(original, hashed)- Register asset path mappingget_path(path)- Get hashed path for originalmanifest()- Get all path mappingsclear()- Clear the manifest
PWA (rs.pwa):
manifest(options)- Generate web app manifest.json (async)service_worker(options)- Generate service worker sw.js (async)
SEO (rs.seo):
sitemap(options)- Generate XML sitemap (async)robots(options)- Generate robots.txt (async)
Text Processing:
slugify(text)- Convert text to URL-friendly slugword_count(text)- Count words in textreading_time(text, wpm?)- Calculate reading time in minutestruncate(text, len, suffix?)- Truncate text with optional suffixstrip_tags(html)- Remove HTML tagshash(content)- Hash content (xxHash64)hash_file(path)- Hash file contentsurl_encode(str)- URL encode a stringurl_decode(str)- URL decode a string
Path Utilities:
join_path(...)- Join path segmentsbasename(path)- Get file name from pathdirname(path)- Get directory from pathextension(path)- Get file extension
Collections:
filter(items, fn)- Filter items where fn returns truesort(items, fn)- Sort items using comparatormap(items, fn)- Transform each itemfind(items, fn)- Find first item where fn returns truegroup_by(items, key_fn)- Group items by keyunique(items)- Remove duplicatesreverse(items)- Reverse array ordertake(items, n)- Take first n itemsskip(items, n)- Skip first n itemskeys(table)- Get all keys from a tablevalues(table)- Get all values from a table
Environment:
env(name, default?)- Get environment variable with optional defaultprint(...)- Log output to build loggit_info(path?)- Get git info (hash, branch, author, timestamp, dirty)
Parallel (rs.parallel): Rayon-backed true parallel operations
load_json(paths)/load_yaml(paths)- Load multiple files in parallelread_files(paths)/read_frontmatter(paths)- Read multiple filescreate_dirs(paths)/copy_files(sources, dests)- Parallel file operationsimage_convert(sources, dests, opts?)- Convert images in parallelmap(items, fn, ctx?)/filter(items, fn, ctx?)- Parallel map/filter with explicit contextmap_seq(items, fn)/filter_seq(items, fn)- Sequential fallbacks for non-serializable items
Async I/O (rs.async): All return handles, await with rs.async.await(task) or rs.async.await_all(tasks)
fetch(url, opts?)/fetch_bytes(url, opts?)- HTTP fetch (text/binary)fetch_sync(url, opts?)- Blocking fetch (returns response directly)write_file(path, content)/write(path, bytes)- Write text/binarycopy_file(src, dst)/create_dir(path)- File/dir operationsexists(path)/read_file(path)- Check existence / read file
Encryption (rs.crypt):
encrypt(content, password?)- Encrypt content (AES-256-GCM)decrypt(data, password?)- Decrypt contentencrypt_html(content, options?)- Generate encrypted HTML block for browser decryption
All file operations respect the sandbox setting and are tracked for incremental builds. Encryption uses SITE_PASSWORD environment variable if password is not provided.
§Frontmatter
Post frontmatter options (YAML or TOML):
---
title: "Post Title" # Required
description: "Description" # Optional
date: 2024-01-15 # Optional (YAML date or string)
tags: ["tag1", "tag2"] # Optional
draft: false # Optional (default: false, excluded from build)
image: "/static/post.png" # Optional: OG image
template: "custom.html" # Optional: Override template
slug: "custom-slug" # Optional: Override URL slug
permalink: "/custom/url/" # Optional: Full URL override
---§Encryption
Encryption is handled via the rs.crypt module in Lua. Use SITE_PASSWORD
environment variable or pass password explicitly:
-- Encrypt content
local encrypted = rs.crypt.encrypt("secret content")
-- Returns: { ciphertext, salt, nonce }
-- Generate encrypted HTML block for browser decryption
local html = rs.crypt.encrypt_html("secret content", {
slug = "post-slug",
block_id = "secret-1",
})
-- Decrypt content
local plaintext = rs.crypt.decrypt(encrypted)§Template Variables
§Home Template (home.html)
site- Site config (title, description, base_url, author)page- Page info (title, description, url, image)sections- All sections with posts (sections.blog.posts)content- Rendered markdown content
§Post Template (post.html)
site- Site configpost- Post info (title, url, date, tags, reading_time, etc.)page- Page info for head.html compatibilitycontent- Rendered markdown contentbacklinks- Posts linking to this post (url, title, section)graph- Local graph data (nodes, edges) for visualization
§Graph Template (graph.html)
site- Site configpage- Page infograph- Full graph data (nodes, edges)
§Modules
config- Configuration loading and structureslua- Lua API includingrs.markdownfor markdown processingtemplates- Tera template renderingencryption- AES-256-GCM encryption utilities (used byrs.cryptLua module)build- Main build orchestrator
Modules§
- assets
- Asset processing (CSS bundling, image optimization)
- build
- Build orchestrator for static site generation
- config
- Configuration loader for rs-web
- data
- Tera template functions for data loading and content rendering
- encryption
- AES-256-GCM encryption with Argon2id key derivation
- git
- Git integration for commit info and file history
- lua
- Lua API for rs-web Usage in Lua:
- server
- Development server with WebSocket live reload
- templates
- Tera template engine wrapper with page rendering
- text
- Plain text generation from HTML content
- tracker
- Build dependency tracker for incremental builds
- watch
- File watcher for incremental rebuilds
Macros§
- rs_
print - Print output at info level (always shown regardless of log level)