js-in-strings
Version:
A library for rendering templates with JavaScript expressions
1 lines • 13.6 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts"],"names":["code","context"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * js-in-strings\n * A library for rendering templates with JavaScript expressions\n */\n\n/**\n * Configuration options for renderTemplateWithJS\n */\nexport interface RenderOptions {\n /**\n * If true, returns the raw evaluated value instead of converting to a string\n */\n returnRawValues?: boolean;\n \n /**\n * If true, restricts access to global objects for security\n */\n sandbox?: boolean;\n \n /**\n * Custom global objects to be provided when in sandbox mode\n */\n allowedGlobals?: Record<string, any>;\n \n /**\n * Timeout in milliseconds for expression evaluation\n * Default: 1000ms (1 second)\n */\n timeout?: number;\n \n /**\n * Custom delimiters for expressions\n * Default: ['{', '}']\n */\n delimiters?: [string, string];\n \n /**\n * Additional context properties to extend the main context\n */\n contextExtensions?: Record<string, any>;\n \n /**\n * If true, uses a more direct but less secure evaluation method\n * Only use this if you trust the template source\n * Default: false\n */\n unsafeEval?: boolean;\n}\n\n/**\n * Renders a template string with JavaScript expressions\n * \n * @param template - The template string containing JavaScript expressions wrapped in {}\n * @param context - The context object containing values to be used in the template\n * @param options - Optional configuration options\n * @returns The rendered template with all expressions evaluated, or the raw evaluated expression if returnRawValues is true\n * \n * @example\n * ```ts\n * // String template rendering\n * const template = \"Hello, {name}! Your score is {Math.round(score)}.\";\n * const context = { name: \"John\", score: 95.6 };\n * const result = renderTemplateWithJS(template, context);\n * // \"Hello, John! Your score is 96.\"\n * \n * // Raw value extraction\n * const data = renderTemplateWithJS(\"{items.filter(i => i > 2)}\", { items: [1, 2, 3, 4, 5] }, { returnRawValues: true });\n * // Returns the actual array: [3, 4, 5]\n * \n * // Custom delimiters\n * const customResult = renderTemplateWithJS(\"Hello, ${name}!\", { name: \"World\" }, { delimiters: [\"${\" ,\"}\"] });\n * // \"Hello, World!\"\n * ```\n */\nexport function renderTemplateWithJS<T = string>(\n template: string, \n context: Record<string, any>, \n options: RenderOptions = {}\n): T | string {\n // Set up delimiters - default to {} if not specified\n const [openDelimiter, closeDelimiter] = options.delimiters || ['{', '}'];\n \n // Escape special characters for regex\n const escOpenDelimiter = openDelimiter.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const escCloseDelimiter = closeDelimiter.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n \n // Create regex pattern for the specified delimiters\n const expressionRegex = new RegExp(`${escOpenDelimiter}([^${escOpenDelimiter}${escCloseDelimiter}]*?)${escCloseDelimiter}`, 'g');\n const singleExpressionRegex = new RegExp(`^${escOpenDelimiter}([\\\\s\\\\S]*?)${escCloseDelimiter}$`);\n \n // Merge context with any extensions\n const mergedContext = {\n ...context,\n ...options.contextExtensions\n };\n \n // If the template is a single expression and returnRawValues is true, return the raw value\n if (options.returnRawValues) {\n const trimmedTemplate = template.trim();\n const match = trimmedTemplate.match(singleExpressionRegex);\n if (match) {\n try {\n const expression = match[1].trim();\n const contextKeys = Object.keys(mergedContext);\n const contextValues = Object.values(mergedContext);\n \n // Check for IIFE patterns first\n const isIIFE = /^\\s*\\(.*\\)\\s*\\(.*\\)/.test(expression) || // (function(){})() pattern\n /^\\s*\\(.*=>.*\\)\\s*\\(/.test(expression); // (() => {})() pattern\n \n // Check if the expression contains declarations or complex statements\n const hasDeclarations = /\\b(const|let|var|function|class)\\b/.test(expression);\n const hasMultipleStatements = expression.includes(';');\n \n let result: any;\n \n // Use a more robust approach to evaluate expressions\n try {\n // Create a safe evaluation environment\n if (options.unsafeEval) {\n // Direct eval approach - only use when templates are trusted\n // This is less secure but avoids string escaping issues\n const contextVars = Object.entries(mergedContext)\n .map(([key, value]) => `const ${key} = ${JSON.stringify(value)};`)\n .join('\\n');\n \n // Use direct eval with proper context setup\n result = eval(`${contextVars}\\n${expression}`);\n } else {\n // Safer approach using indirect evaluation\n // Prepare the expression based on its type\n let code: string;\n \n if (isIIFE) {\n // For IIFE patterns\n code = `(${expression})`;\n } else if (hasDeclarations || hasMultipleStatements) {\n // For complex expressions with declarations\n if (expression.includes('return ')) {\n code = `(function() { ${expression} })();`;\n } else {\n // For expressions with declarations but no return, add a return\n const lastSemicolonIndex = expression.lastIndexOf(';');\n if (lastSemicolonIndex !== -1 && lastSemicolonIndex < expression.length - 1) {\n // There's content after the last semicolon, return that\n const lastExpression = expression.substring(lastSemicolonIndex + 1).trim();\n code = `(function() { ${expression.substring(0, lastSemicolonIndex + 1)} return ${lastExpression}; })();`;\n } else {\n // No obvious return value\n code = `(function() { ${expression}; return undefined; })();`;\n }\n }\n } else {\n // For simple expressions\n code = expression;\n }\n \n // Create a secure context for evaluation\n const secureEval = (code: string, context: Record<string, any>) => {\n // Create a proxy-based sandbox to safely evaluate code\n const sandbox = {};\n Object.entries(context).forEach(([key, value]) => {\n (sandbox as any)[key] = value;\n });\n \n // Use indirect eval through a function to avoid scope issues\n const indirectEval = (code: string) => {\n // This is safer than direct eval\n return Function('sandbox', `with(sandbox) { return ${code}; }`)(sandbox);\n };\n \n return indirectEval(code);\n };\n \n // Execute the expression in the secure context\n result = secureEval(code, mergedContext);\n }\n } catch (error) {\n console.error(`Error evaluating expression:`, error);\n return `[Error: ${error instanceof Error ? error.message : String(error)}]` as any;\n }\n \n return result as T;\n } catch (error) {\n console.error(`Error evaluating expression:`, error);\n return `[Error: ${error instanceof Error ? error.message : String(error)}]` as any;\n }\n }\n }\n \n // Replace each matched expression with its evaluated result\n return template.replace(expressionRegex, (match, expression) => {\n try {\n // Use the same robust approach for evaluating expressions in templates\n let result;\n \n if (options.unsafeEval) {\n // Direct eval approach - only use when templates are trusted\n const contextVars = Object.entries(mergedContext)\n .map(([key, value]) => `const ${key} = ${JSON.stringify(value)};`)\n .join('\\n');\n \n // Use direct eval with proper context setup\n result = eval(`${contextVars}\\n(${expression})`);\n } else {\n // Create a secure context for evaluation\n const secureEval = (code: string, context: Record<string, any>) => {\n // Create a sandbox to safely evaluate code\n const sandbox = {};\n Object.entries(context).forEach(([key, value]) => {\n (sandbox as any)[key] = value;\n });\n \n // Use indirect eval through a function to avoid scope issues\n return Function('sandbox', `with(sandbox) { return (${code}); }`)(sandbox);\n };\n \n // Execute the expression in the secure context\n result = secureEval(expression, mergedContext);\n }\n \n // Handle undefined and null values\n if (result === undefined || result === null) {\n return '';\n }\n \n // Convert the result to string for template rendering\n return String(result);\n } catch (error) {\n // Return an error message if evaluation fails\n console.error(`Error evaluating expression \"${expression}\":`, error);\n return `[Error: ${error instanceof Error ? error.message : String(error)}]`;\n }\n });\n}\n"]}