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

@currentjs/templating

v0.1.2

Published

A lightweight, server-side templating engine for Node.js applications with support for layouts, partials, and basic logic expressions

Readme

@currentjs/templating

"Because sometimes you just want to write HTML like it's 2005, but with the conveniences of 2025"

What is this?

A lightweight server-side HTML templating engine that's desperately trying to justify its existence in a world dominated by React, Vue, and other modern frontend frameworks. Think of it as Mustache's quirky cousin who learned some new tricks but still insists on living in the server-side basement.

Why Not Just Use React?

Excellent question! Here are some totally legitimate reasons:

  1. You enjoy the thrill of server-side rendering without the complexity of Next.js
  2. You miss the simplicity of PHP templates but want to stay in the JavaScript ecosystem
  3. You're building a generated application where the templating needs to be dead simple and predictable
  4. You want to feel nostalgic about the good old days when templates were just HTML with some mustaches
  5. You're secretly a masochist who enjoys debugging template syntax errors

In all seriousness, this templating engine shines when you need:

  • Simple, readable templates that designers can understand
  • Server-side rendering without the React SSR complexity
  • Generated code that doesn't require a PhD in modern JavaScript frameworks
  • Templates that don't change much and don't need state management

Core Features

🔧 Mustache-style Interpolation

<h1>Hello, {{ name }}!</h1>
<p>You are {{ age }} years old.</p>

