tarkine
Version:
Tarkine - A lightweight and high-performance template engine for Node.js, designed for speed and simplicity.
1,363 lines (1,345 loc) • 40.2 kB
JavaScript
var __getOwnPropNames = Object.getOwnPropertyNames;
var __commonJS = (cb, mod) => function __require() {
try {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
} catch (e) {
throw mod = 0, e;
}
};
// packages/tarkine/src/utils/scope.js
var require_scope = __commonJS({
"packages/tarkine/src/utils/scope.js"(exports2, module2) {
function buildRootScope(globals, scope) {
return globals ? Object.assign(Object.create(globals), scope) : scope;
}
function createGlobalsHandle(store) {
return {
get(key) {
return store[key];
},
getAll() {
return store;
},
set(key, value) {
store[key] = value;
return value;
},
delete(key) {
delete store[key];
},
clear() {
for (const key in store) {
delete store[key];
}
}
};
}
module2.exports = {
buildRootScope,
createGlobalsHandle
};
}
});
// packages/tarkine/src/compiler/templateLexer.js
var require_templateLexer = __commonJS({
"packages/tarkine/src/compiler/templateLexer.js"(exports2, module2) {
var HTML_BOOLEAN_ATTRIBUTES = /* @__PURE__ */ new Set([
"checked",
"disabled",
"readonly",
"required",
"autofocus",
"multiple",
"selected",
"hidden",
"open",
"ismap",
"defer",
"async",
"novalidate",
"formnovalidate",
"allowfullscreen",
"itemscope",
"reversed",
"autoplay",
"controls",
"loop",
"muted",
"default"
]);
var TEMPLATE_OP = {
TEXT: "text",
ESCAPE: "escape",
RAW: "raw",
BOOL_ATTR: "boolAttr",
IF: ":if",
ELSE: ":else",
FOR: ":for",
LAYOUT: ":layout",
SLOT: ":slot",
INCLUDE: ":include",
END_IF: "/if",
END_FOR: "/for",
END_SLOT: "/slot",
END_LAYOUT: "/layout"
};
var BOOL_ATTR_PATTERN = [...HTML_BOOLEAN_ATTRIBUTES].join("|");
var TEMPLATE_TOKEN_REGEX = new RegExp(
`(${BOOL_ATTR_PATTERN})\\s*=\\s*(["'])\\s*\\{\\{\\s*([\\s\\S]*?)\\s*\\}\\}\\s*\\2|\\{\\{(#|-|\\/if|\\/for|\\/slot|\\/layout|:if|:else|:for|:layout|:slot|:include)?\\s*([\\s\\S]*?)\\s*\\}\\}`,
"gi"
);
function lexTemplate(template) {
const tokens = [];
let offset = 0;
TEMPLATE_TOKEN_REGEX.lastIndex = 0;
for (const match of template.matchAll(TEMPLATE_TOKEN_REGEX)) {
const text = template.slice(offset, match.index);
const isBooleanAttribute = match[1] !== void 0;
if (isBooleanAttribute) {
const trimmedText = text.replace(/\s+$/, "");
if (trimmedText) {
tokens.push({
type: TEMPLATE_OP.TEXT,
value: trimmedText
});
}
tokens.push({
type: TEMPLATE_OP.BOOL_ATTR,
attribute: match[1].toLowerCase(),
expression: match[3].trim()
});
} else {
if (text) {
tokens.push({
type: TEMPLATE_OP.TEXT,
value: text
});
}
const directive = match[4]?.trim() ?? null;
const expression = match[5].trim();
switch (directive) {
case null:
tokens.push({
type: TEMPLATE_OP.ESCAPE,
expression
});
break;
case "-":
tokens.push({
type: TEMPLATE_OP.RAW,
expression
});
break;
case "#":
break;
default:
tokens.push({
type: directive,
expression
});
break;
}
}
offset = match.index + match[0].length;
}
const remainingText = template.slice(offset);
if (remainingText) {
tokens.push({
type: TEMPLATE_OP.TEXT,
value: remainingText
});
}
return tokens;
}
module2.exports = {
lexTemplate,
TEMPLATE_OP
};
}
});
// packages/tarkine/src/compiler/expressionLexer.js
var require_expressionLexer = __commonJS({
"packages/tarkine/src/compiler/expressionLexer.js"(exports2, module2) {
var KEYWORD_LITERALS = {
true: true,
false: false,
null: null,
undefined: void 0
};
var MULTI_CHAR_OPERATORS = [
"===",
"!==",
">=",
"<=",
"==",
"!=",
"&&",
"||",
"**",
"++",
"--"
];
var ESCAPE_SEQUENCES = {
n: "\n",
t: " ",
r: "\r",
b: "\b",
f: "\f",
v: "\v",
0: "\0"
};
function syntaxError(message, position) {
const err = new SyntaxError(
position == null ? message : `${message} (at index ${position})`
);
err.position = position;
return err;
}
function lexExpression(source) {
const tokens = [];
let index = 0;
while (index < source.length) {
const start = index;
if (/\s/.test(source[index])) {
index++;
continue;
}
if (source[index] === '"' || source[index] === "'") {
const quote = source[index++];
let value = "";
while (index < source.length && source[index] !== quote) {
if (source[index] === "\\") {
index++;
const escaped = source[index];
value += ESCAPE_SEQUENCES[escaped] ?? escaped;
} else {
value += source[index];
}
index++;
}
if (index >= source.length) {
throw syntaxError("Unterminated string literal", start);
}
index++;
tokens.push({ type: "str", value, position: start });
continue;
}
if (source[index] === "`") {
index++;
tokens.push({ type: "tplStart", position: start });
let textSegment = "";
while (index < source.length && source[index] !== "`") {
if (source[index] === "$" && source[index + 1] === "{") {
if (textSegment) {
tokens.push({
type: "str",
value: textSegment,
position: index
});
textSegment = "";
}
const exprStart = index;
index += 2;
tokens.push({ type: "tplExpr", position: exprStart });
let braceDepth = 1;
let expression = "";
let inString = null;
while (index < source.length && braceDepth > 0) {
const ch = source[index];
if (inString) {
expression += ch;
if (ch === "\\" && index + 1 < source.length) {
index++;
expression += source[index];
} else if (ch === inString) {
inString = null;
}
index++;
continue;
}
if (ch === '"' || ch === "'" || ch === "`") {
inString = ch;
expression += ch;
index++;
continue;
}
if (ch === "{") {
braceDepth++;
} else if (ch === "}") {
if (--braceDepth === 0) {
index++;
break;
}
}
expression += ch;
index++;
}
if (braceDepth > 0) {
throw syntaxError(
"Unterminated template expression",
exprStart
);
}
tokens.push({
type: "tplExprEnd",
expr: expression,
position: index
});
} else {
textSegment += source[index++];
}
}
if (index >= source.length) {
throw syntaxError("Unterminated template literal", start);
}
if (textSegment) {
tokens.push({
type: "str",
value: textSegment,
position: index
});
}
index++;
tokens.push({ type: "tplEnd", position: index });
continue;
}
if (/[0-9]/.test(source[index]) || source[index] === "." && /[0-9]/.test(source[index + 1])) {
let numericText = "";
while (index < source.length && /[0-9._]/.test(source[index])) {
numericText += source[index++];
}
if ((numericText.match(/\./g) ?? []).length > 1) {
throw syntaxError(
`Invalid number literal "${numericText}"`,
start
);
}
tokens.push({
type: "num",
value: Number(numericText.replace(/_/g, "")),
position: start
});
continue;
}
let operatorMatched = false;
for (const operator of MULTI_CHAR_OPERATORS) {
if (source.slice(index, index + operator.length) === operator) {
tokens.push({ type: "op", value: operator, position: start });
index += operator.length;
operatorMatched = true;
break;
}
}
if (operatorMatched) continue;
if ("+-*/%<>!?:.,[](){}=".includes(source[index])) {
tokens.push({
type: "op",
value: source[index++],
position: start
});
continue;
}
if (/[a-zA-Z_$]/.test(source[index])) {
let identifier = "";
while (index < source.length && /[a-zA-Z0-9_$]/.test(source[index])) {
identifier += source[index++];
}
if (identifier in KEYWORD_LITERALS) {
tokens.push({
type: "lit",
value: KEYWORD_LITERALS[identifier],
position: start
});
} else if (identifier === "typeof" || identifier === "in") {
tokens.push({ type: "op", value: identifier, position: start });
} else {
tokens.push({ type: "id", value: identifier, position: start });
}
continue;
}
throw syntaxError(`Unexpected character "${source[index]}"`, start);
}
return tokens;
}
module2.exports = {
lexExpression,
syntaxError
};
}
});
// packages/tarkine/src/compiler/expressionParser.js
var require_expressionParser = __commonJS({
"packages/tarkine/src/compiler/expressionParser.js"(exports2, module2) {
var { syntaxError } = require_expressionLexer();
function parseExpression(tokens) {
let pos = 0;
function peek() {
return tokens[pos];
}
function expect(value) {
const token = tokens[pos];
if (token?.value !== value) {
throw syntaxError(
`Expected "${value}"${token ? ` but got "${token.value ?? token.type}"` : ""}`,
token?.position
);
}
pos++;
return token;
}
function primary() {
const token = tokens[pos++];
if (!token) {
throw syntaxError("Unexpected end of expression");
}
if (token.type === "tplStart") {
const parts = [];
while (peek() && peek().type !== "tplEnd") {
const part = tokens[pos++];
if (part.type === "str") {
parts.push(part);
continue;
}
if (part.type === "tplExpr") {
const expr = tokens[pos++];
if (!expr || expr.type !== "tplExprEnd") {
throw syntaxError(
"Expected template expression",
expr?.position
);
}
parts.push({
type: "templateExpr",
expr: expr.expr
});
continue;
}
throw syntaxError(
`Unexpected token "${part.type}" in template literal`,
part.position
);
}
if (peek()?.type !== "tplEnd") {
throw syntaxError("Expected template literal end");
}
pos++;
return {
type: "template",
parts
};
}
if (token.type === "num" || token.type === "str" || token.type === "lit") {
return token;
}
if (token.type === "id") {
return { type: "id", value: token.value };
}
if (token.value === "(") {
const expr = ternary();
expect(")");
return expr;
}
throw syntaxError(
`Unexpected token "${token.value ?? token.type}"`,
token.position
);
}
function postfix() {
let expr = primary();
while (true) {
if (peek()?.value === ".") {
pos++;
const propertyToken = tokens[pos];
if (propertyToken?.type !== "id") {
throw syntaxError(
'Expected property name after "."',
propertyToken?.position
);
}
pos++;
expr = {
type: "member",
object: expr,
property: propertyToken.value,
computed: false
};
continue;
}
if (peek()?.value === "[") {
pos++;
const property = ternary();
expect("]");
expr = {
type: "member",
object: expr,
property,
computed: true
};
continue;
}
if (peek()?.value === "(") {
pos++;
const args = [];
if (peek()?.value !== ")") {
args.push(ternary());
while (peek()?.value === ",") {
pos++;
args.push(ternary());
}
}
expect(")");
expr = { type: "call", callee: expr, args };
continue;
}
break;
}
return expr;
}
function unary() {
if (peek()?.value === "!" || peek()?.value === "-" || peek()?.value === "typeof") {
const operator = tokens[pos++].value;
return {
type: "unary",
operator,
expr: unary()
};
}
return postfix();
}
function exponent() {
let left = unary();
if (peek()?.value === "**") {
pos++;
return {
type: "binary",
operator: "**",
left,
right: exponent()
};
}
return left;
}
function multiply() {
let left = exponent();
while (peek()?.value === "*" || peek()?.value === "/" || peek()?.value === "%") {
const operator = tokens[pos++].value;
left = {
type: "binary",
operator,
left,
right: exponent()
};
}
return left;
}
function add() {
let left = multiply();
while (peek()?.value === "+" || peek()?.value === "-") {
const operator = tokens[pos++].value;
left = {
type: "binary",
operator,
left,
right: multiply()
};
}
return left;
}
function relational() {
let left = add();
while (peek()?.value === "<" || peek()?.value === "<=" || peek()?.value === ">" || peek()?.value === ">=" || peek()?.value === "in") {
const operator = tokens[pos++].value;
left = {
type: "binary",
operator,
left,
right: add()
};
}
return left;
}
function equality() {
let left = relational();
while (peek()?.value === "==" || peek()?.value === "!=" || peek()?.value === "===" || peek()?.value === "!==") {
const operator = tokens[pos++].value;
left = {
type: "binary",
operator,
left,
right: relational()
};
}
return left;
}
function and() {
let left = equality();
while (peek()?.value === "&&") {
pos++;
left = {
type: "binary",
operator: "&&",
left,
right: equality()
};
}
return left;
}
function or() {
let left = and();
while (peek()?.value === "||") {
pos++;
left = {
type: "binary",
operator: "||",
left,
right: and()
};
}
return left;
}
function ternary() {
const test = or();
if (peek()?.value !== "?") {
return test;
}
pos++;
const yes = ternary();
expect(":");
const no = ternary();
return {
type: "ternary",
test,
yes,
no
};
}
const result = ternary();
if (pos < tokens.length) {
const trailing = tokens[pos];
throw syntaxError(
`Unexpected token "${trailing.value ?? trailing.type}" after expression`,
trailing.position
);
}
return result;
}
module2.exports = {
parseExpression
};
}
});
// packages/tarkine/src/compiler/evaluator.js
var require_evaluator = __commonJS({
"packages/tarkine/src/compiler/evaluator.js"(exports2, module2) {
var { parseExpression } = require_expressionParser();
var { lexExpression } = require_expressionLexer();
function createEvaluatorFromAst(node) {
switch (node?.type) {
case "id": {
const key = node.value;
return (scope) => scope[key];
}
case "member": {
const object = createEvaluatorFromAst(node.object);
if (node.computed) {
const property2 = createEvaluatorFromAst(node.property);
return (scope) => object(scope)?.[property2(scope)];
}
const property = node.property;
return (scope) => object(scope)?.[property];
}
case "call": {
const args = node.args.map(createEvaluatorFromAst);
if (node.callee.type === "member") {
const object = createEvaluatorFromAst(node.callee.object);
const computed = node.callee.computed;
const property = computed ? createEvaluatorFromAst(node.callee.property) : node.callee.property;
return (scope) => {
const receiver = object(scope);
const key = computed ? property(scope) : property;
const fn = receiver?.[key];
if (typeof fn !== "function") {
return void 0;
}
return fn.apply(
receiver,
args.map((arg) => arg(scope))
);
};
}
const callee = createEvaluatorFromAst(node.callee);
return (scope) => {
const fn = callee(scope);
if (typeof fn !== "function") {
return void 0;
}
return fn(...args.map((arg) => arg(scope)));
};
}
case "str":
case "num":
case "lit": {
const value = node.value;
return () => value;
}
case "unary": {
const expr = createEvaluatorFromAst(node.expr);
switch (node.operator) {
case "!":
return (scope) => !expr(scope);
case "-":
return (scope) => -expr(scope);
case "typeof":
return (scope) => typeof expr(scope);
}
return () => void 0;
}
case "binary": {
const left = createEvaluatorFromAst(node.left);
const right = createEvaluatorFromAst(node.right);
switch (node.operator) {
case "===":
return (scope) => left(scope) === right(scope);
case "!==":
return (scope) => left(scope) !== right(scope);
case "==":
return (scope) => left(scope) == right(scope);
case "!=":
return (scope) => left(scope) != right(scope);
case ">":
return (scope) => left(scope) > right(scope);
case ">=":
return (scope) => left(scope) >= right(scope);
case "<":
return (scope) => left(scope) < right(scope);
case "<=":
return (scope) => left(scope) <= right(scope);
case "&&":
return (scope) => left(scope) && right(scope);
case "||":
return (scope) => left(scope) || right(scope);
case "+":
return (scope) => left(scope) + right(scope);
case "-":
return (scope) => left(scope) - right(scope);
case "*":
return (scope) => left(scope) * right(scope);
case "/":
return (scope) => left(scope) / right(scope);
case "%":
return (scope) => left(scope) % right(scope);
case "**":
return (scope) => left(scope) ** right(scope);
case "in":
return (scope) => left(scope) in Object(right(scope));
}
return () => void 0;
}
case "template": {
const parts = node.parts.map((part) => {
if (part.type === "str") {
return () => part.value;
}
if (part.type === "templateExpr") {
const evaluator = createEvaluator(part.expr);
return evaluator;
}
return () => "";
});
return (scope) => parts.map((part) => String(part(scope) ?? "")).join("");
}
case "ternary": {
const test = createEvaluatorFromAst(node.test);
const yes = createEvaluatorFromAst(node.yes);
const no = createEvaluatorFromAst(node.no);
return (scope) => test(scope) ? yes(scope) : no(scope);
}
default:
return () => void 0;
}
}
function createEvaluator(expression) {
const nodes = parseExpression(lexExpression(expression));
return createEvaluatorFromAst(nodes);
}
module2.exports = {
createEvaluator
};
}
});
// packages/tarkine/src/compiler/templateParser.js
var require_templateParser = __commonJS({
"packages/tarkine/src/compiler/templateParser.js"(exports2, module2) {
var { TEMPLATE_OP } = require_templateLexer();
var { createEvaluator } = require_evaluator();
var FOR_PATTERN = /^\s*([A-Za-z_$][\w$]*)(?:\s*,\s*([A-Za-z_$][\w$]*))?\s+in\s+([\s\S]+)$/;
var ELSE_IF_PATTERN = /^if\s+([\s\S]+)$/;
function parseTemplate(tokens) {
const root = [];
const stack = [
{
type: "root",
body: root
}
];
function currentBody() {
return stack[stack.length - 1].body;
}
for (const token of tokens) {
switch (token.type) {
case TEMPLATE_OP.TEXT: {
currentBody().push(token);
break;
}
case TEMPLATE_OP.RAW:
case TEMPLATE_OP.ESCAPE: {
currentBody().push({
type: token.type,
evaluate: createEvaluator(token.expression)
});
break;
}
case TEMPLATE_OP.BOOL_ATTR: {
currentBody().push({
type: token.type,
attribute: token.attribute,
evaluate: createEvaluator(token.expression)
});
break;
}
case TEMPLATE_OP.IF: {
const node = {
type: TEMPLATE_OP.IF,
branches: [
{
evaluate: createEvaluator(token.expression),
body: []
}
]
};
currentBody().push(node);
stack.push({
type: TEMPLATE_OP.IF,
node,
body: node.branches[0].body
});
break;
}
case TEMPLATE_OP.ELSE: {
const frame = stack[stack.length - 1];
if (frame.type !== TEMPLATE_OP.IF) {
throw new Error("Unexpected {{:else}} or {{:else if}}");
}
const match = token.expression.match(ELSE_IF_PATTERN);
const branch = {
evaluate: match ? createEvaluator(match[1].trim()) : null,
body: []
};
frame.node.branches.push(branch);
frame.body = branch.body;
break;
}
case TEMPLATE_OP.END_IF: {
const frame = stack.pop();
if (!frame || frame.type !== TEMPLATE_OP.IF) {
throw new Error("Unexpected {{/if}}");
}
break;
}
case TEMPLATE_OP.FOR: {
const match = token.expression.match(FOR_PATTERN);
if (!match) {
throw new Error(
`Invalid for expression: ${token.expression}`
);
}
const [, value, key, iterable] = match;
const node = {
type: TEMPLATE_OP.FOR,
value,
key: key || null,
evaluate: createEvaluator(iterable.trim()),
body: []
};
currentBody().push(node);
stack.push({
type: TEMPLATE_OP.FOR,
body: node.body
});
break;
}
case TEMPLATE_OP.END_FOR: {
const frame = stack.pop();
if (!frame || frame.type !== TEMPLATE_OP.FOR) {
throw new Error("Unexpected {{/for}}");
}
break;
}
case TEMPLATE_OP.INCLUDE: {
currentBody().push({
type: TEMPLATE_OP.INCLUDE,
evaluate: createEvaluator(token.expression)
});
break;
}
case TEMPLATE_OP.LAYOUT: {
const node = {
type: TEMPLATE_OP.LAYOUT,
evaluate: createEvaluator(token.expression),
slots: {}
};
currentBody().push(node);
stack.push({
type: TEMPLATE_OP.LAYOUT,
node,
body: []
// layout has no body of its own, only slots
});
break;
}
case TEMPLATE_OP.END_LAYOUT: {
const frame = stack.pop();
if (!frame || frame.type !== TEMPLATE_OP.LAYOUT) {
throw new Error("Unexpected {{/layout}}");
}
break;
}
case TEMPLATE_OP.SLOT: {
const node = {
type: TEMPLATE_OP.SLOT,
name: token.expression,
body: []
};
const parent = stack[stack.length - 1];
if (parent.type === TEMPLATE_OP.LAYOUT) {
parent.node.slots[node.name] = node.body;
} else {
currentBody().push(node);
}
stack.push({
type: TEMPLATE_OP.SLOT,
body: node.body
});
break;
}
case TEMPLATE_OP.END_SLOT: {
const frame = stack.pop();
if (!frame || frame.type !== TEMPLATE_OP.SLOT) {
throw new Error("Unexpected {{/slot}}");
}
break;
}
default:
throw new Error(`Unknown template token: ${token.type}`);
}
}
if (stack.length !== 1) {
throw new Error("Unclosed template block");
}
return root;
}
module2.exports = {
parseTemplate
};
}
});
// packages/tarkine/src/compiler/compiler.js
var require_compiler = __commonJS({
"packages/tarkine/src/compiler/compiler.js"(exports2, module2) {
var { lexTemplate, TEMPLATE_OP } = require_templateLexer();
var { parseTemplate } = require_templateParser();
function compile(template) {
return parseTemplate(lexTemplate(template));
}
module2.exports = {
compile,
TEMPLATE_OP
};
}
});
// packages/tarkine/src/renderer.js
var require_renderer = __commonJS({
"packages/tarkine/src/renderer.js"(exports2, module2) {
var { TEMPLATE_OP } = require_compiler();
function renderNodes(nodes, scope, ctx, out) {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
switch (node.type) {
case TEMPLATE_OP.TEXT:
out.push(node.value);
break;
case TEMPLATE_OP.ESCAPE: {
const value = node.evaluate(scope);
if (value != null) {
out.push(ctx.escape(value));
}
break;
}
case TEMPLATE_OP.RAW: {
const value = node.evaluate(scope);
if (value != null) {
out.push(String(value));
}
break;
}
case TEMPLATE_OP.BOOL_ATTR: {
const value = node.evaluate(scope);
if (value) {
out.push(` ${node.attribute}`);
}
break;
}
case TEMPLATE_OP.IF: {
for (const branch of node.branches) {
if (branch.evaluate(scope)) {
renderNodes(branch.body, scope, ctx, out);
break;
}
}
break;
}
case TEMPLATE_OP.FOR: {
const iterable = node.evaluate(scope) || [];
if (Array.isArray(iterable)) {
for (let i2 = 0; i2 < iterable.length; i2++) {
const child = Object.create(scope);
child[node.value] = iterable[i2];
if (node.key) {
child[node.key] = i2;
}
renderNodes(node.body, child, ctx, out);
}
} else {
for (const key in iterable) {
const child = Object.create(scope);
child[node.value] = iterable[key];
if (node.key) {
child[node.key] = key;
}
renderNodes(node.body, child, ctx, out);
}
}
break;
}
case TEMPLATE_OP.INCLUDE: {
const includePath = node.evaluate(scope);
const { output, filePath } = ctx.include(
ctx.parentFilePath,
includePath,
ctx
);
renderNodes(
output,
scope,
{
...ctx,
parentFilePath: filePath
},
out
);
break;
}
case TEMPLATE_OP.LAYOUT: {
const layoutPath = node.evaluate(scope);
const { output, filePath } = ctx.include(
ctx.parentFilePath,
layoutPath,
ctx
);
renderNodes(
output,
scope,
{
...ctx,
parentFilePath: filePath,
slots: node.slots
},
out
);
break;
}
case TEMPLATE_OP.SLOT: {
const slotName = node.name;
if (ctx.slots && ctx.slots[slotName]) {
renderNodes(ctx.slots[slotName], scope, ctx, out);
} else {
renderNodes(node.body, scope, ctx, out);
}
break;
}
}
}
}
function renderer(compiledTokens, scope, ctx = {}) {
const out = [];
renderNodes(compiledTokens, scope, ctx, out);
return out.join("");
}
module2.exports = {
renderer
};
}
});
// packages/tarkine/src/utils/escape.js
var require_escape = __commonJS({
"packages/tarkine/src/utils/escape.js"(exports2, module2) {
var ESCAPE_CHARS = {
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'"
};
function escapeHtml(v) {
if (v == null || v === false) return "";
if (typeof v === "object") {
return JSON.stringify(v).replace(/[&<>"']/g, (c) => ESCAPE_CHARS[c]);
}
return String(v).replace(/[&<>"']/g, (c) => ESCAPE_CHARS[c]);
}
module2.exports = {
escapeHtml
};
}
});
// packages/tarkine/src/templateLoader.js
var require_templateLoader = __commonJS({
"packages/tarkine/src/templateLoader.js"(exports2, module2) {
var path2 = require("node:path");
var fs = require("node:fs");
var { buildRootScope } = require_scope();
var { compile } = require_compiler();
var { renderer } = require_renderer();
var { escapeHtml } = require_escape();
function resolveFilePath2(filePath, ext) {
if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
return path2.resolve(filePath, `index.${ext}`);
}
return path2.extname(filePath) ? filePath : `${filePath}.${ext}`;
}
function compileFile2(filePath, cache) {
if (cache) {
const cached = cache.get(filePath);
if (cached) {
return cached;
}
}
const content = fs.readFileSync(filePath, "utf-8");
const compiled = compile(content);
if (cache) {
cache.set(filePath, compiled);
}
return compiled;
}
function includeFile2(parentFilePath, includePath, { cache, ext }) {
const filePath = resolveFilePath2(
path2.resolve(
parentFilePath ? path2.dirname(parentFilePath) : "",
includePath
),
ext
);
if (!fs.existsSync(filePath)) {
throw new Error(`File not found: ${filePath}`);
}
return {
output: compileFile2(filePath, cache),
filePath
};
}
function renderFile2(filepath, scope, engine) {
const { cache, globals } = engine;
const compiled = compileFile2(filepath, cache);
const rootScope = buildRootScope(globals, scope);
const include = engine.include ?? includeFile2;
const escape = engine.escape ?? escapeHtml;
return renderer(compiled, rootScope, {
parentFilePath: filepath,
cache,
include,
escape
});
}
module2.exports = {
resolveFilePath: resolveFilePath2,
compileFile: compileFile2,
includeFile: includeFile2,
renderFile: renderFile2
};
}
});
// packages/tarkine/src/utils/cache.js
var require_cache = __commonJS({
"packages/tarkine/src/utils/cache.js"(exports2, module2) {
function createCacheStore(maxSize) {
const store = /* @__PURE__ */ new Map();
return {
get(key) {
if (!store.has(key)) {
return void 0;
}
const value = store.get(key);
store.delete(key);
store.set(key, value);
return value;
},
set(key, value) {
store.delete(key);
store.set(key, value);
if (maxSize && store.size > maxSize) {
store.delete(store.keys().next().value);
}
}
};
}
module2.exports = {
createCacheStore
};
}
});
// packages/tarkine/src/utils/util.js
var require_util = __commonJS({
"packages/tarkine/src/utils/util.js"(exports2, module2) {
module2.exports = {
isFunction: (value) => typeof value === "function"
};
}
});
// packages/tarkine/src/engine.js
var require_engine = __commonJS({
"packages/tarkine/src/engine.js"(exports2, module2) {
var { buildRootScope, createGlobalsHandle } = require_scope();
var { createCacheStore } = require_cache();
var { compile } = require_compiler();
var { escapeHtml } = require_escape();
var { isFunction } = require_util();
var { renderer } = require_renderer();
function createEngine2({
cache,
include,
globals,
escape,
maxCacheSize,
ext: extension
} = {}) {
const compiledCache = cache === false ? null : cache ?? createCacheStore(maxCacheSize);
const globalsStore = globals ?? {};
const globalsHandle = createGlobalsHandle(globalsStore);
const escapeFn = isFunction(escape) ? escape : escapeHtml;
const ext = extension ?? "tark";
function cachedCompile(template) {
if (!compiledCache) {
return compile(template);
}
let compiled = compiledCache.get(template);
if (!compiled) {
compiled = compile(template);
compiledCache.set(template, compiled);
}
return compiled;
}
function render(template, scope, ctx = {}) {
return renderCompiled(cachedCompile(template), scope, ctx);
}
function renderCompiled(compiledTokens, scope, ctx = {}) {
const rootScope = buildRootScope(globals, scope);
return renderer(compiledTokens, rootScope, {
parentFilePath: null,
escape: escapeFn,
cache: compiledCache,
include,
ext,
...ctx
});
}
return {
cache: compiledCache,
globals: globalsHandle,
compile: cachedCompile,
renderCompiled,
render,
escape,
include,
ext
};
}
module2.exports = {
createEngine: createEngine2
};
}
});
// packages/tarkine/src/adapters/express.js
var require_express = __commonJS({
"packages/tarkine/src/adapters/express.js"(exports2, module2) {
var { renderFile: renderFile2 } = require_templateLoader();
var { isFunction } = require_util();
function express(engine, adapterOptions = {}) {
return function(filepath, scope = {}, callback) {
let output;
try {
output = renderFile2(filepath, scope, engine);
} catch (error) {
if (isFunction(callback)) {
return callback(error);
}
throw error;
}
if (isFunction(callback)) {
return callback(null, output);
}
return output;
};
}
module2.exports = {
express
};
}
});
// packages/tarkine/src/adapters/hono.js
var require_hono = __commonJS({
"packages/tarkine/src/adapters/hono.js"(exports2, module2) {
var { renderFile: renderFile2 } = require_templateLoader();
function hono(engine, adapterOptions = {}) {
const viewsDir = adapterOptions.views || "views";
return async (c, next) => {
c.setRenderer((viewName, scope) => {
const filepath = path.resolve(viewsDir, viewName);
const output = renderFile2(filepath, scope, engine);
return c.html(output);
});
return await next();
};
}
module2.exports = {
hono
};
}
});
// packages/tarkine/src/adapters/index.js
var require_adapters = __commonJS({
"packages/tarkine/src/adapters/index.js"(exports2, module2) {
var { express } = require_express();
var { hono } = require_hono();
module2.exports = {
express,
hono
};
}
});
// packages/tarkine/src/index.js
var {
renderFile,
compileFile,
includeFile,
resolveFilePath
} = require_templateLoader();
var { createEngine } = require_engine();
var adapters = require_adapters();
module.exports = {
adapters,
renderFile,
compileFile,
includeFile,
createEngine,
resolveFilePath
};