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

@trebor/buildhtml

v1.0.6

Published

Zero-dependency, ultra-fast HTML builder for server-side rendering (SSR).

Readme

@trebor/buildhtml

High-performance, server-side rendering (SSR) library for Node.js. "Build HTML at lightning speed with reactive state management."


Overview

BuildHTML is a lightweight SSR library for Node.js featuring object pooling, reactive state management, and CSS-in-JS capabilities. Build HTML on the server with minimal memory usage and blazing-fast performance.

  • Zero dependencies – Only Node.js required.
  • High Performance – Object pooling and LRU caching (1-5ms render time).
  • Reactive State – Built-in state management with automatic UI updates.
  • CSS-in-JS – Scoped and global styling with automatic CSS generation.
  • Security – XSS protection, CSS sanitization, and CSP nonce support.
  • Production Ready – HTML minification, compression, and metrics.
  • JSON Export – Save/restore pages with optional obfuscation.

Installation

npm install @trebor/buildhtml

Quick Start

const { Document } = require('@trebor/buildhtml');

// Create a document
const doc = new Document();
doc.title('Counter App');

// Add global state
doc.state('count', 0);

// Create elements (automatically attached!)
const display = doc.create('h1');
display.bind('count', (val) => `Count: ${val}`);

const button = doc.create('button');
button.text('Increment');
button.on('click', () => { State.count++; });

// Render HTML
const html = doc.render();
console.log(html);

Key Feature: Elements created with doc.create() are automatically attached to the document. No manual attachment needed!


Features

Core Features

  • Automatic Element Attachmentdoc.create('div') automatically adds to document
  • Reactive Statedoc.state() + element.bind() for automatic UI updates
  • Event Handling.on(event, fn) with automatic serialization
  • CSS-in-JS.css({ color: 'red' }) with automatic scoping
  • Computed Values.computed(fn) for derived content
  • JSON Export/Importdoc.toJSON() and Document.fromJSON(json)

Performance Features

  • Object Pooling – Reuses elements across renders
  • LRU Caching – Cache rendered HTML for static pages
  • In-Flight Deduplication – Concurrent requests share one render
  • Minification – Automatic in production mode
  • Metrics – Optional performance tracking

API Guide

Document

Create with new Document(options).

Methods

| Method | Description | |--------|-------------| | title(t) | Set page title (auto-escaped) | | state(key, value) | Set global reactive state | | addMeta(obj) | Add meta tag: { name: 'description', content: '...' } | | addLink(href) | Add stylesheet link | | addStyle(css) | Add inline CSS to <head> | | addScript(src) | Add external script | | globalStyle(selector, rules) | Add global CSS rule | | sharedClass(name, rules) | Define reusable class | | create(tag) | Create element (auto-attached to document) | | child(tag) | Alias for create(tag) | | useFragment(fn) | Add multiple elements via function | | oncreate(fn) | Run function on page load | | toJSON() | Export document structure as JSON | | render() | Return full HTML string | | renderJSON(opts?) | Render with embedded JSON | | save(path) | Save rendered HTML to file |

renderJSON Options

// Default (no JSON)
doc.renderJSON()
// → window.__SCULPTOR_DATA__ = {...}

// With obfuscation (50% smaller!)
doc.renderJSON({ obfuscate: true })
// → window.__SCULPTOR_DATA__ = JSON.parse(_decrypt("..."))

// Custom variable name
doc.renderJSON('MY_DATA')
// → window.MY_DATA = {...}

// Both custom name and obfuscation
doc.renderJSON('MY_DATA', { obfuscate: true })
// → window.MY_DATA = JSON.parse(_decrypt("..."))

// Or use options object
doc.renderJSON({ obfuscate: true, varName: 'MY_DATA' })

Static Methods

| Method | Description | |--------|-------------| | Document.fromJSON(json) | Rebuild document from JSON |

Constructor Options

new Document({
  cache: true,        // Enable response caching
  cacheKey: 'home',   // Cache key for this document
  nonce: 'abc123'     // CSP nonce for inline scripts/styles
})

Element

Created with doc.create(tag). All methods return this for chaining.

Methods

| Method | Description | |--------|-------------| | create(tag) | Create child element (auto-attached to parent) | | child(tag) | Alias for create(tag) | | id(v?) | Set id attribute (auto-generated if omitted) | | attr(key, value) | Set attribute | | text(content) | Append escaped text | | append(child) | Append element or text | | appendUnsafe(html) | Append raw HTML (use carefully!) | | css(styles) | Add scoped styles: { color: 'red' } | | uniqueClass(rules) | Add unique class with styles | | state(value) | Set initial state for hydration | | bind(stateKey, fn?) | Bind to global state | | computed(fn) | Compute content from state | | on(event, fn) | Attach event handler |

Examples

