Skip to content

pixu1980/dout-dev

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

dout.dev

dout.dev - Vanilla-first static blog with WCAG 2.2 AA accessibility and zero runtime dependencies

Overview

dout.dev is a modern static blog built with vanilla JavaScript, CSS, and HTML, focusing on performance, accessibility, and maintainability. The project follows a custom SSG approach with zero runtime dependencies.

Features

  • 🎯 Vanilla-first: Zero runtime dependencies, pure JS/CSS/HTML
  • ♿ Accessibility: WCAG 2.2 AA compliant with semantic markup
  • ⚡ Performance: Optimized for Core Web Vitals with PWA capabilities
  • 🔍 SEO: Complete meta tags, JSON-LD, OG images, RSS feeds
  • 📱 Progressive: Modern CSS with progressive enhancement
  • 🎨 Design System: Proprietary vanilla CSS system with design tokens
  • 🔧 Developer Experience: TypeScript support, live reload, comprehensive tooling

Architecture

  • CMS: Custom content management system for Markdown processing
  • Template Engine: Proprietary template system with include/extend/blocks/expressions
  • Build Pipeline: Vite-based bundling with PostCSS optimization
  • Deploy: GitHub Pages with automated CI/CD pipeline
  • PWA: Service Worker caching with offline support

How To Generate The Blog

This repository already contains the generator. The workflow is split into two phases: content generation with the in-repo CMS, then asset bundling with Vite.

1. Install dependencies

pnpm install

2. Write or update content

  • Add markdown posts in data/posts/.
  • Use front matter for title, date, description, tags, and optional cover fields.
  • Reusable HTML lives in src/components/ and page templates live in src/templates/.

Minimal example:

---
title: 'Your Post Title'
date: '2026-04-04'
published: true
tags: ['css', 'accessibility']
description: 'Short summary used for cards and metadata'
cover_image: ../assets/images/example-cover.jpg
cover_alt: A descriptive alternative text for the cover
canonical_url: false
---
## Start writing

Your markdown content goes here.

3. Generate the static source files

pnpm -s cms:build

This step parses markdown, normalizes metadata, generates archive/search data, and writes the derived HTML files into src/.

4. Run the site locally

pnpm -s dev

Do not open files inside src/ directly in the browser when validating the UI. The project relies on root-relative asset paths such as /styles/main.css, and Vite rewrites production assets during build. Use the dev server or the production preview instead.

If Safari shows TLS failures for local assets while you are on http://127.0.0.1:3000/, check that the generated HTML does not contain upgrade-insecure-requests in the meta CSP. That directive belongs in response headers for production, not in the dev HTML served over plain HTTP.

