GitHub

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 helpers

Custom 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);