// Basic element
const div = doc.create('div')
  .attr('class', 'container')
  .text('Hello World');

// Nested elements (auto-attached to parent)
const container = doc.create('div');
container.create('h1').text('Title');
container.create('p').text('Content');

// CSS-in-JS
const box = doc.create('div').css({
  padding: '20px',
  backgroundColor: '#f0f0f0',
  borderRadius: '8px'
});

// State binding
doc.state('username', 'Alice');
const greeting = doc.create('div');
greeting.bind('username', (name) => `Hello, ${name}!`);

// Event handling
const button = doc.create('button').text('Click me');
button.on('click', () => {
  State.count++;
  console.log('Clicked!');
});

// Computed values
const total = doc.create('div');
total.computed((state) => {
  return state.price * state.quantity;
});

Global State & Reactivity

Sculptor provides a reactive state system:

// Set global state
doc.state('count', 0);
doc.state('user', { name: 'Alice', age: 30 });

// Bind elements to state
const display = doc.create('div');
display.bind('count'); // Shows raw value

const formatted = doc.create('div');
formatted.bind('count', (val) => `Count: ${val}`); // Transform

// Update state (automatically updates UI)
button.on('click', () => {
  State.count++; // Global State proxy
});

// Access state in browser
// window.State.count
// window.State.user

How it works:

  • Server renders initial HTML
  • Client receives window.State as reactive Proxy
  • Changing State.count++ automatically updates all bound elements
  • No manual DOM manipulation needed!

Exports

const {
  Document,           // Main class
  Element,           // Element class (usually not used directly)
  Head,              // Head manager (usually via doc.title(), etc.)
  CONFIG,            // Global configuration
  createCachedRenderer,  // Express middleware
  clearCache,        // Clear response cache
  enableCompression, // Gzip middleware
  responseCache,     // LRU cache instance
  warmupCache,       // Pre-render routes
  getCacheStats,     // Cache statistics
  resetPools,        // Reset object pools
  healthCheck,       // Health check data
  metrics            // Performance metrics
} = require('@trebor/buildhtml');

Express Integration

Basic Route

const express = require('express');
const { Document } = require('@trebor/buildhtml');

const app = express();

app.get('/', (req, res) => {
  const doc = new Document();
  doc.title('Home');
  
  doc.create('h1').text('Welcome!');
  doc.create('p').text('Built with BuildHTML');
  
  res.send(doc.render());
});

Cached Static Page

const { createCachedRenderer } = require('sculptor-js');

app.get('/about', createCachedRenderer(
  async (req) => {
    const doc = new Document();
    doc.title('About Us');
    doc.create('h1').text('About');
    return doc;
  },
  'about-page' // Cache key
));

// First request: ~3ms (render)
// Cached requests: <0.1ms (from cache)

Dynamic Content

app.get('/user/:name', async (req, res) => {
  const doc = new Document();
  doc.title(`Profile - ${req.params.name}`);
  doc.state('userName', req.params.name);
  
  const greeting = doc.create('h1');
  greeting.bind('userName', (name) => `Welcome, ${name}!`);
  
  res.send(doc.render());
});

Interactive Counter

app.get('/counter', (req, res) => {
  const doc = new Document();
  doc.title('Counter');
  doc.state('count', 0);
  
  // Display
  const display = doc.create('h1');
  display.bind('count', (val) => `Count: ${val}`);
  
  // Buttons
  doc.create('button')
    .text('Decrement')
    .on('click', () => { State.count--; });
  
  doc.create('button')
    .text('Reset')
    .on('click', () => { State.count = 0; });
  
  doc.create('button')
    .text('Increment')
    .on('click', () => { State.count++; });
  
  res.send(doc.render());
});

With JSON Export (SPA Mode)

app.get('/spa', (req, res) => {
  const doc = new Document();
  doc.state('page', 'home');
  
  // Build UI...
  
  // Render with obfuscated JSON
  const html = doc.renderJSON({ obfuscate: true });
  res.send(html);
  
  // Client can access: window.__SCULPTOR_DATA__
});

Performance

Benchmarks

| Scenario | Avg Time | Requests/Sec | |----------|----------|--------------| | Simple page (10 elements) | 0.5-1ms | 1,000-2,000 | | Complex page (100 elements) | 3-5ms | 200-333 | | With state (10 bindings) | 2-3ms | 333-500 | | Cached page | <0.1ms | 10,000+ |

Memory Usage

  • Per Request: 50-200 KB
  • 1000 Requests: ~20-40 MB total
  • Object Pooling: Keeps memory stable

File Sizes

| Output | Size | |--------|------| | render() | 1-5 KB | | renderJSON() | 2-8 KB | | renderJSON({ obfuscate: true }) | 1-4 KB (50% smaller!) |


Configuration

const { CONFIG } = require('@trebor/buildhtml');

