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 🙏

© 2025 – Pkg Stats / Ryan Hefner

clovie

v0.1.29

Published

Vintage web dev tooling with modern quality of life

Downloads

68

Readme

Clovie - Vintage Web Dev Tooling with Modern QoL

"The Hollow Knight of Web Dev" - Simple but deep, easy to start but room to grow.

A powerful Node.js-based static site generator and full-stack web framework that bridges the gap between simple static sites and complex web applications. Built on the @brickworks/engine service architecture for maximum flexibility and maintainability.

🚀 Quick Start

# Create a new project
npx clovie create my-site

# Start development
cd my-site
npm install
npm run dev

✨ Key Features

  • 🎯 Dual Mode: Static site generation OR full Express server applications
  • 🔄 Zero Config: Smart auto-detection of project structure
  • ⚡ Fast Builds: Incremental builds with intelligent caching
  • 🎨 Template Agnostic: Handlebars, Nunjucks, Pug, Mustache, or custom engines
  • 📦 Asset Pipeline: SCSS compilation, JavaScript bundling with esbuild
  • 🔄 Live Reload: WebSocket-based live reload during development
  • 🗄️ Database Ready: SQLite integration for server mode applications
  • 🛣️ Dynamic Routing: Data-driven page generation and API endpoints
  • 🔧 Service Architecture: Modular, extensible service-oriented design

🏗️ Architecture Overview

Clovie uses a service-oriented architecture built on @brickworks/engine. All functionality is provided by services that extend ServiceProvider, orchestrated through dependency injection with reactive state management.

Core Services

  • 🗂️ File - File system operations with intelligent watching
  • ⚙️ Compile - Asset compilation with progress tracking
  • 📝 Configurator - Configuration management with hot-reloading
  • 🏃 Run - Build orchestration and task execution
  • 🌐 Server - Express server with kernel-based request handling
  • 🔄 LiveReload - Development live-reload with WebSocket
  • 🛣️ Router - Route processing for both static and dynamic content
  • 💾 Cache - Smart caching for incremental builds

Operating Modes

🗂️ Static Mode (type: 'static'):

  • Generates optimized static HTML files
  • Perfect for blogs, documentation, marketing sites
  • Uses development server only for live reload
  • Deployable to any static hosting (Netlify, Vercel, GitHub Pages)

🌐 Server Mode (type: 'server'):

  • Full Express.js web application
  • API endpoints with state management
  • Server-side rendering with dynamic routes
  • Database integration and real-time features
  • Perfect for web apps, dashboards, APIs

Project Structure

clovie/
├── __tests__/              # Test files
├── bin/                    # CLI executable
│   └── cli.js             # Command line interface
├── config/                # Configuration files  
│   └── clovie.config.js   # Default configuration
├── lib/                   # Source code - Service-based architecture
│   ├── createClovie.js    # Engine factory function
│   ├── Build.js           # Build service
│   ├── Cache.js           # Caching service
│   ├── Compiler.js        # Template compilation service
│   ├── File.js            # File system service
│   ├── Routes.js          # Routing service  
│   ├── Server.js          # Express server service
│   ├── Views.js           # View processing service
│   └── utils/             # Utility functions
│       ├── clean.js       # Directory cleaning
│       ├── discover.js    # Auto-discovery
│       └── liveReloadScript.js # Live reload
├── templates/             # Project templates
└── examples/              # Configuration examples

Service Architecture

Each service in Clovie extends ServiceProvider and defines:

  • Static manifest: Name, namespace, version, and dependencies
  • initialize(): Setup phase with access to config and context
  • actions(): Methods exposed through the engine context

Service Dependencies

Services declare their dependencies in their manifest:

static manifest = {
  name: 'Clovie Build',
  namespace: 'build', 
  version: '1.0.0',
  dependencies: [Cache, Routes]  // Initialized first
};

State Management

The engine provides two state stores:

  • state: Reactive store for build-time data (from config.data)
  • stable: Persistent storage (cache, build stats, etc.)

Services access these via useContext():

actions(useContext) {
  const { state, stable, file, compiler } = useContext();
  // Service methods can access other services and state
}