Values are HTML-escaped by default (&, ", ', <, > → entities). Use triple braces {{{ }}} for raw (unescaped) output—e.g. when injecting pre-rendered HTML like layout content.

🔄 Loops with x-for

<ul x-for="items" x-row="item">
  <li>{{ item.name }} - ${{ item.price }}</li>
</ul>

❓ Conditionals with x-if

<div x-if="user.isAdmin">
  <button>Delete Everything</button>
</div>

📝 Template Includes

<!-- Define a template -->
<!-- @template name="userCard" -->
<div class="card">
  <h3>{{ name }}</h3>
  <p>{{ email }}</p>
</div>

<!-- Use it elsewhere -->
<userCard name="John" email="[email protected]" />

🎭 Layout Support

<!-- layout.html -->
<!DOCTYPE html>
<html>
<head><title>{{ title }}</title></head>
<body>{{ content }}</body>
</html>

<!-- Use with layout -->
engine.renderWithLayout('layout', 'myPage', { title: 'My Page' });

Real-World Usage (From Generated Apps)

When you use the @currentjs/gen package to generate an application, it automatically sets up the templating engine for you. Here's how it looks in practice:

Main Application Setup

// Generated in app.ts
import { createTemplateEngine } from '@currentjs/templating';

const renderEngine = createTemplateEngine({ 
  directories: [process.cwd()] 
});

const app = createWebServer({ controllers, webDir }, { 
  port: 3000, 
  renderer: (template, data, layout) => {
    try {
      return layout 
        ? renderEngine.renderWithLayout(layout, template, data) 
        : renderEngine.render(template, data);
    } catch (e) {
      return String(e instanceof Error ? e.message : e);
    }
  },
  errorTemplate: 'error'
});

Layout Template Example

<!-- src/common/ui/templates/main_view.html -->
<!-- @template name="main_view" -->
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Your App</title>
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
  <script src="/app.js"></script>
</head>
<body>
  <div class="container-fluid">
    <div id="main">{{{ content }}}</div>
  </div>
</body>
</html>

Data List Template

<!-- src/modules/Cat/views/catlist.html -->
<!-- @template name="catList" -->
<div class="container-fluid py-4">
  <div class="row">
    <div class="col">
      <h1 class="h2">Cat List</h1>
      <table class="table table-hover">
        <thead>
          <tr>
            <th>ID</th><th>Name</th><th>Age</th><th>Breed</th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody x-for="$root" x-row="row">
          <tr>
            <td>{{ row.id }}</td>
            <td>{{ row.name }}</td>
            <td>{{ row.age }}</td>
            <td>{{ row.breed }}</td>
            <td>
              <a href="/cat/{{ row.id }}" class="btn btn-sm btn-outline-primary">View</a>
              <a href="/cat/{{ row.id }}/edit" class="btn btn-sm btn-outline-secondary">Edit</a>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</div>

Detail View Template

<!-- src/modules/Cat/views/catdetail.html -->
<!-- @template name="catDetail" -->
<div class="container-fluid py-4">
  <div class="row justify-content-center">
    <div class="col-lg-8">
      <h1 class="h2">Cat Details</h1>
      <div class="card">
        <div class="card-body">
          <table class="table table-borderless">
            <tbody>
              <tr><th>Name</th><td>{{ $root.name }}</td></tr>
              <tr><th>Age</th><td>{{ $root.age }}</td></tr>
              <tr><th>Breed</th><td>{{ $root.breed }}</td></tr>
            </tbody>
          </table>
        </div>
      </div>
      <a href="{{ basePath }}/{{ $root.id }}/edit" class="btn btn-primary">Edit</a>
    </div>
  </div>
</div>

API Reference

TemplateEngine Class

Constructor Options

type TemplateEngineOptions = {
  directories: string[];           // Where to look for templates
  extensions?: string[];           // File extensions to scan (default: ['.html', '.htm', '.tpl', '.tpl.html'])
  nameDirectiveRegex?: RegExp;     // Custom regex for template name directives
  caseInsensitiveNames?: boolean;  // Case-insensitive template names (default: true)
};

Methods

render(templateName: string, data: unknown): string Renders a template with the given data.

renderWithLayout(layoutTemplateName: string, innerTemplateName: string, data: unknown, contentVarName?: string): string Renders a template inside a layout. The inner template is rendered first, then injected into the layout as the content variable.

reload(): void Rescans all directories for templates. Useful during development.

listTemplateNames(): string[] Returns all available template names.

Template Syntax

Template Names

Templates are identified by a directive at the top of the file:

<!-- @template name="myTemplate" -->
<!-- or -->
<!-- @template: myTemplateName -->

If no directive is found, the filename (without extension) is used.

Variable Interpolation

{{ variableName }}
{{ object.property }}
{{ $root.someProperty }}  <!-- Access root data object -->
{{ $index }}              <!-- Current loop index (inside x-for) -->
  • {{ }} — Escaped output: characters &, ", ', <, > are converted to HTML entities. Use for text and attribute values to prevent broken markup and XSS when data contains quotes (e.g. JSON strings).
  • {{{ }}} — Raw (unescaped) output: use only for trusted HTML you intend to inject as markup (e.g. {{{ content }}} in layouts for pre-rendered inner templates).

Loops

<div x-for="arrayVariable" x-row="itemName">
  <p>{{ itemName.property }}</p>
  <span>Index: {{ $index }}</span>
</div>
  • x-for specifies the array to iterate over
  • x-row specifies the variable name for each item (defaults to "item")
  • $index is available inside loops

Conditionals

<div x-if="user.isActive">
  <p>User is active!</p>
</div>

<section x-if="items.length > 0">
  <h2>Items Found</h2>
</section>

Template Includes

<!-- Self-closing tag with template name -->
<myTemplate prop1="value" prop2="{{ dynamicValue }}" />

<!-- Attributes can be static strings or dynamic expressions -->
<userCard 
  name="{{ user.fullName }}" 
  role="admin" 
  isActive="{{ user.status === 'active' }}" 
/>

Security Considerations

⚠️ Important: This templating engine uses eval() for expression evaluation (wrapped in a Function constructor with a limited scope). While it's sandboxed to some degree, never use untrusted user input directly in templates. This is designed for server-side templates with controlled data, not for rendering user-generated content.

By default, {{ }} interpolations are HTML-escaped, which helps prevent broken attributes and XSS when values contain quotes or special characters. Use {{{ }}} only for trusted pre-rendered HTML (e.g. layout content).

Performance Notes

  • Templates are cached after the first scan
  • Use reload() in development, but avoid it in production
  • The engine is designed for server-side rendering, not real-time updates
  • Nested includes can impact performance (there's a 50-level recursion limit)

Debugging Tips

  1. Template not found? Check your template name directive and file location
  2. Expression errors? The engine will throw with context about which expression failed
  3. Infinite recursion? Check for circular includes between templates
  4. Variables undefined? Remember that {{ undefinedVar }} renders as empty string
  5. x-for not working? Make sure your data is actually an array

Common Patterns

Master-Detail Views

// Controller
return this.render('catDetail', cat, 'main_view');

Form with Validation Errors

<div x-if="errors.name">
  <span class="error">{{ errors.name }}</span>
</div>
<input type="text" name="name" value="{{ formData.name || '' }}">

Dynamic Navigation

<nav>
  <ul x-for="menuItems" x-row="item">
    <li x-if="item.visible">
      <a href="{{ item.url }}" x-if="item.url === currentPath" class="active">
        {{ item.title }}
      </a>
    </li>
  </ul>
</nav>

Comparison with Other Solutions

| Feature | @currentjs/templating | Mustache | Handlebars | JSX/React | |---------|----------------------|----------|------------|-----------| | Learning Curve | Low | Low | Medium | High | | Logic in Templates | Limited | None | Some | Full JavaScript | | Server-Side Only | ✅ | ✅ | ✅ | ❌ (needs SSR setup) | | Template Inheritance | ✅ | ❌ | ✅ | ❌ (component-based) | | Performance | Good | Excellent | Good | Excellent (client-side) | | Bundle Size | Small | Tiny | Medium | Large | | Ecosystem | Minimal | Large | Large | Massive |

When to Use This vs. Modern Frameworks

Use @currentjs/templating when:

  • Building traditional server-rendered applications
  • Working with generated/scaffolded code
  • Need simple templates that non-developers can edit
  • Want minimal build complexity
  • Building content-heavy sites with minimal interactivity

Use React/Vue/etc. when:

  • Building interactive SPAs
  • Need complex state management
  • Want rich ecosystem and tooling
  • Building apps with real-time updates
  • Team is comfortable with modern JavaScript

Installation & Setup

As promised, here's the boring installation stuff at the bottom:

🚀 Using with @currentjs/gen (Code Generator)

You don't need to install anything When you generate an application using @currentjs/gen, this templating package is automatically installed and configured for you. Just start creating your .html templates and you're ready to go.

# Generate your app - templating is included automatically
currentjs create app my-app
# Start creating templates in src/modules/YourModule/views/

(for more details see the documentation)

📦 Manual Installation (Standalone Usage)

Only needed if you want to use this templating engine outside of generated applications:

npm install @currentjs/templating

Basic Setup

import { createTemplateEngine } from '@currentjs/templating';

const engine = createTemplateEngine({
  directories: ['./views', './templates'],
  extensions: ['.html', '.tpl']  // optional
});

// Render a template
const html = engine.render('myTemplate', { 
  title: 'Hello World',
  users: [{ name: 'Alice' }, { name: 'Bob' }]
});

// Render with layout
const htmlWithLayout = engine.renderWithLayout(
  'layout',      // layout template name
  'content',     // inner template name  
  { title: 'My Page' },
  'content'      // variable name in layout (default: 'content')
);

Integration with Express.js

import express from 'express';
import { createTemplateEngine } from '@currentjs/templating';

const app = express();
const engine = createTemplateEngine({ directories: ['./views'] });

app.get('/', (req, res) => {
  const html = engine.render('home', { message: 'Hello World!' });
  res.send(html);
});

Remember: This templating engine is like a reliable old car - it might not have all the bells and whistles of a Tesla, but it'll get you where you need to go without breaking down. And sometimes, that's exactly what you need.

Authorship & contribution

Vibecoded with claude-4-sonnet & gpt-5 by Konstantin Zavalny. Yes, it is a vibecoded solution, really.

Any contributions such as bugfixes, improvements, etc are very welcome.

License

GNU Lesser General Public License (LGPL)

It simply means, that you:

  • can create a proprietary application that uses this library without having to open source their entire application code (this is the "lesser" aspect of LGPL compared to GPL).
  • can make any modifications, but must distribute those modifications under the LGPL (or a compatible license) and include the original copyright and license notice.