CONFIG.mode = 'prod';           // 'prod' or 'dev'
CONFIG.poolSize = 150;          // Max pooled elements
CONFIG.cacheLimit = 2000;       // Max cached responses
CONFIG.enableMetrics = true;    // Track performance
CONFIG.sanitizeCss = true;      // CSS injection protection

Middleware Helpers

createCachedRenderer(builderFn, cacheKeyOrFn, options)

app.get('/page', createCachedRenderer(
  async (req) => {
    const doc = new Document();
    // Build page...
    return doc;
  },
  'page-key' // or (req) => `page-${req.params.id}`
));

clearCache(pattern?)

clearCache();           // Clear all
clearCache('user-');    // Clear all keys containing 'user-'

enableCompression()

const { enableCompression } = require('@trebor/buildhtml');
app.use(enableCompression());

warmupCache(routes)

const { warmupCache } = require('@trebor/buildhtml');

await warmupCache([
  { key: 'home', builder: () => buildHomePage() },
  { key: 'about', builder: () => buildAboutPage() }
]);

JSON Export/Import

Export

const doc = new Document();
doc.state('count', 0);
// ... build document ...

// Get JSON
const json = doc.toJSON();
fs.writeFileSync('./page.json', JSON.stringify(json));

Import

const json = JSON.parse(fs.readFileSync('./page.json'));
const doc = Document.fromJSON(json);
const html = doc.render();

Embedded JSON

// Render with JSON embedded in HTML
const html = doc.renderJSON({ obfuscate: true });

// In browser:
console.log(window.__SCULPTOR_DATA__);
// Can rebuild page from JSON if needed

Security

Built-in Security Features

Automatic XSS Protection

doc.create('div').text('<script>alert("XSS")</script>');
// Output: &lt;script&gt;alert("XSS")&lt;/script&gt;

CSS Injection Prevention

el.css({ background: 'red; } body { display: none; }' });
// Sanitized automatically

CSP Nonce Support

const doc = new Document({ nonce: 'abc123' });
// All inline scripts/styles get nonce attribute

Enhanced Security Features (NEW!)

Security Headers Middleware

const { securityHeaders } = require('@trebor/buildhtml');

app.use(securityHeaders({
  enableCSP: true,
  enableHSTS: true,
  enableXFrameOptions: true,
  cspNonce: (req) => req.nonce
}));

Input Sanitization

const { sanitize } = require('@trebor/buildhtml');

// Text sanitization
const safe = sanitize.text('<script>bad</script>');

// URL sanitization (blocks javascript:, data:, vbscript:)
const url = sanitize.url('javascript:alert(1)'); // Returns ''

// Email validation
const email = sanitize.email('[email protected]');

// Filename sanitization (prevents path traversal)
const filename = sanitize.filename('../../../etc/passwd');

CSRF Protection

const { csrf } = require('@trebor/buildhtml');

app.use(csrf.middleware());

app.post('/submit', (req, res) => {
  // CSRF token automatically validated
  // Request blocked if invalid
});

Rate Limiting

const { createRateLimiter } = require('@trebor/buildhtml');

app.use('/api', createRateLimiter({
  windowMs: 15 * 60 * 1000, // 15 minutes
  maxRequests: 100
}));

CSP Header Generation

const { generateCSP } = require('@trebor/buildhtml');

const csp = generateCSP({
  scriptSrc: ["'self'", "'nonce-abc123'"],
  styleSrc: ["'self'"],
  imgSrc: ["'self'", 'data:', 'https:']
});

📖 See SECURITY.md for complete security documentation


Advanced Features

Fragments

function Header(doc) {
  const header = doc.create('header');
  header.create('h1').text('My Site');
  header.create('nav').text('Navigation');
  return header;
}

doc.useFragment(Header);

OnCreate Hook

doc.oncreate(() => {
  console.log('Page loaded!');
  // Initialize analytics, etc.
});

Metrics

process.env.ENABLE_METRICS = 'true';

const { metrics } = require('@trebor/buildhtml');

// After some requests...
console.log(metrics.getStats());
// {
//   counters: { 'render.count': 1000 },
//   timings: { 'render.total': { avg: 2.5, p95: 5 } }
// }

Best Practices

✅ DO

// Use global state for reactive data
doc.state('count', 0);
btn.on('click', () => { State.count++; });

// Cache static pages
app.get('/about', createCachedRenderer(..., 'about'));

// Use CSS-in-JS for scoped styles
el.css({ padding: '20px', backgroundColor: '#f0f0f0' });

// Leverage object pooling (automatic)
// Elements are recycled after render()

❌ DON'T

// Don't use closures in event handlers
let count = 0; // This won't work after serialization
btn.on('click', () => { count++; });

// Don't store non-serializable data in state
doc.state('callback', () => {}); // Functions can't be serialized

