UNPKG

js-in-strings

Version:

A library for rendering templates with JavaScript expressions

389 lines (318 loc) 11.6 kB
# js-in-strings A powerful JavaScript library for rendering templates with embedded JavaScript expressions, supporting advanced features like custom delimiters, context extensions, and sandboxing. ## Installation ```bash npm install js-in-strings # or yarn add js-in-strings # or bun add js-in-strings ``` ## Basic Usage ```javascript import { renderTemplateWithJS } from 'js-in-strings'; // Basic usage const template = 'Hello, {name}!'; const context = { name: 'World' }; const result = renderTemplateWithJS(template, context); // Output: "Hello, World!" ``` ## Features - **Full JavaScript Support**: Evaluate any valid JavaScript expression within templates - **Nested Properties**: Access deeply nested object properties - **Advanced JavaScript**: Use modern ES6+ features, complex expressions, and control flow - **Raw Value Extraction**: Return raw data types (arrays, objects, primitives) when needed - **Custom Delimiters**: Define your own delimiters instead of the default `{}` - **Context Extensions**: Extend the context with helper functions and utilities - **Robust Error Handling**: Graceful handling of syntax errors and runtime exceptions - **Lightweight**: Zero dependencies ## Examples ### String Template Rendering ```javascript import { renderTemplateWithJS } from 'js-in-strings'; // Basic expressions renderTemplateWithJS('The result is {a + b * 2}.', { a: 5, b: 3 }); // Output: "The result is 11." // Nested properties renderTemplateWithJS('Welcome, {user.profile.firstName}!', { user: { profile: { firstName: 'Jane' } } }); // Output: "Welcome, Jane!" // Array methods renderTemplateWithJS('Filtered items: {items.filter(i => i > 2).join(", ")}', { items: [1, 2, 3, 4, 5] } ); // Output: "Filtered items: 3, 4, 5" // Conditional expressions renderTemplateWithJS('Status: {isActive ? "Active" : "Inactive"}', { isActive: true }); // Output: "Status: Active" // Template literals renderTemplateWithJS('Message: {`Hello ${name}, today is ${new Date().toLocaleDateString()}`}', { name: 'Alice' } ); // Output: "Message: Hello Alice, today is 5/15/2025" (date will vary) ``` ### Raw Value Extraction Extract raw values (preserving their original data types) from expressions: ```javascript // Extract an array const arrayResult = renderTemplateWithJS( '{items.filter(i => i > 2)}', { items: [1, 2, 3, 4, 5] }, { returnRawValues: true } ); // Output: [3, 4, 5] (actual array, not a string) // Extract an object const objectResult = renderTemplateWithJS( '{user}', { user: { name: 'John', age: 30 } }, { returnRawValues: true } ); // Output: { name: 'John', age: 30 } (actual object) // Complex data transformations const transformedData = renderTemplateWithJS( '{data.filter(item => item.active).map(item => ({ id: item.id, upper: item.name.toUpperCase() }))}', { data: [ { id: 1, name: 'apple', active: true }, { id: 2, name: 'banana', active: false }, { id: 3, name: 'cherry', active: true } ] }, { returnRawValues: true } ); // Output: [{ id: 1, upper: 'APPLE' }, { id: 3, upper: 'CHERRY' }] ``` ### Advanced JavaScript Features ```javascript // Destructuring renderTemplateWithJS( 'The coordinates are {[x, y] = coords; `(${x},${y})`}', { coords: [10, 20] } ); // Output: "The coordinates are (10,20)" // Object destructuring renderTemplateWithJS( 'Contact: {({name, email} = user; `${name} <${email}>`)}', { user: { name: 'John Doe', email: 'john@example.com' } } ); // Output: "Contact: John Doe <john@example.com>" // IIFE (Immediately Invoked Function Expression) renderTemplateWithJS( 'Sum: {(() => { let sum = 0; for(let i = 1; i <= 5; i++) { sum += i; } return sum; })()}', {} ); // Output: "Sum: 15" // Class definitions renderTemplateWithJS( '{class Person { constructor(name) { this.name = name; } greet() { return `Hello, ${this.name}`; } }; const p = new Person("Alice"); p.greet()}', {}, { returnRawValues: true } ); // Output: "Hello, Alice" // Async/await with Promise.resolve renderTemplateWithJS( '{(async () => { const value = await Promise.resolve(42); return value; })()}', {}, { returnRawValues: true } ); // Output: Promise object that resolves to 42 ``` ### Custom Delimiters Use custom delimiters instead of the default `{}`: ```javascript // Using ${} delimiters (like template literals) renderTemplateWithJS( 'Hello, ${name}!', { name: 'World' }, { delimiters: ['${', '}'] } ); // Output: "Hello, World!" // Using custom delimiters renderTemplateWithJS( 'Hello, <<name>>!', { name: 'World' }, { delimiters: ['<<', '>>'] } ); // Output: "Hello, World!" ``` ### Context Extensions Extend the context with helper functions: ```javascript // Adding a formatting helper renderTemplateWithJS( 'Total: {formatCurrency(total)}', { total: 99.95 }, { contextExtensions: { formatCurrency: (amount) => `$${amount.toFixed(2)}` } } ); // Output: "Total: $99.95" // Combining context and extensions renderTemplateWithJS( '{formatList(items.map(item => item.name))}', { items: [ {name: 'Apple', category: 'Fruit'}, {name: 'Banana', category: 'Fruit'}, {name: 'Carrot', category: 'Vegetable'} ] }, { contextExtensions: { formatList: (items) => items.join(' • ') } } ); // Output: "Apple • Banana • Carrot" // Complex calculations with extensions renderTemplateWithJS( '{calculateTotal(items)}', { items: [ {name: 'Widget', price: 9.99, quantity: 2}, {name: 'Gadget', price: 14.95, quantity: 1} ] }, { returnRawValues: true, contextExtensions: { calculateTotal: (items) => { return items.reduce((sum, item) => sum + (item.price * item.quantity), 0); } } } ); // Output: 34.93 ``` ### Error Handling The library handles errors gracefully: ```javascript // Syntax error renderTemplateWithJS('{const x = }', {}); // Output: "[Error: Unexpected token '}'" // Runtime error renderTemplateWithJS('{undefined.property}', {}); // Output: "[Error: Cannot read property 'property' of undefined]" // Undefined variable renderTemplateWithJS('{nonExistentVar}', {}); // Output: "[Error: nonExistentVar is not defined]" ``` ## API Reference ### renderTemplateWithJS(template, context, options) Renders a template string with JavaScript expressions or extracts raw values from expressions. #### Parameters - `template` (string): The template string containing JavaScript expressions wrapped in delimiters - `context` (object): The context object containing values to be used in the template - `options` (object, optional): Configuration options - `returnRawValues` (boolean): If true, returns the raw evaluated value instead of converting to string - `delimiters` (array): Custom delimiters to use instead of the default `{}`. Must be an array with exactly two strings: `[openDelimiter, closeDelimiter]` - `contextExtensions` (object): Additional properties and functions to extend the context - `sandbox` (boolean): If true, restricts access to global objects for security - `allowedGlobals` (object): Custom global objects to be provided when in sandbox mode - `timeout` (number): Timeout in milliseconds for expression evaluation (default: 1000ms) - `unsafeEval` (boolean): If true, uses a more direct but less secure evaluation method. Only use this if you trust the template source and are experiencing issues with complex expressions. This can resolve certain string escaping issues. (default: false) #### Returns - When `options.returnRawValues` is `false` or not provided: - (string): The rendered template with all expressions evaluated and converted to strings - When `options.returnRawValues` is `true`: - (any): The raw value of the evaluated expression with its original data type preserved ## Advanced Use Cases ### Data Processing Pipeline ```javascript // Process data through multiple transformations const processedData = renderTemplateWithJS( '{ // Get raw data const data = rawData; // Filter active items const activeItems = data.filter(item => item.active); // Transform the data const transformed = activeItems.map(item => ({ id: item.id, name: item.name.toUpperCase(), price: item.price * 1.1 // Add 10% tax })); // Sort by price transformed.sort((a, b) => a.price - b.price); // Return the processed data return transformed; }', { rawData: [ { id: 1, name: 'apple', price: 1.99, active: true }, { id: 2, name: 'banana', price: 0.99, active: true }, { id: 3, name: 'cherry', price: 2.99, active: false }, { id: 4, name: 'date', price: 3.99, active: true } ] }, { returnRawValues: true } ); // Output: [ // { id: 2, name: 'BANANA', price: 1.089 }, // { id: 1, name: 'APPLE', price: 2.189 }, // { id: 4, name: 'DATE', price: 4.389 } // ] ``` ### Dynamic Report Generation ```javascript const report = renderTemplateWithJS( 'Sales Report: {reportDate}\n\n' + 'Total Sales: {formatCurrency(sales.reduce((sum, sale) => sum + sale.amount, 0))}\n' + 'Average Sale: {formatCurrency(sales.reduce((sum, sale) => sum + sale.amount, 0) / sales.length)}\n\n' + 'Top Performers:\n{sales.sort((a, b) => b.amount - a.amount).slice(0, 3).map(sale => `- ${sale.salesperson}: ${formatCurrency(sale.amount)}`).join("\n")}\n\n' + 'Sales by Category:\n{(() => {\n' + ' const byCategory = {};\n' + ' sales.forEach(sale => {\n' + ' byCategory[sale.category] = (byCategory[sale.category] || 0) + sale.amount;\n' + ' });\n' + ' return Object.entries(byCategory)\n' + ' .map(([category, amount]) => `- ${category}: ${formatCurrency(amount)}`)\n' + ' .join("\n");\n' + '})()}', { reportDate: new Date().toLocaleDateString(), sales: [ { salesperson: 'Alice', amount: 12500, category: 'Electronics' }, { salesperson: 'Bob', amount: 8700, category: 'Furniture' }, { salesperson: 'Charlie', amount: 15200, category: 'Electronics' }, { salesperson: 'Diana', amount: 9300, category: 'Clothing' }, { salesperson: 'Eve', amount: 10800, category: 'Furniture' } ] }, { contextExtensions: { formatCurrency: (amount) => `$${amount.toFixed(2)}` } } ); // Output: A formatted sales report with totals, averages, top performers, and sales by category ``` ## Troubleshooting ### String Escaping Issues If you encounter errors like `SyntaxError: Arg string terminates parameters early` or other string escaping issues when evaluating expressions with special characters, try enabling the `unsafeEval` option: ```javascript renderTemplateWithJS(template, context, { unsafeEval: true }); ``` This uses a more direct evaluation approach that can resolve issues with special characters in your expressions or context values. Only use this option when you trust the template source, as it uses a less sandboxed evaluation method. ### Complex Expressions For complex expressions with multiple statements, make sure to properly terminate statements with semicolons and consider using an IIFE (Immediately Invoked Function Expression) pattern for cleaner code: ```javascript renderTemplateWithJS('{ (() => { const x = 10; const y = 20; return x + y; })() }', {}); // Output: "30" ``` ### Context Access If you're having trouble accessing certain properties or methods from your context, check that: 1. The property exists in the context object 2. You're using the correct case (JavaScript is case-sensitive) 3. For nested properties, all parent objects exist ## License MIT