npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

kamado

v2.0.0-alpha.1

Published

Bake your HTML hard. The on-demand static site generator.

Readme

Kamado

npm version

kamado

Kamado is an extremely simple static site build tool. No hydration, no client-side runtime, no magic. No runtime needed, just the file system and raw HTML. Baked on demand. Thoroughly baked in a Kamado, that is what Kamado is.

Project Overview

Kamado is a static site build tool similar to 11ty, but aims for a simpler design. It is a tool for those who stick to the legacy, old-school ways of building.

The biggest feature of Kamado is that it requires absolutely no runtime. No client-side runtime (hydration) is needed. Because it generates pure static HTML, it achieves persistence and robustness. It generates HTML that will work just the same 10 years, or even 20 years from now.

Modern frameworks like Astro or Next.js require a runtime. Kamado does not depend on a runtime and generates pure static HTML. It is a tool for developers who prefer legacy approaches and do not want to depend on a runtime.

Key Features

No Runtime Required

The biggest feature of Kamado is that it requires absolutely no runtime. No client-side runtime (hydration) is needed. Only pure static HTML is generated. This ensures persistence and robustness. You won't be troubled by runtime version upgrades or security patches.

Use with esbuild/vite

Leave CSS and JavaScript to esbuild or vite, and Kamado will focus on managing HTML. This allows development that leverages the strengths of each tool.

On-Demand Build System

The development server builds only the necessary files when they are accessed. With the transpile-on-demand method, it works comfortably even on sites with 10,000 pages. A lean design that bakes only what is needed.

Large-Scale Site Support

Mapping management via a page tree allows for efficient builds even on large-scale sites.

Rich Logging and Parallel Builds

Kamado adopts parallel build processing. What is happening during the build is clearly output to the console. You can check the build status of each file in real-time, and progress is obvious at a glance. Parallel processing also improves build speed.

Development Server

Hono-based Lightweight Server

Fire up the Kamado with Hono 🔥

Transpile-on-Demand Method

If a server request matches a destination path, it builds starting from the requested file in a chain reaction. There is no need to watch dependency files; only the necessary files are automatically built.

No File Watching

It doesn't use Chokidar and doesn't do live reload. During development, only server requests from browser reloads trigger builds.

Mapping Management via Page Tree

The page tree holds the source file paths and destination paths. Since mapping is managed at this point, if a server request matches a destination path, only the source file needs to be built.

Basic Usage

Installation

npm install kamado
# or
yarn add kamado

Configuration File

Create kamado.config.ts in the project root:

import path from 'node:path';

import { defineConfig } from 'kamado/config';
import { createPageCompiler } from '@kamado-io/page-compiler';
import { createScriptCompiler } from '@kamado-io/script-compiler';
import { createStyleCompiler } from '@kamado-io/style-compiler';

export default defineConfig({
	dir: {
		root: import.meta.dirname,
		input: path.resolve(import.meta.dirname, '__assets', 'htdocs'),
		output: path.resolve(import.meta.dirname, 'htdocs'),
	},
	devServer: {
		open: true,
		port: 8000,
	},
	compilers: (def) => [
		def(createPageCompiler(), {
			files: '**/*.{html,pug}',
			outputExtension: '.html',
			globalData: {
				dir: path.resolve(import.meta.dirname, '__assets', '_libs', 'data'),
			},
			layouts: {
				dir: path.resolve(import.meta.dirname, '__assets', '_libs', 'layouts'),
			},
			// Transform pipeline (optional, defaults to createDefaultPageTransforms())
			// See @kamado-io/page-compiler documentation for customization
		}),
		def(createStyleCompiler(), {
			files: '**/*.{css,scss,sass}',
			ignore: '**/*.{scss,sass}',
			outputExtension: '.css',
			alias: {
				'@': path.resolve(import.meta.dirname, '__assets', '_libs'),
			},
		}),
		def(createScriptCompiler(), {
			files: '**/*.{js,ts,jsx,tsx,mjs,cjs}',
			outputExtension: '.js',
			minifier: true,
			alias: {
				'@': path.resolve(import.meta.dirname, '__assets', '_libs'),
			},
		}),
	],
	async onBeforeBuild(context) {
		// Process before build
		// context.mode is available: 'build' or 'serve'
	},
	async onAfterBuild(context) {
		// Process after build
		// context.mode is available: 'build' or 'serve'
	},
});

Configuration Description

Directory Settings

  • dir.root: Project root directory
  • dir.input: Source file directory
  • dir.output: Output directory