If you open src/*.html or dist/*.html through file://, Chromium will also block linked CSS and JS as CORS requests from origin null. That failure is expected browser behavior, not a missing stylesheet in the repo.

For content-heavy work you can also run the CMS watcher in a second terminal:

pnpm -s cms:watch

5. Validate before publishing

pnpm -s test
pnpm -s format:check
pnpm -s validate:all

If you only need the full gate in one shot:

pnpm -s quality:check

6. Produce the production build

pnpm -s build

This runs the image pipeline, rebuilds CMS output, bundles the site with Vite, copies static assets, and verifies the final dist/ artifact.

7. Preview the production output

pnpm -s preview

Daily Workflow

  1. Create or edit markdown in data/posts/.
  2. Update templates/components in src/templates/ or src/components/ when structure changes.
  3. Run pnpm -s cms:build after content or template edits.
  4. Use pnpm -s dev to inspect the generated site.
  5. Run pnpm -s quality:check before merging or publishing.

Where Things Live

  • data/posts/: source articles in markdown.
  • scripts/cms/: normalization, page generation, archive indexes, image metadata.
  • scripts/template-engine/: custom HTML-oriented rendering engine.
  • src/templates/: source page templates.
  • src/components/: reusable HTML fragments.
  • src/styles/: global design system and component styling.
  • docs/: project notes, roadmap, and auxiliary design/color references.

Source Of Truth Vs Generated Output

  • Edit and keep: data/posts/, src/components/, src/layouts/, src/templates/, src/styles/, src/scripts/, scripts/cms/, scripts/template-engine/, _headers, cspell.config.json, package.json, vite.config.js.
  • Generated by the CMS, not by hand: src/posts/, src/tags/, src/months/, src/series/, src/data/*.json, src/feed.json, src/feed.rss, src/feed.xml, src/sitemap.xml.
  • Disposable build or test artifacts: dist/, test-results/, playwright-report/.
  • Reference or development support: tests/, docs/, design/, custom/.

When cleaning the repository, start from disposable artifacts first. Remove reference or development folders only if you are sure you no longer need their documentation, fixtures, or design assets.

Quick Start

# Install dependencies
pnpm install

# Build content from Markdown
pnpm -s cms:build

# Start development server
pnpm -s dev

# Build for production
pnpm -s build

# Preview production build
pnpm -s preview

Publishing

  • pnpm build now produces a GitHub Pages-ready dist/ artifact, including CNAME, RSS feeds, sitemap.xml, search indexes, and sw.js.
  • .github/workflows/deploy-pages.yml deploys automatically on pushes to main and on manual dispatch.
  • In repository settings, set Pages to use GitHub Actions and configure the custom domain as dout.dev.
  • CNAME remains in the repo for the apex domain build artifact.

Giscus Comments

Giscus integrates GitHub Discussions for post comments. Configuration is driven by environment variables read at build time.

Setup

  1. Get Giscus values from giscus.app configuration wizard. You need:

    • Repository (owner/repo)
    • Discussion category
    • Both IDs (from GitHub API)
  2. Copy the template:

    cp .env.example .env.local
  3. Update .env.local with your Giscus values:

    GISCUS_REPO=your-owner/your-repo
    GISCUS_REPO_ID=<from giscus.app>
    GISCUS_CATEGORY=General
    GISCUS_CATEGORY_ID=<from giscus.app>
    GISCUS_MAPPING=url
    GISCUS_STRICT=0
    # ... other settings

    (.env.local is git-ignored and stays local)

  4. For production (GitHub Actions): set these as repository secrets in GitHub:

    • GISCUS_REPO
    • GISCUS_REPO_ID
    • GISCUS_CATEGORY
    • GISCUS_CATEGORY_ID

    GitHub Actions will inject them at build time via ${{ secrets.GISCUS_REPO }} etc.

Configuration Options

  • GISCUS_REPO – owner/repo format (e.g., pixu1980/dout-dev)
  • GISCUS_REPO_ID – repository ID from GitHub API
  • GISCUS_CATEGORY – discussion category name
  • GISCUS_CATEGORY_ID – category ID from GitHub API
  • GISCUS_MAPPINGurl (default), pathname, title, og:title, specific
  • GISCUS_STRICT0 (allow unlisted URLs) or 1 (strict mode)
  • GISCUS_REACTIONS_ENABLED1 (enable emoji reactions)
  • GISCUS_EMIT_METADATA1 (emit title/URL metadata to discussions)
  • GISCUS_INPUT_POSITIONtop or bottom (comment form placement)
  • GISCUS_THEMEdark, light, preferred_color_scheme, or custom
  • GISCUS_LANG – language code (e.g., en)
  • GISCUS_LOADINGlazy or eager

How It Works

  • Comments are enabled/disabled based on presence of all 4 required env vars
  • Build-time rendering: Giscus config is baked into the <script> tag in generated post pages
  • Discussions are keyed to the post's published URL (GISCUS_MAPPING=url)
  • Each discussion thread title includes the source markdown path for reference

Project Structure

├── scripts/
│   ├── cms/                # Content management pipeline
│   │   ├── build.js        # Main CMS build script
│   │   ├── posts.js        # Post generation
│   │   ├── months.js       # Month archive generation
│   │   └── tags.js         # Tag page generation
│   └── template-engine/    # Custom template engine
├── data/                   # Markdown source files
│   └── posts/              # Blog posts in Markdown
├── src/                    # Generated static HTML files
│   ├── templates/          # HTML templates
│   │   ├── layouts/        # Base layouts
│   │   └── components/     # Reusable components
│   ├── styles/             # CSS with proprietary design system
│   ├── posts/              # Generated post pages
│   ├── months/             # Generated month archives
│   ├── tags/               # Generated tag pages
│   ├── sw.js               # Service Worker for PWA
│   ├── manifest.json       # Web App Manifest
│   └── performance-test.html # Performance testing suite
├── dist/                   # Final build output
└── .github/                # CI/CD workflows

Content Management

The CMS system processes Markdown files with YAML front-matter:

# Build all content
pnpm -s cms:build

# Watch for content changes (development)
pnpm -s cms:watch

# Clean generated files
pnpm -s cms:clean

Writing Posts

Create Markdown files in data/posts/ with this front-matter:

---
title: 'Your Post Title'
date: '2025-08-15'
tags: ['javascript', 'css', 'performance']
description: 'Brief description for SEO and social sharing'
published: true
---
Your content here...

Template Development

Templates use a custom syntax with powerful features:

<extends src="./layouts/base.html">
  <block name="title">{{ title }} - dout.dev</block>
  <block name="content">
    <h1>{{ title | capitalize }}</h1>
    <p>{{ description | default:"No description available" }}</p>

    <if condition="tags.length > 0">
      <ul>
        <for each="tag in tags">
          <li><a href="/tags/{{ tag.key }}.html">{{ tag.label }}</a></li>
        </for>
      </ul>
    </if>
  </block>
</extends>

Template Features

  • Inheritance: <extends> and <block> for layout structure
  • Composition: <include> for reusable components
  • Expressions: {{ variable | filter }} with built-in filters
  • Control Flow: <if>, <for>, <switch> statements
  • Security: Expression sandboxing without eval()

Authoring rules (mandatory)

  • Do NOT place <if> elements inside an opening tag to conditionally add attributes.
    • Instead, use JavaScript expressions (ternary or logical OR) inside attribute values.
    • Examples:
      • width="{{ post.coverWidth ? post.coverWidth : '' }}"
      • height="{{ post.coverHeight || '' }}"
      • Avoid: <img <if condition="post.coverWidth">width="{{ post.coverWidth }}"</if> />

Performance Features

  • PWA Ready: Service Worker with caching strategies
  • Critical CSS: Inlined above-the-fold styles
  • Lazy Loading: Images and below-the-fold content
  • Code Splitting: Per-page CSS and JavaScript bundles
  • Compression: Gzip optimization and minification

Responsive Images (Markdown) +

The Markdown renderer supports responsive images with lazy loading, <picture> with WebP+raster sources, and <noscript> fallback.

  • Title meta syntax (segments separated by |):
    • srcset=... candidates list (path 320w, path 640w)
    • sizes=... sizes descriptor ((max-width: 640px) 100vw, 640px)
    • loading=eager|lazy (default: lazy)
    • priority=high|low sets fetchpriority (default: low). If priority=high or loading=eager, lazy/noscript are disabled and attributes are inlined for LCP.

Example:

![Alt](../assets/images/example.jpg "Hero | srcset=../img/320.jpg 320w, ../img/640.jpg 640w | sizes=(max-width: 640px) 100vw, 640px")

Notes:

  • For local PNG/JPEG assets the engine tries to add width/height to reduce CLS.
  • If srcset isn’t provided, it’s built automatically from src/assets/images-manifest.json.
  • Output uses <picture>: WebP <source> + raster <source>; eager mode emits real srcset, lazy uses data-srcset and a <noscript> <img> fallback.
  • For LCP images (e.g., covers) prefer loading=eager | priority=high.

Theme, dark mode and accent color (M9)

  • Theme modes: auto (default), light, dark. The toggle in the header cycles through Auto → Dark → Light → Auto.
  • Persistence: user choice is stored in localStorage under theme and applied by setting documentElement.dataset.theme.
  • System preference: when set to Auto, prefers-color-scheme decides between light/dark; switching OS theme updates the site live.
  • Accent: choose among Default, Violet, Green. Stored as accent in localStorage and applied as body[data-accent].
  • A11y: header menu is keyboard accessible with a focus trap when open; Escape closes it; outside click closes it on touch/mouse.

Image Pipeline

Run pnpm -s images:generate to create responsive variants for images under src/assets/images:

  • Resized variants for JPG/PNG: -320, -640, -960, -1280 (no upscaling)
  • WebP base and matching WebP variants
  • A manifest written to src/assets/images-manifest.json

The pnpm build script runs this step automatically before CMS and Vite.

Pagination & archives

  • URL scheme:
    • Page 1 flat: /tags/slug.html, /months/YYYY-MM.html (and /series/<name>.html)
    • Pages 2+: subfolder with index.html: /tags/slug/2/, /months/YYYY-MM/2/
  • A11y/SEO:
    • rel="prev"/"next", aria-current="page", ellipses non-clickable
  • Archives generated:
    • Tags: src/tags/<slug>.html + RSS src/tags/<slug>.xml
    • Months: src/months/<YYYY-MM>.html + RSS src/months/<YYYY-MM>.xml
    • Series: src/series/<slug>.html

Quick use:

  • In a listing template, include the shared UI:
    • <include src="../components/pagination.html"></include>
  • Expose pagination from your generator to drive the component.
  • Add RSS link in <head> of tag/month pages:
    • <link rel="alternate" type="application/rss+xml" href="{{ canonicalUrl.replace('.html', '.xml') }}" />

Code highlighting

  • Use fenced code blocks in Markdown (js, css, ```html, etc.).
  • Renderer outputs <pre is="pix-highlighter" lang="..."><code>…</code></pre>.
  • Supported lexers: js, ts, css, html, json, md, bash, python, go, rust, c, cpp, php, csharp, yaml.

Post front-matter (fields)

  • title, date, description, tags, published
  • Optional: coverImage, pinned, series, keywords, layout
  • coverImage local (PNG/JPG): width/height inferred automatically when possible; benefits responsive pipeline.

Design System

Built with proprietary vanilla CSS system and custom design tokens:

/* Spacing system */
padding: var(--space-4);
margin: var(--space-6);

