Skip to content
Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Repository files navigation

Static Site Builder & Minifier

A small, fast static site builder written in Go. It minifies HTML, CSS, and JavaScript into a distribution directory, and ships a development server with live reload so you can see changes in the browser as you save.

The project keeps its dependency footprint deliberately tiny. The minifier, the file watcher, and the WebSocket server are all implemented internally on top of the Go standard library; the only external dependencies are caarlos0/env for configuration and uber-go/zap for logging.

Features

  • Minifies HTML, CSS, and JavaScript with an internal tokenizer-based minifier (internal/minify)
  • Copies all other assets (images, fonts, favicons) verbatim
  • Development server with live reload over an internal WebSocket implementation (internal/websocket, RFC 6455 server side)
  • Polling file watcher with no OS-specific dependencies (internal/watcher)
  • File access is scoped with os.Root, so neither the builder nor the dev server can ever read or write outside the directories you give them
  • Configured entirely through environment variables

How minification works

The minifier is conservative by design: it removes what is provably safe to remove and preserves everything else.

  • HTML - comments are dropped (conditional comments kept), text and tag whitespace is collapsed, <script>/<style> contents are minified with the JS/CSS minifiers, and <pre>/<textarea> contents are preserved verbatim.
  • CSS - a tokenizer strips comments, collapses whitespace, and drops redundant semicolons while preserving strings, spacing inside parentheses (calc()), and descendant selectors like div :hover.
  • JavaScript - a tokenizer that understands strings, template literals (with nested ${} expressions), and regex-vs-division removes comments and whitespace. A scope analysis then renames function-local variables to short names, removes line breaks (inserting ; exactly where automatic semicolon insertion applied, so parsing is provably unchanged), drops redundant semicolons, and shortens literals (true to !0, undefined to void 0). Together this minifies lodash by 85% and jQuery by 66% - within a few points of AST-based minifiers. Everything is strictly conservative: a name is renamed only when provably local everywhere, new names are globally fresh, top-level bindings are never touched, and these passes disable themselves entirely on constructs outside the analysable subset (ES2015+ binding forms, eval, with), falling back to whitespace-only minification. License banner comments (/*!) are preserved.

Requirements

  • Go 1.26+

Installation

Build from source:

make build          # binary in bin/builder

Or install straight into your GOBIN:

go install github.com/aanantaco/static-site-minifier/cmd/builder@latest

Pre-built binaries for Linux (amd64, arm64, arm), macOS (amd64, arm64), and Windows are attached to each GitHub release.

GitHub Action

Minify a site in CI without installing anything - the action downloads a prebuilt binary from the releases and runs it:

steps:
  - uses: actions/checkout@v7
  - uses: aanantaco/static-site-minifier@v1
    with:
      src: src    # source directory (default: src)
      dest: dist  # output directory (default: dist)

The v1 tag always points at the latest release. Pin a specific release with version: v0.3.1-abc1234 (defaults to latest). Works on Linux, macOS, and Windows runners, including ARM.

The action reports the release tag it actually downloaded in its version output:

  - uses: aanantaco/static-site-minifier@v1
    id: minify
  - run: echo "minified with ${{ steps.minify.outputs.version }}"

Releasing

Every push to main publishes a GitHub release with binaries for all platforms and moves the v1 tag to it, so action users are always on the latest release. The GitHub Marketplace listing is the one thing releases cannot update automatically: GitHub only lists versions that were published to the Marketplace by hand (edit the release, tick "Publish this Action to the GitHub Marketplace", update). Do that for milestone releases; the moving v1 tag keeps users on current code either way.

Usage

The builder binary has three subcommands:

builder build       # minify src/ into dist/
builder run         # serve src/ with live reload on :8080
builder version     # print version, commit, and build time

During a build, .html, .css, and .js files are minified; every other file is copied as-is. Files and directories starting with . are skipped. Minified HTML pages get a trailing <!-- minified at ... --> timestamp comment.

The dev server watches the source directory and pushes a reload message over a WebSocket to every connected browser whenever a file is added, changed, or removed. The reload listener is injected into served HTML pages automatically - nothing to add to your pages.

Configuration

All configuration is via environment variables:

Variable Default Description
SRC_DIR src Source directory
DEST_DIR dist Destination (build) directory
PORT 8080 Dev server port
DEBUG false Enable debug logging

Example:

SRC_DIR=site DEST_DIR=public builder build
PORT=3000 builder run

Performance

Files are minified concurrently, one worker per CPU. On a modest 4-core 2.1GHz container, a 61-file, 35.7MB site builds in about 0.2 seconds at ~20MB peak memory; the binary starts serving in under 20ms.

Minifier throughput on real-world inputs (see internal/minify benchmarks): lodash ~45 MB/s, Bootstrap CSS ~135 MB/s, HTML ~110 MB/s. The watcher polls a 1,000-file tree in ~2ms, so the dev server idles near zero CPU.

go test -bench . -benchmem ./internal/...

Development

Common tasks are wrapped in make targets:

make build          # Build the binary into bin/
make run            # Build and run
make test           # Run tests (race detector on)
make test-cover     # Run tests with a coverage summary
make lint           # golangci-lint
make fmt            # go fmt
make check          # fmt-check + vet + lint + test
make release        # Cross-compile for all platforms

Tests live next to the code (*_test.go) and cover the minifier, the builder, the dev server (including path traversal and live reload at the WebSocket frame level), the watcher, the logger, and the CLI. Overall coverage is above 90%.

CI runs lint, tests with coverage, a security scan (Trivy, govulncheck, gosec), CodeQL, and cross-platform builds on every pull request. Pushes to main additionally publish a GitHub release with binaries for every platform.

Project Layout

cmd/builder/         CLI entry point (build, run, version)
internal/builder/    Build pipeline: minify + copy into dist
internal/minify/     Tokenizer-based HTML/CSS/JS minifier
internal/server/     Dev server with live reload
internal/websocket/  Minimal RFC 6455 WebSocket server side
internal/watcher/    Polling directory watcher
internal/logger/     zap logger setup
test/                Example site used as a fixture

License

MIT License

About

Fast static site builder in Go with a tokenizer-based HTML/CSS/JS minifier and a zero-config live-reload dev server.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages