js-in-strings
Version:
A library for rendering templates with JavaScript expressions
104 lines (102 loc) • 4.44 kB
JavaScript
// src/index.ts
function renderTemplateWithJS(template, context, options = {}) {
const [openDelimiter, closeDelimiter] = options.delimiters || ["{", "}"];
const escOpenDelimiter = openDelimiter.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const escCloseDelimiter = closeDelimiter.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const expressionRegex = new RegExp(`${escOpenDelimiter}([^${escOpenDelimiter}${escCloseDelimiter}]*?)${escCloseDelimiter}`, "g");
const singleExpressionRegex = new RegExp(`^${escOpenDelimiter}([\\s\\S]*?)${escCloseDelimiter}$`);
const mergedContext = {
...context,
...options.contextExtensions
};
if (options.returnRawValues) {
const trimmedTemplate = template.trim();
const match = trimmedTemplate.match(singleExpressionRegex);
if (match) {
try {
const expression = match[1].trim();
const contextKeys = Object.keys(mergedContext);
const contextValues = Object.values(mergedContext);
const isIIFE = /^\s*\(.*\)\s*\(.*\)/.test(expression) || // (function(){})() pattern
/^\s*\(.*=>.*\)\s*\(/.test(expression);
const hasDeclarations = /\b(const|let|var|function|class)\b/.test(expression);
const hasMultipleStatements = expression.includes(";");
let result;
try {
if (options.unsafeEval) {
const contextVars = Object.entries(mergedContext).map(([key, value]) => `const ${key} = ${JSON.stringify(value)};`).join("\n");
result = eval(`${contextVars}
${expression}`);
} else {
let code;
if (isIIFE) {
code = `(${expression})`;
} else if (hasDeclarations || hasMultipleStatements) {
if (expression.includes("return ")) {
code = `(function() { ${expression} })();`;
} else {
const lastSemicolonIndex = expression.lastIndexOf(";");
if (lastSemicolonIndex !== -1 && lastSemicolonIndex < expression.length - 1) {
const lastExpression = expression.substring(lastSemicolonIndex + 1).trim();
code = `(function() { ${expression.substring(0, lastSemicolonIndex + 1)} return ${lastExpression}; })();`;
} else {
code = `(function() { ${expression}; return undefined; })();`;
}
}
} else {
code = expression;
}
const secureEval = (code2, context2) => {
const sandbox = {};
Object.entries(context2).forEach(([key, value]) => {
sandbox[key] = value;
});
const indirectEval = (code3) => {
return Function("sandbox", `with(sandbox) { return ${code3}; }`)(sandbox);
};
return indirectEval(code2);
};
result = secureEval(code, mergedContext);
}
} catch (error) {
console.error(`Error evaluating expression:`, error);
return `[Error: ${error instanceof Error ? error.message : String(error)}]`;
}
return result;
} catch (error) {
console.error(`Error evaluating expression:`, error);
return `[Error: ${error instanceof Error ? error.message : String(error)}]`;
}
}
}
return template.replace(expressionRegex, (match, expression) => {
try {
let result;
if (options.unsafeEval) {
const contextVars = Object.entries(mergedContext).map(([key, value]) => `const ${key} = ${JSON.stringify(value)};`).join("\n");
result = eval(`${contextVars}
(${expression})`);
} else {
const secureEval = (code, context2) => {
const sandbox = {};
Object.entries(context2).forEach(([key, value]) => {
sandbox[key] = value;
});
return Function("sandbox", `with(sandbox) { return (${code}); }`)(sandbox);
};
result = secureEval(expression, mergedContext);
}
if (result === void 0 || result === null) {
return "";
}
return String(result);
} catch (error) {
console.error(`Error evaluating expression "${expression}":`, error);
return `[Error: ${error instanceof Error ? error.message : String(error)}]`;
}
});
}
exports.renderTemplateWithJS = renderTemplateWithJS;
//# sourceMappingURL=index.cjs.map
//# sourceMappingURL=index.cjs.map
;