A dynamic template engine for Node.js that lets you embed JavaScript directly in HTML files using <?jhs ... ?> delimiters — similar to PHP, but powered by JavaScript.
NODE-JHS2 is a complete rewrite of node-jhs, with improved security, sandboxed execution, XSS protection, and a cleaner API.
Template files use the .jhs extension and integrate seamlessly with native Node.js HTTPS servers and custom middleware chains.
| Feature | node-jhs v1 | NODE-JHS2 v2 |
|---|---|---|
| XSS auto-escaping | ❌ | ✅ |
| VM sandbox execution | ❌ | ✅ |
require() filtering |
❌ | ✅ |
| Execution timeout | ❌ | ✅ 5s |
raw() helper |
❌ | ✅ |
include() support |
❌ | ✅ |
echo() function |
❌ | ✅ |
| Template caching | ✅ | ✅ |
| Configurable delimiters | ✅ | ✅ |
⚠️ Breaking change: The internal compilation and execution model has been completely rewritten. See Migration from v1.
- 🔖 PHP-like syntax — use
<?jhs ... ?>to run JavaScript inside HTML <?= expr ?>shorthand for outputting values- 🛡️ Auto XSS protection — HTML escaping enabled by default
- ⚡ Template caching — compiled templates are cached for performance
- 🔒 Sandboxed execution — templates run inside a Node.js
vmcontext - 📦
requirefiltering — block dangerous or unwanted modules - 🔁
include()support — embed sub-templates with shared data - ⏱️ Execution timeout — 5-second limit per template to prevent hangs
npm install node-jhs2Or clone and use locally:
git clone https://github.com/YOUR_USERNAME/NODE-JHS2.git<!DOCTYPE html>
<html>
<head>
<title><?= title ?></title>
</head>
<body>
<h1>Hello, <?= name ?>!</h1>
<?jhs if (items.length > 0) { ?>
<ul>
<?jhs items.forEach(item => { ?>
<li><?= item ?></li>
<?jhs }); ?>
</ul>
<?jhs } ?>
</body>
</html>const https = require('https');
const fs = require('fs');
const JSTemplateEngine = require('node-jhs2');
const engine = new JSTemplateEngine({ viewsPath: './views' });
https.createServer({
key: fs.readFileSync('./ssl/key.pem'),
cert: fs.readFileSync('./ssl/cert.pem')
}, async (req, res) => {
const html = await engine.render('index.jhs', {
title: 'My App',
name: 'World',
items: ['Apple', 'Banana', 'Cherry']
});
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(html);
}).listen(443);<?= variableName ?><?jhs
const greeting = 'Hello World';
let count = 0;
?><?jhs if (user.isAdmin) { ?>
<p>Welcome, Admin!</p>
<?jhs } else { ?>
<p>Access denied.</p>
<?jhs } ?><?jhs users.forEach(user => { ?>
<li><?= user.name ?> — <?= user.email ?></li>
<?jhs }); ?><?jhs const nav = await include('partials/nav.jhs', { active: 'home' }); ?>
<?= raw(nav) ?>
include()inherits the parent template's data by default.
<?= raw('<strong>Bold</strong>') ?><?jhs echo('Hello ', username, '!'); ?>NODE-JHS2 is designed to work naturally with custom middleware chains on native Node.js HTTPS servers — no Express required.
The recommended pattern is a MiddlewareManager class that chains async functions, with one middleware that attaches res.renderTemplate() to the response object. All subsequent middleware and routes then call it directly.
const server = new MiddlewareManager();
// ── Attach res.renderTemplate() ──────────────────────────────────────────────
server.use((req, res, next) => {
res.renderTemplate = async (templatePath, data = {}, statusCode = 200) => {
const html = await templateEngine.render(templatePath, { ...data, req, res });
res.writeHead(statusCode, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(html);
};
next();
});
// ── Serve .jhs files automatically from /public ──────────────────────────────
server.use(async (req, res, next) => {
const filePath = path.join(PUBLIC_DIR, req.pathname);
if (path.extname(filePath) === '.jhs') {
await res.renderTemplate(filePath, { user: req.user });
return;
}
next();
});
// ── Application routes ────────────────────────────────────────────────────────
server.use(async (req, res, next) => {
if (req.pathname === '/contact' && req.method === 'GET') {
await res.renderTemplate('contact.jhs', { title: 'Contact Us' });
} else {
next();
}
});
// ── Start HTTPS server ────────────────────────────────────────────────────────
https.createServer(sslOptions, (req, res) => server.execute(req, res))
.listen(443);See the full working example in examples/https-server.js.
| Option | Type | Default | Description |
|---|---|---|---|
viewsPath |
string |
./views |
Directory where .jhs template files are stored |
cache |
boolean |
true |
Cache compiled templates in memory |
openTag |
string |
<?jhs |
Opening tag for code blocks |
closeTag |
string |
?> |
Closing tag |
echoTag |
string |
<?= |
Opening tag for output expressions |
encoding |
string |
utf-8 |
File encoding |
autoEscape |
boolean |
true |
Auto-escape HTML output to prevent XSS |
banned_require |
string[] |
['vm','jhs2'] |
Modules blocked from use inside templates |
Renders a .jhs file from disk.
const html = await engine.render('index.jhs', { title: 'Home' });Renders a template directly from a string.
const html = await engine.renderString('<p><?= msg ?></p>', { msg: 'Hi!' });Clears the in-memory template cache.
Clears Node.js require cache entries matching a pattern (useful in development).
JSTemplateEngine.clearRequireCache('.jhs');All values output via <?= ?> or echo() are HTML-escaped by default:
| Character | Escaped as |
|---|---|
& |
& |
< |
< |
> |
> |
" |
" |
' |
' |
Use raw() to output trusted HTML without escaping:
<?= raw(trustedHtmlString) ?>Templates run inside a Node.js vm.createContext() sandbox with a 5-second execution timeout. Only explicitly whitelisted globals are available inside templates.
Block specific modules from use inside templates:
const engine = new JSTemplateEngine({
banned_require: ['child_process', 'fs', 'net']
});vm and jhs2 are always blocked by default.
If you are migrating from node-jhs v1:
- Template syntax is unchanged — your
.jhsfiles work as-is. autoEscapeis now on by default. If you were outputting raw HTML via<?= ?>, wrap it withraw():<?= raw(myHtml) ?>.require()is now filtered.vmandjhs2are always blocked. Add others viabanned_require.- Constructor options are backwards compatible — no changes needed.
NODE-JHS2/
├── index.js # Main engine module
├── package.json
├── README.md
├── LICENSE
├── .gitignore
├── CHANGELOG.md
├── examples/
│ ├── https-server.js # Full middleware-based HTTPS server example
│ ├── server.js # Minimal HTTPS server example
│ └── views/
│ ├── index.jhs
│ └── partials/
│ └── nav.jhs
└── test/
└── engine.test.js # Unit tests (13 tests)
Pull requests are welcome. For major changes, open an issue first.
- Fork the repo
- Create your branch:
git checkout -b feature/my-feature - Commit:
git commit -m 'Add my feature' - Push:
git push origin feature/my-feature - Open a Pull Request
- jhs-instantiator — Hierarchical Instantiator companion module: progressively degrades multi-rank JHS templates down to plain HTML.
MIT — see LICENSE for details.