Core Features

  • Template Engine Agnostic: Support for Handlebars, Nunjucks, Pug, Mustache, or custom engines
  • Asset Processing: JavaScript bundling with esbuild, SCSS compilation, static asset copying
  • Development Server: Live reload with Express and file watching
  • Dynamic Routing: Powerful route system for both static and server-side page generation
  • Server-Side Rendering: Full Express applications with dynamic routes and API endpoints
  • Incremental Builds: Smart caching for faster rebuilds
  • Auto-Discovery: Intelligent project structure detection

📦 Installation & Usage

Creating New Projects

Quick Start with Templates

# Static site (blogs, docs, marketing)
npx clovie create my-blog --template static

# Server application (APIs, web apps)
npx clovie create my-app --template server

# Default (auto-detected)
npx clovie create my-site

Manual Installation

# Install in existing project
npm install --save-dev clovie

# Or globally
npm install -g clovie

Development Workflow

🗂️ Static Site Development

# Start development server with live reload
npm run dev
# or
clovie dev

# Build optimized static files
npm run build  
# or
clovie build

# Preview production build
clovie serve --static

🌐 Server Application Development

# Start development server with live reload
npm run dev
# or  
clovie dev

# Start production server
npm start
# or
clovie serve

# Build assets only (server handles routing)
clovie build

⚙️ Configuration

Zero Config Start

Clovie uses smart auto-detection - just create your files and start:

// clovie.config.js (minimal)
export default {
  data: {
    title: 'My Site',
    description: 'Built with Clovie'
  }
};

Automatically detects:

  • views/ → HTML templates
  • scripts/main.js → JavaScript entry
  • styles/main.scss → SCSS entry
  • assets/ → Static assets
  • partials/ → Reusable components

🗂️ Static Site Configuration

Perfect for blogs, documentation, and marketing sites:

export default {
  type: 'static', // Generate static HTML files
  
  // Data available in all templates
  data: {
    site: {
      title: 'My Blog',
      url: 'https://myblog.com',
      description: 'A fast static blog'
    },
    nav: [
      { title: 'Home', url: '/' },
      { title: 'About', url: '/about' },
      { title: 'Blog', url: '/blog' }
    ]
  },
  
  // Template engine (auto-detected from usage)
  renderEngine: 'handlebars', // or nunjucks, pug, custom function
  
  // Dynamic pages from data
  routes: [{
    name: 'Blog Posts',
    path: '/posts/:slug',
    template: 'post.html',
    repeat: (data) => data.posts, // Generate page for each post
    data: (globalData, post) => ({
      ...globalData,
      post,
      title: `${post.title} - ${globalData.site.title}`
    })
  }],
  
  // Build optimizations
  minify: true,
  generateSitemap: true
};

🌐 Server Application Configuration

Full-stack web applications with APIs and dynamic content:

import express from 'express';

export default {
  type: 'server', // Express application mode
  port: 3000,
  
  // Shared data and configuration
  data: {
    app: {
      name: 'My App',
      version: '1.0.0'
    }
  },
  
  // Database configuration (optional)
  dbPath: './data/app.db',
  
  // Express middleware (auto-selects Express adapter)
  middleware: [
    express.json(),
    express.urlencoded({ extended: true }),
    
    // Authentication middleware for protected routes
    (req, res, next) => {
      if (req.url.startsWith('/api/protected/')) {
        const token = req.headers.authorization?.replace('Bearer ', '');
        if (!token) {
          return res.status(401).json({ error: 'Authentication required' });
        }
        // Verify token and attach user to request
        req.user = verifyJWT(token);
      }
      next();
    }
  ],
  
  // API endpoints
  api: [
    {
      path: '/api/users',
      method: 'GET',
      action: async (state, event) => {
        const users = await state.db.query('SELECT * FROM users');
        return { users, total: users.length };
      }
    },
    {
      path: '/api/users',
      method: 'POST', 
      action: async (state, event) => {
        const { name, email } = event.body;
        const user = await state.db.insert('users', { name, email });
        return { success: true, user };
      }
    },
    {
      path: '/api/users/:id',
      method: 'GET',
      action: async (state, event) => {
        const user = await state.db.findById('users', event.params.id);
        return user ? { user } : { error: 'User not found', status: 404 };
      }
    }
  ],
  
  // Server-rendered routes
  routes: [
    {
      name: 'User Dashboard',
      path: '/dashboard/:userId',
      template: 'dashboard.html',
      data: async (state, params) => {
        const user = await state.db.findById('users', params.userId);
        const stats = await getUserStats(params.userId);
        return { user, stats, title: `Dashboard - ${user.name}` };
      }
    }
  ]
};