Development Server Settings

  • devServer.port: Server port number (default: 3000)
  • devServer.host: Server host name (default: localhost)
  • devServer.open: Whether to automatically open the browser on startup (default: false)
  • devServer.startPath: Custom path to open in the browser when starting the server (optional, e.g., '__tmpl/')
  • devServer.transforms: Array of response transformation functions that modify responses during development (optional, see Response Transform API)

Compiler Settings

The compilers option uses a callback form for type-safe compiler configuration. The callback receives a def helper function that binds compiler factories to their options. Each def(factory(), options) call returns a compiler with metadata. The compiler options include:

  • files (optional): Glob pattern for files to compile. Patterns are resolved relative to dir.input. Default values are provided by each compiler (see below).
  • ignore (optional): Glob pattern for files to exclude from compilation. Patterns are resolved relative to dir.input. For example, '**/*.scss' will ignore all .scss files in the input directory and subdirectories.
  • outputExtension (optional): Output file extension (e.g., .html, .css, .js, .php). Default values are provided by each compiler (see below).
  • Other compiler-specific options (see each compiler's documentation below)

The order of entries in the returned array determines the processing order.

createPageCompiler
  • files (optional): Glob pattern for files to compile. Patterns are resolved relative to dir.input (default: '**/*.html')
  • ignore (optional): Glob pattern for files to exclude from compilation. Patterns are resolved relative to dir.input. For example, '**/*.tmp' will ignore all .tmp files.
  • outputExtension (optional): Output file extension (default: '.html')
  • globalData.dir: Global data file directory
  • globalData.data: Additional global data
  • layouts.dir: Layout file directory
  • compileHooks: Compilation hooks for customizing compile process (required for Pug templates)
  • transforms: Array of transform functions to apply to compiled HTML. If omitted, uses createDefaultPageTransforms(). See @kamado-io/page-compiler for details on the Transform Pipeline API.

Note: page-compiler is a generic container compiler and does not compile Pug templates by default. To use Pug templates, install @kamado-io/pug-compiler and configure compileHooks. See @kamado-io/pug-compiler README for details.

Example: To compile .pug files to .html:

def(createPageCompiler(), {
	files: '**/*.pug',
	outputExtension: '.html',
	compileHooks: {
		main: {
			compiler: compilePug(),
		},
	},
});
createStyleCompiler
  • files (optional): Glob pattern for files to compile. Patterns are resolved relative to dir.input (default: '**/*.css')
  • ignore (optional): Glob pattern for files to exclude from compilation. Patterns are resolved relative to dir.input. For example, '**/*.{scss,sass}' will ignore all .scss and .sass files.
  • outputExtension (optional): Output file extension (default: '.css')
  • alias: Path alias map (used in PostCSS @import)
  • banner: Banner configuration (can specify CreateBanner function or string)

Example: To compile .scss files to .css while ignoring source files:

def(createStyleCompiler(), {
	files: '**/*.{css,scss,sass}',
	ignore: '**/*.{scss,sass}',
	outputExtension: '.css',
	alias: {
		'@': path.resolve(import.meta.dirname, '__assets', '_libs'),
	},
});
createScriptCompiler
  • files (optional): Glob pattern for files to compile. Patterns are resolved relative to dir.input (default: '**/*.{js,ts,jsx,tsx,mjs,cjs}')
  • ignore (optional): Glob pattern for files to exclude from compilation. Patterns are resolved relative to dir.input. For example, '**/*.test.ts' will ignore all test files.
  • outputExtension (optional): Output file extension (default: '.js')
  • alias: Path alias map (esbuild alias)
  • minifier: Whether to enable minification
  • banner: Banner configuration (can specify CreateBanner function or string)

Example: To compile TypeScript files to JavaScript:

def(createScriptCompiler(), {
	files: '**/*.{js,ts,jsx,tsx}',
	outputExtension: '.js',
	minifier: true,
	alias: {
		'@': path.resolve(import.meta.dirname, '__assets', '_libs'),
	},
});

Page List Configuration

The pageList option allows you to customize the page list used for navigation, breadcrumbs, and other features that require a list of pages.

import { defineConfig } from 'kamado/config';
import { urlToFile, getFile } from 'kamado/files';

export default defineConfig({
	// ... other config
	pageList: async (pageAssetFiles, config) => {
		// Filter pages (e.g., exclude drafts)
		const filtered = pageAssetFiles.filter((page) => !page.url.includes('/drafts/'));

		// Add external pages with custom metadata
		const externalPage = {
			...urlToFile('/external-page/', {
				inputDir: config.dir.input,
				outputDir: config.dir.output,
				outputExtension: '.html',
			}),
			metaData: { title: 'External Page Title' },
		};

		return [...filtered, externalPage];
	},
});

The function receives:

  • pageAssetFiles: Array of all page files found in the file system
  • config: The full configuration object

Returns an array of PageData objects (extends CompilableFile with optional metaData).

Note about metaData and titles:

  • During individual page compilation, metaData is automatically populated from frontmatter
  • However, at pageList hook time (globalData collection), metaData is NOT yet populated
  • If you need titles for breadcrumbs/navigation, you must explicitly set metaData.title in the pageList hook
  • Without explicit metaData.title, breadcrumbs and navigation will show __NO_TITLE__

Hook Functions

  • onBeforeBuild: Function executed before build. Receives Context (which extends Config with mode: 'build' | 'serve')
  • onAfterBuild: Function executed after build. Receives Context (which extends Config with mode: 'build' | 'serve')

Response Transform API

The Response Transform API allows you to modify response content during development server mode. This is useful for injecting scripts, implementing pseudo-SSI, adding meta tags, or any other response transformation needs.

Important Distinction:

Both use the same Transform interface (kamado/config), but differ in scope and application:

  • devServer.transforms: Applied to ALL responses during development server mode only (kamado server). Middleware-style transforms that can process any file type (HTML, CSS, JS, images, etc.). The filter option (include/exclude) is respected here. Does not run during builds.
  • createPageCompiler()({ transforms }): Applied to compiled HTML pages in both build and serve modes. Transform pipeline for HTML processing only. The filter option is ignored (all HTML pages are processed). See @kamado-io/page-compiler for details.

You can reuse the same transform functions (like manipulateDOM(), prettier(), or custom transforms) in both places.

Key Features:

  • Development-only: Transforms only apply in serve mode, not during builds
  • Flexible filtering: Filter by glob patterns (include/exclude)
  • Error resilient: Errors in transform functions don't break the server
  • Async support: Supports both synchronous and asynchronous transform functions
  • Chainable: Multiple transforms are applied in array order

Configuration:

import path from 'node:path';
import fs from 'node:fs/promises';

import { defineConfig } from 'kamado/config';

export default defineConfig({
	devServer: {
		port: 3000,
		transforms: [
			// Example 1: Inject development script into HTML
			{
				name: 'inject-dev-script',
				filter: {
					include: '**/*.html',
				},
				transform: (content) => {
					if (typeof content !== 'string') {
						const decoder = new TextDecoder('utf-8');
						content = decoder.decode(content);
					}
					return content.replace(
						'</body>',
						'<script src="/__dev-tools.js"></script></body>',
					);
				},
			},

			// Example 2: Implement pseudo-SSI (Server Side Includes)
			{
				name: 'pseudo-ssi',
				filter: {
					include: '**/*.html',
				},
				transform: async (content, ctx) => {
					if (typeof content !== 'string') {
						const decoder = new TextDecoder('utf-8');
						content = decoder.decode(content);
					}

					// Process <!--#include virtual="/path/to/file.html" -->
					const includeRegex = /<!--#include virtual="([^"]+)" -->/g;
					let result = content;

					for (const match of content.matchAll(includeRegex)) {
						const includePath = match[1];
						const filePath = path.resolve(
							ctx.context.dir.output,
							includePath.replace(/^\//, ''),
						);

						try {
							const includeContent = await fs.readFile(filePath, 'utf-8');
							result = result.replace(match[0], includeContent);
						} catch (error) {
							console.warn(`Failed to include ${includePath}:`, error);
						}
					}

					return result;
				},
			},

			// Example 3: Add source comment to CSS files
			{
				name: 'css-source-comment',
				filter: {
					include: '**/*.css',
				},
				transform: (content, ctx) => {
					if (typeof content !== 'string') {
						const decoder = new TextDecoder('utf-8');
						content = decoder.decode(content);
					}
					const source = ctx.inputPath || ctx.outputPath;
					return `/* Generated from: ${source} */\n${content}`;
				},
			},
		],
	},
});

Transform Interface:

interface Transform<M extends MetaData> {
	readonly name: string; // Transform name for debugging
	readonly filter?: {
		readonly include?: string | readonly string[]; // Glob patterns to include
		readonly exclude?: string | readonly string[]; // Glob patterns to exclude
	};
	readonly transform: (
		content: string | ArrayBuffer,
		context: TransformContext<M>,
	) => Promise<string | ArrayBuffer> | string | ArrayBuffer;
}

interface TransformContext<M extends MetaData> {
	readonly path: string; // Request path
	readonly filePath: string; // File path (alias for path)
	readonly inputPath?: string; // Original input file path (if available)
	readonly outputPath: string; // Output file path
	readonly outputDir: string; // Output directory path
	readonly isServe: boolean; // Always true in dev server
	readonly context: Context<M>; // Full execution context
	readonly compile: CompileFunction; // Function to compile other files
}

Filter Options:

  • include: Glob pattern(s) to match request paths (e.g., '**/*.html', ['**/*.css', '**/*.js'])
  • exclude: Glob pattern(s) to exclude (e.g., '**/_*.html' to skip files starting with _)

Important Notes:

  • Transform functions receive either string or ArrayBuffer. For text-based transformations, decode ArrayBuffer using TextDecoder:
    if (typeof content !== 'string') {
    	const decoder = new TextDecoder('utf-8');
    	content = decoder.decode(content);
    }
  • Static files (non-compiled files) are typically passed as ArrayBuffer, so always decode them if you need to process as text
  • Errors in transform functions are logged but don't break the server (original content is returned)
  • Transforms are executed in array order
  • Only applied in development server mode (kamado server), not during builds

CLI Commands

Build Entire Site

kamado build

Build Specific Files Only

kamado build "path/to/file.pug" # Build a specific file
kamado build "path/to/*.css" # Build only CSS files
kamado build "path/to/*.ts" # Build only TypeScript files

Start Development Server

kamado server

When the development server starts, pages accessed via the browser are built on demand. If there is a request, it bakes it on the spot and returns it.

CLI Options

The following options are available for all commands:

| Option | Short | Description | | ----------------- | ----- | ------------------------------------------------------------------------------------------------------------------ | | --config <path> | -c | Path to a specific config file. If not specified, Kamado searches for kamado.config.js, kamado.config.ts, etc. | | --verbose | | Enable verbose logging |

Examples

# Use a specific config file
kamado build --config ./custom.config.ts
kamado server -c ./dev.config.js

# Enable verbose logging during build
kamado build --verbose

Type Safety & Generics

Kamado's core types accept a generic type parameter M extends MetaData for type-safe custom metadata. This section explains how to use it.

Default Generics

Most user-facing types (Config, Context, UserConfig, Transform, TransformContext, PageData, GlobalData) have a default of = MetaData. If you don't need custom metadata, you can use the types without a type argument:

import type { Config, Transform, PageData } from 'kamado/config';

// No type argument needed — defaults to MetaData
const config: Config = {
	/* ... */
};
const transform: Transform = {
	/* ... */
};

Custom Metadata

The base MetaData interface is an empty interface ({}). Any interface or type satisfies the extends MetaData constraint. You can define custom metadata properties via generics.

To propagate custom metadata types throughout your project, pass a type argument to defineConfig:

interface MyMeta {
	title: string;
	description?: string;
	draft?: boolean;
}

export default defineConfig<MyMeta>({
	pageList: async (pageAssetFiles, config) => {
		// pageAssetFiles are CompilableFile[], return PageData<MyMeta>[]
		return pageAssetFiles.map((file) => ({
			...file,
			metaData: { title: 'Default Title' },
		}));
	},
	async onBeforeBuild(context) {
		// context is Context<MyMeta> — fully typed
	},
});

Note: Config<M> is invariant in M.

Due to TypeScript's type system, Config<M> is invariant in its type parameter M. This means Config<PageMetaData> cannot be assigned to Config<MetaData> (or vice versa), because M appears in both covariant positions (return types like PageData<M>[]) and contravariant positions (callback parameters like config: Config<M>).

If you write a helper function that receives a Config, make it generic:

// ✅ Good — works with any metadata type
function helper<M extends MetaData>(config: Config<M>) { ... }

// ❌ Bad — Config<PageMetaData> is NOT assignable to Config<MetaData>
function helper(config: Config<MetaData>) { ... }

The def Callback

The compilers option uses a callback form: compilers: (def) => [...]. The def parameter is a CompilerDefine<M> function that binds a compiler factory to its options. This exists so TypeScript can automatically infer each compiler's option types — you never need to write type arguments manually:

compilers: (def) => [
	// TypeScript infers the options type from createPageCompiler's return type
	def(createPageCompiler(), {
		files: '**/*.html',
		outputExtension: '.html',
	}),
];

M Propagation Chain

The type parameter M flows through the system:

defineConfig<M>() → Config<M> → Context<M> → TransformContext<M>
                                            → PageData<M>
                                            → CompileData<M> → NavNode<M>

This ensures that custom metadata types are consistent across configuration, compilation, and template data.