smartTemplate()
Mini template engine with support for fallbacks, conditionals, and async data resolution.
Utility Function
Templates
Async
Syntax
smartTemplate(template, data, options)Parameters
template string
Template string with placeholders in {{key}} format.
data Object
Data object containing values for template placeholders.
options object optional
Configuration options.
fallback- Default value for missing keys (default: '')strict- Throw error on missing keys (default: false)async- Support async data resolution (default: true)helpers- Custom helper functions
Returns
Promise<string>
- Promise resolving to the rendered template.Examples
Basic Template Rendering
// Simple variable substitution
const template = "Hello {{name}}, welcome to {{platform}}!";
const data = { name: "Alice", platform: "Lopos" };
const result = await smartTemplate(template, data);
console.log(result); // "Hello Alice, welcome to Lopos!"
// With missing data and fallback
const incompleteData = { name: "Bob" };
const resultWithFallback = await smartTemplate(template, incompleteData, {
fallback: "[missing]"
});
console.log(resultWithFallback); // "Hello Bob, welcome to [missing]!"Nested Object Access
const template = "User: {{user.name}} ({{user.profile.role}}) from {{user.location.city}}";
const data = {
user: {
name: "John Doe",
profile: { role: "Developer" },
location: { city: "New York", country: "USA" }
}
};
const result = await smartTemplate(template, data);
console.log(result); // "User: John Doe (Developer) from New York"Async Data Resolution
// Template with async data
const template = "Weather in {{city}}: {{weather.temperature}}°C, {{weather.description}}";
const data = {
city: "London",
weather: async () => {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 100));
return {
temperature: 18,
description: "Partly cloudy"
};
}
};
const result = await smartTemplate(template, data);
console.log(result); // "Weather in London: 18°C, Partly cloudy"
// Mixed sync and async data
const mixedTemplate = "Hello {{name}}, your balance is ${{balance}}";
const mixedData = {
name: "Alice",
balance: async () => {
// Simulate database query
await new Promise(resolve => setTimeout(resolve, 50));
return 1250.75;
}
};
const mixedResult = await smartTemplate(mixedTemplate, mixedData);
console.log(mixedResult); // "Hello Alice, your balance is $1250.75"Conditional Rendering
// Template with conditionals
const template = `
{{#if user.isPremium}}
Welcome, Premium Member {{user.name}}!
Your benefits: {{user.benefits}}
{{else}}
Hello {{user.name}}, upgrade to Premium for more features!
{{/if}}
`.trim();
const premiumUser = {
user: {
name: "Alice",
isPremium: true,
benefits: "Ad-free experience, Priority support"
}
};
const regularUser = {
user: {
name: "Bob",
isPremium: false
}
};
// Note: This example shows the template syntax
// Actual conditional support would need to be implemented
// in the smartTemplate function or via helpersCustom Helper Functions
const template = "{{formatCurrency amount}} on {{formatDate date}}";
const data = {
amount: 1234.56,
date: "2023-12-25T10:00:00Z"
};
const options = {
helpers: {
formatCurrency: (amount) => `$${amount.toFixed(2)}`,
formatDate: (dateStr) => {
const date = new Date(dateStr);
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
}
};
const result = await smartTemplate(template, data, options);
console.log(result); // "$1234.56 on December 25, 2023"Email Template System
class EmailTemplateEngine {
constructor() {
this.templates = new Map();
this.defaultHelpers = {
uppercase: (str) => str.toUpperCase(),
lowercase: (str) => str.toLowerCase(),
formatDate: (date) => new Date(date).toLocaleDateString(),
formatCurrency: (amount, currency = 'USD') =>
new Intl.NumberFormat('en-US', {
style: 'currency',
currency
}).format(amount)
};
}
registerTemplate(name, template) {
this.templates.set(name, template);
}
async renderTemplate(name, data, customHelpers = {}) {
const template = this.templates.get(name);
if (!template) {
throw new Error(`Template '${name}' not found`);
}
const helpers = { ...this.defaultHelpers, ...customHelpers };
return await smartTemplate(template, data, {
helpers,
fallback: '[MISSING]',
strict: false
});
}
async sendWelcomeEmail(userData) {
const template = `
Dear {{user.firstName}},
Welcome to {{platform.name}}! We're excited to have you join our community.
Your account details:
- Email: {{user.email}}
- Member since: {{formatDate user.joinDate}}
- Account type: {{uppercase user.accountType}}
{{#if user.referralCode}}
Your referral code: {{user.referralCode}}
Share it with friends to earn rewards!
{{/if}}
Best regards,
The {{platform.name}} Team
`.trim();
this.registerTemplate('welcome', template);
return await this.renderTemplate('welcome', {
user: userData,
platform: { name: 'Lopos Platform' }
});
}
async sendInvoiceEmail(invoiceData) {
const template = `
Invoice #{{invoice.number}}
Bill To:
{{customer.name}}
{{customer.address}}
Items:
{{#each invoice.items}}
- {{name}}: {{formatCurrency price}} x {{quantity}} = {{formatCurrency total}}
{{/each}}
Subtotal: {{formatCurrency invoice.subtotal}}
Tax: {{formatCurrency invoice.tax}}
Total: {{formatCurrency invoice.total}}
Due Date: {{formatDate invoice.dueDate}}
`.trim();
this.registerTemplate('invoice', template);
return await this.renderTemplate('invoice', invoiceData);
}
}
// Usage
const emailEngine = new EmailTemplateEngine();
const welcomeEmail = await emailEngine.sendWelcomeEmail({
firstName: "Alice",
email: "alice@example.com",
joinDate: "2023-12-01",
accountType: "premium",
referralCode: "ALICE123"
});
console.log(welcomeEmail);Dynamic Content Generation
// Generate dynamic reports
class ReportGenerator {
async generateSalesReport(salesData) {
const template = `
Sales Report - {{reportDate}}
================================
Total Sales: {{formatCurrency totalSales}}
Number of Orders: {{orderCount}}
Average Order Value: {{formatCurrency averageOrderValue}}
Top Products:
{{#each topProducts}}
{{@index}}. {{name}} - {{formatCurrency revenue}} ({{units}} units)
{{/each}}
Regional Performance:
{{#each regions}}
{{name}}: {{formatCurrency sales}} ({{percentage}}% of total)
{{/each}}
Generated on {{formatDate generatedAt}}
`.trim();
// Process sales data
const processedData = await this.processSalesData(salesData);
return await smartTemplate(template, processedData, {
helpers: {
formatCurrency: (amount) => `$${amount.toLocaleString()}`,
formatDate: (date) => new Date(date).toLocaleDateString()
}
});
}
async processSalesData(rawData) {
// Simulate data processing
await new Promise(resolve => setTimeout(resolve, 100));
return {
reportDate: "December 2023",
totalSales: 125000,
orderCount: 450,
averageOrderValue: 277.78,
topProducts: [
{ name: "Laptop Pro", revenue: 45000, units: 45 },
{ name: "Wireless Headphones", revenue: 28000, units: 140 },
{ name: "Smart Watch", revenue: 22000, units: 88 }
],
regions: [
{ name: "North America", sales: 62500, percentage: 50 },
{ name: "Europe", sales: 37500, percentage: 30 },
{ name: "Asia Pacific", sales: 25000, percentage: 20 }
],
generatedAt: new Date().toISOString()
};
}
}
const reportGenerator = new ReportGenerator();
const salesReport = await reportGenerator.generateSalesReport([]);
console.log(salesReport);Configuration File Generation
// Generate configuration files from templates
class ConfigGenerator {
async generateDockerCompose(services) {
const template = `
version: '3.8'
services:
{{#each services}}
{{name}}:
image: {{image}}
{{#if ports}}
ports:
{{#each ports}}
- "{{this}}"
{{/each}}
{{/if}}
{{#if environment}}
environment:
{{#each environment}}
{{@key}}: {{this}}
{{/each}}
{{/if}}
{{#if volumes}}
volumes:
{{#each volumes}}
- {{this}}
{{/each}}
{{/if}}
{{#if dependsOn}}
depends_on:
{{#each dependsOn}}
- {{this}}
{{/each}}
{{/if}}
{{/each}}
`.trim();
return await smartTemplate(template, { services });
}
async generateNginxConfig(config) {
const template = `
server {
listen {{port}};
server_name {{serverName}};
{{#if ssl.enabled}}
ssl_certificate {{ssl.certPath}};
ssl_certificate_key {{ssl.keyPath}};
{{/if}}
{{#each locations}}
location {{path}} {
{{#if proxy}}
proxy_pass {{proxy}};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
{{/if}}
{{#if root}}
root {{root}};
index {{index}};
{{/if}}
}
{{/each}}
}
`.trim();
return await smartTemplate(template, config);
}
}
const configGen = new ConfigGenerator();
const dockerConfig = await configGen.generateDockerCompose([
{
name: "web",
image: "nginx:alpine",
ports: ["80:80", "443:443"],
volumes: ["./nginx.conf:/etc/nginx/nginx.conf"]
},
{
name: "api",
image: "node:16-alpine",
ports: ["3000:3000"],
environment: {
NODE_ENV: "production",
DATABASE_URL: "postgresql://user:pass@db:5432/myapp"
},
dependsOn: ["db"]
},
{
name: "db",
image: "postgres:13",
environment: {
POSTGRES_DB: "myapp",
POSTGRES_USER: "user",
POSTGRES_PASSWORD: "pass"
},
volumes: ["postgres_data:/var/lib/postgresql/data"]
}
]);
console.log(dockerConfig);