🚀 Advanced Features

🔐 Middleware & Authentication

Clovie supports Express middleware for server applications. When you configure middleware, Clovie automatically uses the Express adapter for full compatibility.

Common Authentication Pattern:

export default {
  type: 'server',
  
  middleware: [
    // Request logging
    (req, res, next) => {
      console.log(`${req.method} ${req.url}`);
      next();
    },
    
    // Selective authentication
    (req, res, next) => {
      // Public routes
      const publicPaths = ['/api/login', '/api/health'];
      if (publicPaths.some(path => req.url.startsWith(path))) {
        return next();
      }
      
      // Protect /api/protected/* routes
      if (req.url.startsWith('/api/protected/')) {
        const token = req.headers.authorization?.replace('Bearer ', '');
        if (!token) {
          return res.status(401).json({ error: 'Token required' });
        }
        try {
          req.user = verifyJWT(token);
          next();
        } catch (error) {
          res.status(401).json({ error: 'Invalid token' });
        }
      } else {
        next();
      }
    }
  ]
};

Test Authentication:

# Public endpoint
curl http://localhost:3000/api/health

# Protected endpoint (fails)
curl http://localhost:3000/api/protected/data

# Protected endpoint (works)  
curl -H "Authorization: Bearer your-token" http://localhost:3000/api/protected/data

📊 Database Integration (Server Mode)

Clovie includes built-in SQLite database support for server applications:

export default {
  type: 'server',
  dbPath: './data/app.db', // SQLite database path
  
  // Database is available in API actions and routes
  api: [{
    path: '/api/posts',
    method: 'GET',
    action: async (state, event) => {
      // Access database through state.db
      const posts = await state.db.query('SELECT * FROM posts ORDER BY created_at DESC');
      return { posts };
    }
  }]
};

🔄 Async Data Loading

Load data dynamically at build time or runtime:

export default {
  // Static: Load data at build time
  data: async () => {
    const posts = await fetch('https://api.example.com/posts').then(r => r.json());
    const authors = await loadAuthorsFromFile('./data/authors.json');
    
    return {
      site: { title: 'My Blog' },
      posts,
      authors,
      buildTime: new Date().toISOString()
    };
  },
  
  // Server: Load data per request
  routes: [{
    path: '/posts/:slug',
    template: 'post.html',
    data: async (state, params) => {
      const post = await fetchPostBySlug(params.slug);
      const comments = await fetchComments(post.id);
      return { post, comments };
    }
  }]
};

🎯 Dynamic Route Generation

Generate pages from data with powerful routing:

export default {
  data: {
    posts: [
      { id: 1, title: 'Getting Started', slug: 'getting-started', category: 'tutorial' },
      { id: 2, title: 'Advanced Usage', slug: 'advanced-usage', category: 'guide' }
    ],
    categories: ['tutorial', 'guide', 'news']
  },
  
  routes: [
    // Individual post pages
    {
      name: 'Blog Posts',
      path: '/posts/:slug',
      template: 'post.html',
      repeat: (data) => data.posts,
      data: (globalData, post) => ({ ...globalData, post })
    },
    
    // Category pages
    {
      name: 'Category Pages', 
      path: '/category/:category',
      template: 'category.html',
      repeat: (data) => data.categories,
      data: (globalData, category) => ({
        ...globalData,
        category,
        posts: globalData.posts.filter(p => p.category === category)
      })
    }
  ]
};

🎨 Template Engine Support

Use any template engine or create custom ones:

// Handlebars
import Handlebars from 'handlebars';
export default {
  renderEngine: (template, data) => {
    const compiled = Handlebars.compile(template);
    return compiled(data);
  }
};

// Nunjucks
import nunjucks from 'nunjucks';
export default {
  renderEngine: (template, data) => {
    return nunjucks.renderString(template, data);
  }
};

// Custom engine
export default {
  renderEngine: (template, data) => {
    return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
      return data[key] || match;
    });
  }
};

🛠️ CLI Commands

Clovie includes powerful CLI tools for development and deployment:

# Project creation
clovie create <name>                    # Creates static site (default)
clovie create <name> --template server  # Creates server application

# Development
clovie dev              # Start development server with live reload
clovie watch            # Alias for dev

# Building  
clovie build            # Build for production
clovie serve            # Start production server

# Utilities
clovie kill --port 3000         # Kill process on port 3000
clovie kill --common            # Kill processes on common dev ports
clovie kill --check             # Check which ports are in use

📁 Project Structure

When you create a new project, you get a clean, organized structure:

my-site/
├── clovie.config.js    # Configuration file
├── package.json        # Dependencies and scripts
├── views/              # HTML templates
│   ├── index.html     # Homepage
│   └── about.html     # Additional pages
├── partials/           # Reusable components (optional)
│   ├── header.html    # Site header
│   └── footer.html    # Site footer  
├── scripts/            # JavaScript
│   └── main.js        # Main JS entry point
├── styles/             # SCSS/CSS
│   └── main.scss      # Main stylesheet
├── assets/             # Static files
│   └── images/        # Images, fonts, etc.
└── dist/               # Build output (generated)

🚀 Performance Features

  • ⚡ Incremental Builds: Only rebuilds changed files
  • 📦 Smart Caching: Content-based cache invalidation
  • 🔄 Live Reload: WebSocket-based for instant updates
  • 📊 Progress Tracking: Real-time build progress
  • 🗜️ Asset Optimization: Minification and optimization
  • 🌐 Static + Server: Best of both worlds

💡 Best Practices & Tips

🎯 Development Best Practices

Static Sites:

  • Use partials/ for reusable components (header, footer, nav)
  • Organize data in the config file or external JSON files
  • Use semantic HTML and meaningful route paths for SEO
  • Test builds regularly with npm run build

Server Applications:

  • Validate input data in API actions
  • Use middleware for authentication and request parsing
  • Handle errors gracefully in API endpoints
  • Implement proper database migrations and seeding

🔧 Configuration Tips

export default {
  // Environment-specific config
  ...(process.env.NODE_ENV === 'production' && {
    baseUrl: 'https://mysite.com',
    minify: true
  }),
  
  // Async data with error handling
  data: async () => {
    try {
      const posts = await fetchPosts();
      return { posts, lastUpdated: Date.now() };
    } catch (error) {
      console.warn('Failed to fetch posts:', error);
      return { posts: [], lastUpdated: Date.now() };
    }
  }
};

📊 Performance Optimization

  • Use incremental builds: Clovie's caching handles this automatically
  • Optimize images: Place optimized images in assets/
  • Minimize JavaScript: Use esbuild's built-in minification
  • Cache API responses: Implement caching in server mode API actions
  • Static generation: Use static mode for content that doesn't change often

🔄 Development Workflow

# Recommended development flow
npm run dev          # Start with live reload
# Edit files and see changes instantly
npm run build        # Test production build
npm run serve        # Test production serving (server mode)

🛠️ Development & Contributing

# Clone repository
git clone https://github.com/adrianjonmiller/clovie.git
cd clovie

# Install dependencies
npm install

# Run tests  
npm test
npm run test:watch    # Watch mode
npm run test:coverage # Coverage report

# Build package
npm run build

# Test with example projects
npm run dev           # Uses __dev__ example

🐛 Troubleshooting

Common Issues

Port Already in Use

clovie kill --port 3000     # Kill specific port
clovie kill --common        # Kill common dev ports  
clovie kill --check         # Check port status

Build Errors

  • Check that all file paths in config exist
  • Verify template syntax matches your render engine
  • Ensure async data functions handle errors properly
  • Check console output for detailed error messages

Template Not Found

  • Verify the views directory path in your config
  • Check that template files have correct extensions
  • Ensure partial files are in the partials directory

Database Connection Issues (Server Mode)

  • Check that dbPath in config points to a writable directory
  • Ensure SQLite is properly installed
  • Verify database initialization in your API actions

Live Reload Not Working

  • Check that you're in development mode (npm run dev)
  • Verify WebSocket connection in browser dev tools
  • Try refreshing the page manually

Getting Help

📄 License

MIT - see LICENSE file for details.


Built with ❤️ by Adrian Miller

Clovie: Vintage web dev tooling with modern quality of life.