/* Typography scale */
font-size: var(--text-lg);
line-height: var(--font-lineheight-3);

/* Color system */
color: var(--text-primary);
background: var(--surface-1);

Testing

Performance testing suite available at /performance-test.html:

  • PWA functionality validation
  • Service Worker cache testing
  • Performance metrics monitoring
  • Design system verification

Browser Support

  • Modern Browsers: Chrome 90+, Firefox 90+, Safari 14+, Edge 90+
  • Progressive Enhancement: Graceful degradation for older browsers
  • Accessibility: Screen reader compatible, keyboard navigation

Deployment

Automated deployment to GitHub Pages:

  1. Push to main branch
  2. GitHub Actions runs build process
  3. Deploys to https://dout.dev

Manual deployment:

pnpm build
# Upload dist/ to your hosting provider

Favicons e build (importante)

Il processo di build si aspetta che alcuni asset di favicon/manife­st siano presenti alla radice del progetto. I nomi attesi (come riferiti in favicon.data.json) sono, ad esempio:

  • favicon-96x96.png
  • favicon.svg
  • favicon.ico
  • apple-touch-icon.png (180x180 consigliato)
  • site.webmanifest (web manifest)

Dove posizionarli

  • Copia i file nella root del repository (stesso livello di package.json). Lo script scripts/build-assets.js cercherà i percorsi esattamente come indicati in favicon.data.json.

