a quick and dirty static site generator

Craig Fletcher 557b05b05b Add config override switch 2 hari lalu
src 557b05b05b Add config override switch 2 hari lalu
.gitignore 5937e76daa Initial commit 11 bulan lalu
.nvmrc 5937e76daa Initial commit 11 bulan lalu
README.md 557b05b05b Add config override switch 2 hari lalu
eslint.config.js 1a4361527a Add cache support 5 bulan lalu
package-lock.json 3b580f85ed Refactoring processor API 5 bulan lalu
package.json d580801284 Refactor: break up utils 5 bulan lalu

README.md

rhedyn

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".

Installation and usage

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.

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).

Caching

Rhedyn includes intelligent caching that tracks both file dependencies and configuration state. Tasks are only re-run when:

  • Input files have changed (detected via file hashes)
  • Configuration has changed (detected via state hashes)
  • Output files are missing (can be skipped by setting 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.

You can get additional detail on state cache misses by setting opts.includeStateValues to true, at the cost of considerably larger cache files.

Configuration

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 processors separated out to src/processors.js 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.

opts

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 processor 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: false,
  logLevel: 'debug',
  include: { 
    styles: [{ pattern: '~/.rhedyn/styles/*.scss' }] 
  },
  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

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",
  inputFiles: [{ pattern: "styles/**/*.scss", ignore: "**/_*.scss" }],
  stripPaths: ["styles/"],
  outputDir: "static/styles/",
  outputFileExtension: ".css",
  processor: compileSass
}

Task Properties:

  • name: Task identifier (required)
  • inputFiles: Array of glob pattern objects with pattern and optional ignore properties - if set, the task will be expanded for every file found
  • stripPaths: Array of path prefixes to remove from input paths when generating output paths
  • outputDir: Directory within outDir where processed files should be placed
  • outputFileExtension: File extension for processed files
  • processor: Function that processes the files (required)
  • logLevel: optionally override the log level for a specific task
  • Additional processor-specific properties (e.g., imageSizes, quality for image processing)

Input File Patterns:

inputFiles: [
  { pattern: "styles/**/*.scss", ignore: "**/_*.scss" },
  { pattern: "images/*.jpg" },
  { pattern: "static/*" }
]

processors

A processor is a function that receives an object with config and meta properties and returns an object describing what was processed.

A processor that returns a ref will have it's detail, paths, ref and fromCache properties made available in meta.resources, with the ref as the key under the task name:

{
  ...meta,
  resources: {
    [task.name]: {
      [jobResult.ref]: {
        detail,
        paths,
        ref,
        fromCache
      }
    }
  }
}

The processor function signature:

async function myProcessor({ config, meta }) {
  // config contains the task configuration plus file-specific properties:
  // - filePath: path to the input file being processed
  // - fileOutputPath: calculated output path
  // - fileOutputDir: directory for the output file
  
  // 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 Processors:

The following processors are available from processors:

  • compileSass: Compiles SCSS files to compressed CSS
  • renderMarkdownWithTemplate: Renders markdown with Handlebars templates, includes frontmatter support
  • optimiseSvg: Optimizes SVG files using SVGO
  • copy: Copies files without processing
  • imageToWebP: Converts images to WebP with multiple sizes for responsive images
  • generateFavicons: Generates favicon sets and web app manifests

Resources and Cross-Task References:

Processed files are made available to subsequent tasks via meta.resources[taskName][ref]. For example, the image processor makes processed images available to the markdown renderer for automatic srcset generation.

Some examples can be found in src/processors.js, and you can find utility functions exported from this package as utils. The sample processors are also exported from this module as processors.

Default Tasks

The default configuration includes:

  1. Parallel processing group:

    • styles: Compiles SCSS files to CSS
    • icons: Optimizes SVG icons
    • images: Converts JPG images to WebP with multiple sizes
    • static files: Copies static files
    • favicons: Generates favicon sets from source images
  2. Sequential processing:

    • pages: Renders Markdown files with Handlebars templates (runs after the parallel group to access processed resources)

Logging

Rhedyn includes comprehensive logging with configurable levels:

  • silent (1): No output
  • error (2): Errors only
  • warn (3): Warnings and errors
  • info (4): General information (default)
  • debug: Detailed debugging information
  • trace (6): Maximum verbosity

Set the log level in your config:

opts: {
  logLevel: 'debug'
}