a quick and dirty static site generator
|
|
2 settimane fa | |
|---|---|---|
| .claude | 2 settimane fa | |
| src | 2 settimane fa | |
| .gitignore | 1 anno fa | |
| .nvmrc | 1 anno fa | |
| CONTEXT.md | 2 settimane fa | |
| README.md | 2 settimane fa | |
| eslint.config.js | 7 mesi fa | |
| package-lock.json | 2 settimane fa | |
| package.json | 2 settimane fa |
a quick and dirty static site generator with intelligent caching.
Run this script from a directory containing markdown, templates and styles and a dist/ directory will be created
containing the rendered-out static assets ready for deployment.
While the defaults should work for most (of my) use cases, you can configure behaviour using a rhedyn.config.js in the
directory where you run the tool, or pass a path to a config using -c /path/to/config.js or --config
path/to/config.js.
"Rhedyn" is Welsh for "fern", and is pronounced a bit like "read in".
The default config will look for .md files in the markdown/ directory, .scss files in the styles/ directory, and
handlebars (.hbs) files in the templates/ directory. It also processes images, SVG icons, static files, and generates favicons.
The styles will be compiled using sass and output to a matching path in the dist/ directory with a .css file
extension. These file paths are made available to the template renderer, for inclusion as whatever tags you see fit.
Markdown is compiled using marked with the marked-code-preview extension enabled, then passed to the template
renderer in the content property. Images referenced in markdown are automatically processed and converted to responsive
srcsets if matching images are found in the images task. Raw HTML in markdown is stripped by default; set
opts.markdown.allowHtml to true to allow it.
Once the markdown content and template have been rendered out, the resulting .html file is minified and output to dist/,
with the matching path (e.g. markdown/recipes/soup.md would be rendered to dist/recipes/soup.html).
Usual stuff applies - install as a module locally or globally and call the default function:
npx rhedyn
"scripts": {
"build": "rhedyn"
}
You can also build a self-contained executable if you like chunky binaries with npm run build. That'll output to the
dist/ directory, and you can then put that binary wherever you like (probably somewhere in your $path).
Rhedyn includes intelligent caching that tracks both file dependencies and configuration state. Tasks are only re-run when:
opts.ignoreExisting to true)Cache files are stored in .cache/ by default and can be disabled by setting cacheDir: false in your config.
If output file checks are skipped with ignoreExisting, only files that have changed inputs will be output.
opts.includeStateValues controls whether cache files store state values for debugging. It defaults to true and can be
set to false to reduce cache file size.
By default, rhedyn will look for rhedyn.config.js in the current directory and fall back to the default if not found.
You can override this behaviour using the -c /path/to/config.js or --config path/to/config.js CLI switches.
A working example of how to configure can be found in src/defaults.js, with the actions separated out to
src/actions/ for tidiness.
Your rhedyn.config.js should export an object with "tasks" and "opts". These are detailed below.
If you want to extend the default config, it can be imported as defaultConfig from this package.
The opts object in your config is for anything related to the project as a whole, rather than individual tasks. That
means things like the base output directory, cache directory, site metadata, etc. It'll be passed to the
action function as a key on the meta object.
opts can also have an include property, an object containing task keys (e.g. "styles") and additional glob patterns to include for
that task. The path can be anywhere, not just local to the project - I use it to
include basic typography styles that I know I'll want in every project, but it could just as easily be used to produce a
sort-of "theme" that could be shared across multiple projects.
opts: {
outDir: 'dist/',
runDir: process.cwd(),
cacheDir: '.cache',
clean: true,
ignoreExisting: false,
includeStateValues: true,
logLevel: 'debug',
include: {
styles: [{ pattern: '~/.rhedyn/styles/*.scss' }]
},
markdown: {
allowHtml: false
},
site: {
name: "My Website",
shortName: "My Site",
description: "A website generated from files using Rhedyn",
author: "Your Name",
url: "https://example.com",
language: "en-GB",
backgroundColor: "#ffffff",
themeColor: "#000000"
}
}
Tasks can be either individual task objects or arrays of task objects that run in parallel. Tasks in different array elements run sequentially, allowing you to control dependencies between task groups.
Example structure:
tasks: [
[
// These tasks run in parallel
{ name: "styles", ... },
{ name: "icons", ... },
{ name: "images", ... }
],
// This task runs after the above group completes
{ name: "pages", ... }
]
Each task object should look something like this:
{
name: "styles",
action: compileSass,
jobConfig: {
inputFiles: [{ pattern: "styles/**/*.scss", ignore: "**/_*.scss" }],
stripPaths: ["styles/"],
outputDir: "static/styles/",
outputFileExtension: ".css"
},
actionConfig: {}
}
Task Properties:
name: Task identifier (required)action: Function that processes the files (required)jobConfig: Config for file/state selection, expansion, output paths, and caching (see below)actionConfig: Config passed to the action (plus computed fields like filePath and fileOutputPath)Input File Patterns:
jobConfig: {
inputFiles: [
{ pattern: "styles/**/*.scss", ignore: "**/_*.scss" },
{ pattern: "images/*.jpg" },
{ pattern: "static/*" }
]
}
jobConfig options:
inputFiles: Array of glob objects ({ pattern, ignore?, dot? }) used for file expansion.stateSelectors: Array of dot-paths (e.g. resources.markdown) to expand from meta.expand: When false, run a single job with aggregated inputs instead of expanding per file/state item.stripPaths: Array of path prefixes removed from inputs when building output paths (supports ~).outputDir: Output directory relative to opts.outDir for generated files.outputFileExtension: New extension appended to output files (e.g. .html).outputFileName: Base filename used when building a single output path for state tasks.buildFilePath: When true, compute and pass fileOutputPath into the action for state tasks.buildIndexList: When true (default), generate an index.html in the output dir listing all index pages (state expansion only).itemsPerPage: Page size for state expansion pagination (defaults to opts.itemsPerPage).skipCache: When true, disables cache usage for this job.deps: Extra cache dependencies { paths?: string[], state?: string[] } added to the job fingerprint.logLevel: Override log level for this task only.jobId: Override the job ID used for logging and caching.An action is a function that receives an object with config, jobConfig, and meta properties and returns an object describing what was processed.
An action that returns a ref will have its detail, paths, ref and fromCache properties made available in
meta.resources. For expanded tasks (multiple results), the ref is used as the key under the task name. For
single-result tasks, the result is stored directly under the task name:
{
...meta,
resources: {
[task.name]: {
[jobResult.ref]: {
detail,
paths,
ref,
fromCache
}
}
// or, for single-result tasks:
// [task.name]: { detail, paths, ref, fromCache }
}
}
The action function signature:
async function myAction({ config, jobConfig, meta }) {
// config is the actionConfig plus computed properties:
// - filePath: path to the input file being processed
// - fileOutputPath: calculated output path
// - fileOutputDir: directory for the output file
// - inputs/pagination/stateKey when expanding from state
//
// jobConfig contains selection/expansion config (inputFiles, stateSelectors, etc.)
// meta contains:
// - opts: global configuration
// - resources: results from previously completed tasks, structured as described above
// Process the file...
return {
detail: {}, // Optional: any metadata about the processed file
paths: [outputPath], // Array of output file paths (relative to outDir)
deps: { // Optional: dependencies for caching
paths: [inputFile1, inputFile2], // File dependencies
state: [] // State dependencies (tracked automatically)
},
ref: "unique-identifier" // Optional: reference key for this result
}
}
Built-in Actions:
The following actions are available from actions:
compileSass: Compiles SCSS files to compressed CSSrenderMarkdownToHtml: Parses markdown into HTML and returns it as task detailrenderMarkdownToMeta: Parses markdown into frontmatter metadata onlyrenderTemplate: Renders Handlebars templates with the current task staterenderIndex: Renders index pages and index list pagesrenderMarkdownWithTemplate: Renders markdown with Handlebars templates, includes frontmatter supportoptimiseSvg: Optimizes SVG files using SVGOcopy: Copies files without processingimageToWebP: Converts images to WebP with multiple sizes for responsive imagesgenerateFavicons: Generates favicon sets and web app manifestsgenerateTaxonomy: Builds grouped/sorted lists from state inputsResources and Cross-Task References:
Processed files are made available to subsequent tasks via meta.resources[taskName][ref]. For example, the image action makes processed images available to the markdown renderer for automatic srcset generation.
Some examples can be found in src/actions/, and you can find utility functions exported from this package as
utils. The sample actions are also exported from this module as actions.
The default configuration includes:
Parallel processing group:
images: Converts JPG images to WebP with multiple sizesstyles: Compiles SCSS files to CSSicons: Optimizes SVG iconsstatic files: Copies static filesfavicons: Generates favicon sets from source imagesSequential tasks:
blog-markdown: Parses blog frontmatter metadata (no HTML rendering)markdown: Parses general frontmatter metadata (no HTML rendering)Parallel processing group:
author-taxonomy: Builds author groupings from blog entriestag-taxonomy: Builds tag groupings from blog entriesblog-latest: Builds a sorted list of recent blog entriesincludes: Renders template includes to HTMLParallel processing group:
render pages: Renders standard pages from markdown filesrender blog pages: Renders blog pages from blog markdown filesrender author indexes: Renders author index pages and index listrender tag indexes: Renders tag index pages and index listrender blog home: Renders the blog index pageRhedyn includes comprehensive logging with configurable levels:
silent (1): No outputerror (2): Errors onlywarn (3): Warnings and errorsinfo (4): General information (default)debug: Detailed debugging informationtrace (6): Maximum verbositySet the log level in your config:
opts: {
logLevel: 'debug'
}