Skip to content

Latest commit

 

History

History
753 lines (553 loc) · 18.4 KB

File metadata and controls

753 lines (553 loc) · 18.4 KB

Development Guide

This guide will help you set up and develop on pob.dev locally.

Prerequisites

  • Node.js - Version 24.15.0 or higher (CI uses 24)
  • npm - Version 12.0.0 or higher (Node's bundled npm may lag behind; run npm install -g npm@12 to update)
  • Git - For version control

Initial Setup

  1. Clone the repository
git clone https://github.com/p-ob/pob.dev.git
cd pob.dev
  1. Install dependencies
npm ci

This installs exact versions from package-lock.json.

  1. Start the development server
npm start

The site will be available at http://localhost:8080 with hot reload enabled.

Available Scripts

Development

npm start
  • Starts Eleventy in watch mode with live reload
  • Incremental builds for faster development
  • Draft posts are visible
  • Serves on http://localhost:8080

Building

npm run build
  • Full production build (Eleventy + PageFind search index)
  • Minifies CSS
  • Optimizes images
  • Generates search index
  • Outputs to public/ directory
npm run build:11ty
  • Builds static site only (no search index)
  • Faster for testing build output
npm run build:index
  • Generates PageFind search index only
  • Run after build:11ty to update search

Code Quality

npm run lint
  • Checks code formatting with Prettier
  • Validates JavaScript, Markdown, CSS, JSON, HTML
npm run format
  • Auto-formats all source files
  • Applies Prettier configuration

Deployment (Local Testing)

npm run dev
  • Starts local Cloudflare Workers environment
  • Simulates production deployment locally
  • Requires prior npm run build
npm run deploy
  • Deploys to Cloudflare Workers
  • Requires Cloudflare credentials
  • Typically done via CI/CD

Cleanup

npm run clean
  • Removes build artifacts (public/ directory)
  • Preserves .env file if present
  • Uses git clean to ensure complete cleanup

Project Structure

Source Files (src/)

All source content lives here:

src/
├── _data/              # Global data files (author.js, metadata.js)
├── _includes/          # Layouts and reusable components
├── assets/             # Static assets (CSS, JS)
├── blog/               # Blog posts (organized by year/month)
├── index.njk           # Homepage
├── blog.njk            # Blog listing
├── reading.njk         # RSS feed reader
├── search.njk          # Search page
├── feed.njk            # Feed listing page
└── about.md            # About page

Custom Plugins (11ty/)

Eleventy plugins that extend functionality:

  • draft.js - Hides draft posts in production
  • externals.js - Manages external dependencies with import maps
  • feeds.js - Generates RSS/Atom/JSON feeds
  • feed-aggregator.js - Aggregates external RSS feeds
  • json-html.js - Sanitizes JSON for HTML output
  • table-of-contents.js - Generates article TOC
  • syntax-highlight.js - Per-page syntax highlighting with language detection

Configuration Files

  • eleventy.config.js - Main Eleventy configuration
  • wrangler.jsonc - Cloudflare Workers configuration
  • feeds.json - External RSS feed sources
  • .editorconfig - Editor settings
  • .prettierrc - Code formatting rules

Creating Content

Writing a Blog Post

  1. Create a new markdown file
# Follow the date-based structure
src/blog/YYYY/MM/your-post-title.md

Example: src/blog/2024/11/new-feature.md

  1. Add frontmatter
---
title: Your Post Title
description: A brief description of your post
date: 2024-11-30 10:00:00 -06:00
tags:
  - tag1
  - tag2
---

Your content here...
  1. Required frontmatter fields

    • title - Post title
    • description - Brief description (used in lists and feeds)
    • date - Publication date, always with an explicit time and UTC offset (see "Dates and time zones" below) — a bare YYYY-MM-DD is parsed as UTC midnight and can render as the previous day once formatted in a non-UTC time zone
  2. Optional frontmatter fields

    • tags - Array of tags for categorization
    • draft - Set to true to mark as draft (visible in dev only)
    • updatedDate - Last modification date (displays alongside publish date)

Dates and Time Zones

readableDate formats dates with Intl.DateTimeFormat without a fixed timeZone, so it renders in whatever time zone the build/dev machine is running in. A bare date: 2024-11-30 is parsed as 2024-11-30T00:00:00.000Z (UTC midnight) — format that in any time zone behind UTC (e.g. npm start on a laptop set to America/Chicago) and it displays as November 29, one day early.

Always give date (and updatedDate) an explicit time and UTC offset, e.g. 2024-11-30 10:00:00 -06:00. A mid-morning local time keeps the calendar date stable regardless of which time zone the renderer uses. This applies to every content type with a date field (blog posts under src/blog/, talks under src/talks/), not just blog posts.

This site is authored from Milwaukee, WI (America/Chicago). Pick the offset based on whether the date falls in Daylight Saving Time:

  • -05:00 (CDT) — mid-March through early November
  • -06:00 (CST) — early November through mid-March

Updated Posts

When you update a post significantly, add the updatedDate field:

---
title: My Post
description: A post that was updated
date: 2024-11-30 10:00:00 -06:00
updatedDate: 2025-01-15 10:00:00 -06:00
---

This will:

  • Display as "November 30, 2024 (Updated January 15, 2025)" on the post
  • Add dateModified to Schema.org structured data for SEO
  • Keep the original date for sorting and feeds

Draft Posts

To work on a post without publishing:

---
title: Work in Progress
description: Still writing this
date: 2024-11-30 10:00:00 -06:00
draft: true
---

Drafts are:

  • ✅ Visible in development (npm start)
  • ❌ Hidden in production builds (npm run build)
  • ❌ Excluded from feeds and collections

Markdown Features

Standard markdown plus:

  • Footnotes - Via markdown-it-footnote
  • Code blocks - With automatic syntax highlighting (see below)
  • External links - Automatically open in new tab
  • Images - Automatically optimized by Eleventy

Code Blocks with Syntax Highlighting

Code blocks automatically get syntax highlighting using the <syntax-highlight> element. The system intelligently loads only the languages you use.

Basic usage:

```javascript
function hello() {
  console.log("Hello, world!");
}
```

Supported languages:

The site supports all Prism languages. Common ones include:

  • javascript, js - JavaScript
  • typescript, ts - TypeScript
  • python, py - Python
  • csharp, cs - C#
  • bash, sh, shell - Shell scripts
  • html, xml - Markup
  • css, scss - Stylesheets
  • json - JSON
  • markdown, md - Markdown

Note Boxes

Use GitLab-style alert syntax to create styled note boxes. Five types are supported:

Note - General information or reminders:

> [!note]
> This is important information readers should know.

Info - Helpful tips or additional context:

> [!info]
> This provides helpful context or tips.

Success - Positive outcomes or achievements:

> [!success]
> The operation completed successfully!

Warning - Important cautions or considerations:

> [!warning]
> Be careful when doing this operation.

Error - Critical errors or failures:

> [!error]
> An error occurred during processing.

Multi-line notes:

> [!warning]
> This warning spans multiple lines.
> Each line should start with `> ` to be included.
> The note will display all lines together.

Custom labels: You can override the default label with custom text:

> [!warning] Data deletion
> The following instructions will make your data unrecoverable.

> [!info] Pro tip
> Use keyboard shortcuts to speed up your workflow.

Notes are rendered as <pob-note> web components with appropriate styling for each type.

Language aliases:

Many languages have aliases that map to the same grammar:

  • jsjavascript
  • pypython
  • cscsharp
  • sh, shellbash

Multiple languages in one post:

```javascript
// JavaScript example
const x = 42;
```

```python
# Python example
x = 42
```

```csharp
// C# example
int x = 42;
```

When you use multiple languages, only those specific languages are loaded from the CDN. Pages without code blocks don't load the syntax highlighting library at all, improving performance.

How it works:

  1. The build process detects which languages are used in your post
  2. The page loads only those specific languages (plus base languages: markup, css, javascript)
  3. Syntax highlighting happens at runtime using the CSS Custom Highlight API
  4. No <span> elements clutter your HTML – just clean, semantic markup

Live HTML Demos

You can make HTML code blocks interactive by adding the live modifier. This renders the code in a sandboxed iframe that readers can run.

Basic usage:

```html live
<button onclick="alert('Hello!')">Click me</button>
```

This will display:

  1. The syntax-highlighted code block
  2. A "Run" button in a toolbar below the code
  3. When clicked, an "Output" panel slides in showing the live rendered HTML

Features:

  • Sandboxed execution - Code runs in an iframe with sandbox="allow-scripts" for security
  • Dark mode support - The output panel respects the user's color scheme preference
  • Auto-sizing - The iframe automatically resizes to fit its content
  • One-click run - Click "Run" to see the result; the demo stays visible

Example with CSS and JavaScript:

```html live
<button id="btn">Count: 0</button>

<style>
  #btn {
    padding: 0.5em 1em;
    font-size: 1.2em;
    cursor: pointer;
  }
</style>

<script>
  let count = 0;
  document.getElementById('btn').onclick = () => {
    count++;
    document.getElementById('btn').textContent = `Count: ${count}`;
  };
</script>
```

Limitations:

  • Only works with html language blocks
  • Cannot access the parent page's DOM or styles
  • External resources may be blocked by the sandbox

Styling:

Code blocks automatically match your site's theme (light/dark mode) using CSS custom properties. The highlighting styles are defined in src/assets/css/partials/_code.css.

Using Web Components

Note Boxes

<pob-note type="note">
This is an informational note.
</pob-note>

<pob-note type="warning">
This is a warning.
</pob-note>

<pob-note type="error">
This is an error or critical information.
</pob-note>

Table of Contents

TOC is automatically generated from heading elements (h2, h3, etc.) when using the post layout.

No action needed - it just works!

Styling

CSS Architecture

CSS is organized using CSS layers:

@layer reset, config, base, utility, interactions, layout;

Adding styles:

  1. Global utilitiessrc/assets/css/partials/_base.css
  2. Component stylessrc/assets/css/components/component-name.css
  3. Theme variablessrc/assets/css/partials/_vars.css
  4. Interaction stylessrc/assets/css/partials/_interactions.css

CSS Custom Properties

Theme variables are defined in _vars.css:

@property --font-color {
	syntax: "<color>";
	inherits: true;
	initial-value: hsl(0, 0%, 20%);
}

@property --page-background-color {
	syntax: "<color>";
	inherits: true;
	initial-value: hsl(0, 0%, 96%);
}

Dark mode variants use prefers-color-scheme:

@media (prefers-color-scheme: dark) {
	:root {
		--font-color: hsl(0, 0%, 91%);
		--page-background-color: hsl(220, 4%, 14%);
	}
}

Component Naming

Use BEM-like naming:

.component-name { }
.component-name__element { }
.component-name--modifier { }

Working with Web Components

Location

Web components live in src/assets/js/components/:

  • app.js - Main application shell
  • demo.js - Live code demo component (used by html live code blocks)
  • note.js - Note/alert component
  • tile.js - Card/tile component

Creating a Component

import { LitElement, html, css } from "lit";

export class MyComponent extends LitElement {
	static styles = css`
		:host {
			display: block;
		}
	`;

	render() {
		return html`<div>Hello, World!</div>`;
	}
}

customElements.define("my-component", MyComponent);

Using a Component

  1. Import in app.js or create new component file
  2. Use in templates:
<my-component></my-component>

Components are server-side rendered at build time via @lit-labs/eleventy-plugin-lit.

Adding External Feeds

External feed fetching is off by default (the Reading page renders with no aggregated items) so that ordinary builds and dev servers don't depend on network access to third-party feeds. To actually fetch feeds — e.g. while working on the Reading page — set FETCH_EXTERNAL_FEEDS=true:

FETCH_EXTERNAL_FEEDS=true npm start

To add RSS feeds to the "Reading" page:

  1. Edit feeds.json
{
	"feeds": [
		{
			"name": "Example Blog",
			"url": "https://example.com/feed.xml",
			"siteUrl": "https://example.com"
		}
	]
}
  1. Configure date filtering (optional)

In eleventy.config.js, you can limit how far back to aggregate posts using ISO 8601 duration strings:

eleventyConfig.addPlugin(FeedAggregatorPlugin, {
	configFile: "feeds.json",
	durationLimit: "P90D", // Last 90 days
});

Common duration examples:

  • P90D - 90 days
  • P1Y - 1 year
  • P1Y6M - 1 year and 6 months
  • P2W - 2 weeks
  • P6M - 6 months

Omit durationLimit to include all posts from the feeds.

  1. Development mode auto-reload

The feed configuration file is automatically watched in development mode. Changes to feeds.json will trigger a rebuild when running npm start.

  1. Rebuild the site
npm run build

Feeds are fetched at build time and cached in the static output.

Debugging

Common Issues

Hot reload not working

  • Check that you're running npm start (not npm run build)
  • Ensure no other process is using port 8080

Draft posts not showing

  • Drafts only show in development mode (npm start)
  • Check frontmatter has draft: true

Search not working

  • Run full build: npm run build
  • Ensure build:index completed successfully
  • Check public/pagefind/ directory exists

CSS not applying

  • Check CSS layer order
  • Ensure import in global.css
  • Clear browser cache

Build failing

  • Run npm run clean to clear build artifacts
  • Delete node_modules/ and run npm ci
  • Check Node.js version: node --version (should be 24.15.0+) and npm version: npm --version (should be 12+)

Verbose Output

For detailed build information:

DEBUG=Eleventy* npm start

Code Style

Formatting Rules

  • Indentation - Tabs (size 2)
  • Line width - 120 characters (200 for Markdown/SCSS)
  • Quotes - Consistent use of template literals
  • Semicolons - Required
  • Braces - Always required, even for single-line statements
// ✅ Good - always use braces
if (condition) {
	return null;
}

// ❌ Bad - no braces
if (condition) return null;

Enforcing Style

# Check formatting
npm run lint

# Fix formatting
npm run format

All code should pass npm run lint before committing.

Git Workflow

Branch Strategy

  • main - Production branch (auto-deploys)
  • Feature branches for development

Commit Messages

Use clear, descriptive commit messages:

git commit -m "feat: add dark mode toggle"
git commit -m "fix: correct search index generation"
git commit -m "docs: update development guide"

Prefixes:

  • feat: - New feature
  • fix: - Bug fix
  • docs: - Documentation
  • style: - Code style/formatting
  • refactor: - Code refactoring
  • test: - Tests
  • chore: - Maintenance tasks

Testing

Manual Testing

Before submitting changes:

  1. Local development - Test with npm start
  2. Production build - Test with npm run build && npm run dev
  3. Multiple browsers - Test in Chrome, Firefox, Safari
  4. Responsive design - Test on mobile and desktop viewports
  5. Dark mode - Test with system dark mode enabled
  6. Search functionality - Verify search works after rebuild

Automated Testing

The project has two test suites, both run in CI on every push and pull request:

Unit tests (tests/unit/) - Cover the custom Eleventy plugins in 11ty/ using the Node.js built-in test runner:

npm run test:unit

End-to-end tests (tests/e2e/) - Playwright browser tests covering the homepage, blog, feeds, reading page, dark mode, and page structure:

npm test        # Headless run (starts the dev server automatically)
npm run test:ui # Interactive Playwright UI mode

When changing a plugin in 11ty/, update its corresponding test in tests/unit/. When changing page structure or behavior, run npm test to catch regressions.

Performance Considerations

Build Time

  • Only rebuild search index when content changes
  • Clear cache with npm run clean if builds seem stale
  • npm start intentionally omits --incremental: the Lit SSR plugin (@lit-labs/eleventy-plugin-lit, mode: "worker") only imports component modules once per worker thread, so incremental rebuilds can silently keep serving stale component code after editing src/assets/js/components/. A full rebuild spawns a fresh worker every time, so component changes are always picked up without restarting the dev server.

Output Size

  • CSS is automatically minified via clean-css
  • Images are optimized via @11ty/eleventy-img
  • Minimal JavaScript (only web components)

Runtime Performance

  • Static HTML loads instantly
  • No JavaScript required for core functionality
  • Web components enhance progressively
  • Search runs entirely client-side

Resources

Getting Help