// Don't manually manipulate the DOM
// Use State instead for reactivity

Limitations

Function Serialization

Event handlers are serialized with .toString():

// ❌ BAD - Uses closure (won't work)
let count = 0;
btn.on('click', () => { count++; });

// ✅ GOOD - Uses global State
doc.state('count', 0);
btn.on('click', () => { State.count++; });

State Values

State must be JSON-serializable:

// ✅ GOOD
doc.state('user', { name: 'Alice', age: 30 });
doc.state('items', [1, 2, 3]);

// ❌ BAD
doc.state('callback', () => {}); // Functions
doc.state('dom', document.getElementById('x')); // DOM nodes


Complete Example

const express = require('express');
const { Document, createCachedRenderer } = require('@trebor/buildhtml');

const app = express();

// Simple counter with reactive state
app.get('/counter', (req, res) => {
  const doc = new Document();
  doc.title('Counter App');
  
  // Global state
  doc.state('count', 0);
  
  // Styled container
  const container = doc.create('div');
  container.css({
    maxWidth: '400px',
    margin: '50px auto',
    padding: '20px',
    textAlign: 'center',
    fontFamily: 'Arial, sans-serif'
  });
  
  // Title
  container.create('h1').text('Counter Demo');
  
  // Count display (bound to state)
  const display = container.create('div');
  display.css({ 
    fontSize: '48px', 
    margin: '20px',
    color: '#333' 
  });
  display.bind('count', (val) => `Count: ${val}`);
  
  // Button container
  const buttons = container.create('div');
  
  // Decrement button
  const decBtn = buttons.create('button');
  decBtn.text('− Decrement');
  decBtn.css({ 
    padding: '10px 20px', 
    margin: '5px',
    cursor: 'pointer',
    fontSize: '16px'
  });
  decBtn.on('click', () => { State.count--; });
  
  // Reset button
  const resetBtn = buttons.create('button');
  resetBtn.text('Reset');
  resetBtn.css({ 
    padding: '10px 20px', 
    margin: '5px',
    cursor: 'pointer',
    fontSize: '16px'
  });
  resetBtn.on('click', () => { State.count = 0; });
  
  // Increment button
  const incBtn = buttons.create('button');
  incBtn.text('+ Increment');
  incBtn.css({ 
    padding: '10px 20px', 
    margin: '5px',
    cursor: 'pointer',
    fontSize: '16px'
  });
  incBtn.on('click', () => { State.count++; });
  
  res.send(doc.render());
});

// Form with input binding
app.get('/form', (req, res) => {
  const doc = new Document();
  doc.title('Form Example');
  
  // State
  doc.state('username', '');
  doc.state('greeting', 'Enter your name');
  
  // Form
  const form = doc.create('div');
  form.css({ padding: '20px', fontFamily: 'Arial' });
  
  form.create('h1').text('Form Demo');
  
  // Input
  const input = form.create('input');
  input.attr('type', 'text');
  input.attr('placeholder', 'Enter name...');
  input.css({ padding: '10px', fontSize: '16px' });
  
  // Submit button
  const submitBtn = form.create('button');
  submitBtn.text('Submit');
  submitBtn.css({ padding: '10px 20px', marginLeft: '10px' });
  submitBtn.on('click', () => {
    const input = document.querySelector('input');
    State.username = input.value;
    State.greeting = `Hello, ${State.username}!`;
  });
  
  // Display greeting
  const greetingEl = form.create('div');
  greetingEl.css({ marginTop: '20px', fontSize: '24px' });
  greetingEl.bind('greeting');
  
  res.send(doc.render());
});

// Cached static page
app.get('/about', createCachedRenderer(
  async () => {
    const doc = new Document();
    doc.title('About Us');
    
    const page = doc.create('div');
    page.css({ 
      maxWidth: '800px', 
      margin: '0 auto', 
      padding: '20px',
      fontFamily: 'Arial'
    });
    
    page.create('h1').text('About BuildHTML');
    page.create('p').text('High-performance SSR library for Node.js');
    page.create('p').text('Features: Object pooling, reactive state, CSS-in-JS');
    page.create('p').text('This page is cached for maximum performance!');
    
    return doc;
  },
  'about-page'
));

app.listen(3000, () => {
  console.log('Server running at http://localhost:3000');
  console.log('Routes:');
  console.log('  /counter - Interactive counter');
  console.log('  /form    - Form with state binding');
  console.log('  /about   - Cached static page');
});

What This Example Shows

Reactive state binding - bind() automatically updates text
Event handling - Buttons update state
CSS-in-JS - Inline styling with scoped classes
Form inputs - Reading input values in events
Cached pages - Static pages served from cache
Auto-attachment - All elements automatically added

Try It

node app.js
# Visit http://localhost:3000/counter