Custom Tags¶
Create your own template tags by registering a parser function. miki-template's tag API mirrors Django's — a tag is a parser that returns a Node object with a render(context) method.
Table of Contents¶
Register a Simple Tag¶
Usage in templates:
Passing Arguments¶
registerTag('greet', (tagContent, parser) => {
// tagContent is the full text after the tag name: "user.name"
const varName = tagContent.trim();
return {
render: (context) => {
const value = context.get(varName);
return `Hello, ${value}!`;
}
};
});
Returning a Node Class¶
For more complex tags, return a Node class instance:
const { registerTag } = require('miki-template');
class GreetNode {
constructor(varName) {
this.varName = varName;
}
render(context) {
const value = context.get(this.varName);
return `Hello, ${value || 'Guest'}!`;
}
}
registerTag('greet', (tagContent, parser) => {
const varName = tagContent.trim();
return new GreetNode(varName);
});
import { registerTag } from 'miki-template';
class GreetNode {
constructor(varName) {
this.varName = varName;
}
render(context) {
const value = context.get(this.varName);
return `Hello, ${value || 'Guest'}!`;
}
}
registerTag('greet', (tagContent, parser) => {
const varName = tagContent.trim();
return new GreetNode(varName);
});
Async Custom Tags¶
If your render() method returns a Promise, the template must be rendered with asyncRender():
const { registerTag, asyncRender } = require('miki-template');
registerTag('fetch_greeting', (tagContent, parser) => {
const urlVar = tagContent.trim();
return {
async render(context) {
const url = context.get(urlVar);
const res = await fetch(url);
const data = await res.json();
return data.message;
}
};
});
// Must use asyncRender
const html = await asyncRender('{% fetch_greeting api_url %}', { api_url: 'https://...' });
import { registerTag, asyncRender } from 'miki-template';
registerTag('fetch_greeting', (tagContent, parser) => {
const urlVar = tagContent.trim();
return {
async render(context) {
const url = context.get(urlVar);
const res = await fetch(url);
const data = await res.json();
return data.message;
}
};
});
const html = await asyncRender('{% fetch_greeting api_url %}', { api_url: 'https://...' });
Parsing Complex Tags¶
Use the parser object to consume tokens and build multi-part tags:
const { registerTag } = require('miki-template');
registerTag('panel', (tagContent, parser) => {
const classes = tagContent.trim() || '';
const nodelist = parser.parse(['endpanel']);
parser.skipTag(); // consume endpanel
return {
render: (context) => {
const body = nodelist.map(n => n.render(context)).join('');
return `<div class="panel ${classes}">${body}</div>`;
}
};
});
import { registerTag } from 'miki-template';
registerTag('panel', (tagContent, parser) => {
const classes = tagContent.trim() || '';
const nodelist = parser.parse(['endpanel']);
parser.skipTag();
return {
render: (context) => {
const body = nodelist.map(n => n.render(context)).join('');
return `<div class="panel ${classes}">${body}</div>`;
}
};
});
Usage with nested content:
Real-World Example: Cache Tag¶
const { registerTag } = require('miki-template');
registerTag('cache_block', (tagContent, parser) => {
const [key, ...rest] = tagContent.trim().split(/\s+/);
const nodelist = parser.parse(['endcache_block']);
parser.skipTag();
return {
render: (context) => {
const cacheKey = key;
const cache = context.get('cache') || global.__cache__;
if (!cache) return nodelist.map(n => n.render(context)).join('');
if (cache.has(cacheKey)) return cache.get(cacheKey);
const output = nodelist.map(n => n.render(context)).join('');
cache.set(cacheKey, output, rest[0] || 300);
return output;
}
};
});
import { registerTag } from 'miki-template';
registerTag('cache_block', (tagContent, parser) => {
const [key, ...rest] = tagContent.trim().split(/\s+/);
const nodelist = parser.parse(['endcache_block']);
parser.skipTag();
return {
render: (context) => {
const cacheKey = key;
const cache = context.get('cache') || global.__cache__;
if (!cache) return nodelist.map(n => n.render(context)).join('');
if (cache.has(cacheKey)) return cache.get(cacheKey);
const output = nodelist.map(n => n.render(context)).join('');
cache.set(cacheKey, output, rest[0] || 300);
return output;
}
};
});
Tag Registration Best Practices¶
-
Return objects with
render(context)— the render signature must accept a context object. -
Use
parser.parse([...terminators])for tags with bodies — this lets the parser consume nested content correctly. -
Always call
parser.skipTag()afterparser.parseto consume the end tag. -
Handle whitespace —
tagContent.trim()for single-argument tags. -
Async tags need asyncRender — return a Promise from
render()and useasyncRender()to render. -
Access context values — use
context.get('key')orcontext.resolve('expr').