Cosa fa lo script di build

  • Se i file sono mancanti, lo script ora genera dei placeholder (file PNG/SVG/manifest minimi) dentro dist/ in modo da permettere preview e debug.
  • Nonostante i placeholder vengano creati, la build è comunque progettata per fallire quando mancano i file reali: questo provoca un errore chiaro in CI così da prevenire pubblicazioni incomplete.

Test locale

  • Per verificare localmente:
    • Installa dipendenze: pnpm install
    • Esegui: pnpm build
    • Se mancano i favicon reali, vedrai un errore come Missing favicon assets e i placeholder saranno comunque creati in dist/.

Come risolvere il fallimento

  • Aggiungi i file reali nella root con i nomi attesi.
  • In alternativa (temporaneo) puoi creare dei file vuoti con i nomi corretti prima di eseguire la build:
touch favicon-96x96.png favicon.svg favicon.ico apple-touch-icon.png site.webmanifest
  • Se preferisci cambiare il comportamento (es. trasformare il fallimento in warning), modifica scripts/build-assets.js nella funzione processFavicons rimuovendo il throw dopo la creazione dei placeholder.

Suggerimenti

  • For production usa immagini reali (PNG/SVG/ICO) alle risoluzioni consigliate: 48–512px per PNG, SVG per scalabilità e apple-touch-icon a 180x180.
  • Aggiorna favicon.data.json se cambi i nomi o i percorsi.

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Test with pnpm build
  5. Submit a pull request

License

MIT License - see LICENSE for details.


Live Site: https://dout.dev
Repository: https://github.com/pixu1980/dout-dev

Search (M10)

  • The search page (/search.html) loads static JSON datasets from /data/posts.json, /data/tags.json, /data/months.json, /data/series.json.
  • URL parameters:
    • q: the search term
    • page: the page number (1-based)
    • type: optional, repeated param to filter result types. Allowed values: post, tag, series, month. Example: ?q=css&type=post&type=tag
  • Accessibility: the form has role="search"; results summary uses aria-live="polite" and announces page changes.
  • Filters: a fieldset of checkboxes lets you include/exclude types (posts, tags, series, months). Selection is reflected in the URL (type=...).
  • Pagination: client-side (10 items/page). UI and semantics aligned with the shared pagination component (Prev/Next, aria-current, links preserve q and type).
  • Ranking: simple text matching with light boosts for title/tags; extra boost applied on exact keyword matches (keywords are extracted at build time).

About

The DoUtDev Blog, a place for sharing and caring from developers to developers: create your Pull Request adding a markdown file for you Article and we'll review it and publish it (according to our CoC)

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors