Asynchronous templates for the browser and server (LinkedIn fork)
Built-in and extensible filtering system for data transformation with automatic escaping support and custom filter registration.
Dust provides several built-in filters for common data transformations and security escaping.
/**
* Built-in filter functions
*/
const filters: {
/** HTML escape filter - escapes HTML entities for safe output */
h: (value: any) => string;
/** JavaScript string escape filter - escapes for safe JS string literals */
j: (value: any) => string;
/** URL encode filter - encodes using encodeURI */
u: (value: any) => string;
/** URL component encode filter - encodes using encodeURIComponent */
uc: (value: any) => string;
/** JSON stringify filter - safe JSON conversion for JavaScript */
js: (value: any) => string;
/** JSON parse filter - parses JSON strings to objects */
jp: (value: any) => any;
};Usage Examples:
const dust = require('dustjs-linkedin');
// Template usage with built-in filters:
// {name|h} - HTML escape
// {script|j} - JavaScript string escape
// {url|u} - URL encode
// {param|uc} - URL component encode
// {data|js} - JSON stringify
// {jsonString|jp} - JSON parse
// Test built-in filters directly
console.log(dust.filters.h('<script>alert("xss")</script>'));
// Output: "<script>alert("xss")</script>"
console.log(dust.filters.j('Hello "world"\nNew line'));
// Output: "Hello \\\"world\\\"\\nNew line"
console.log(dust.filters.u('hello world'));
// Output: "hello%20world"
console.log(dust.filters.uc('hello@world.com'));
// Output: "hello%40world.com"
console.log(dust.filters.js({name: 'test', value: 123}));
// Output: '{"name":"test","value":123}'
console.log(dust.filters.jp('{"name":"test","value":123}'));
// Output: {name: 'test', value: 123}Core function for applying filters to values with automatic escaping.
/**
* Applies filter chain to string value
* @param string - Input value to filter
* @param auto - Automatic filter (usually 'h' for HTML escape)
* @param filters - Array of filter names to apply in sequence
* @param context - Template context for filter execution
* @returns Filtered string value
*/
function filter(
string: any,
auto: string | null,
filters: string[],
context: Context
): string;Usage Examples:
const context = dust.context({});
// Apply single filter
const htmlEscaped = dust.filter('<script>alert("xss")</script>', null, ['h'], context);
console.log(htmlEscaped); // "<script>alert("xss")</script>"
// Apply multiple filters in sequence
const multiFiltered = dust.filter('Hello World', null, ['j', 'u'], context);
// First applies 'j' (JavaScript escape), then 'u' (URL encode)
// Apply with automatic filter
const autoFiltered = dust.filter('<div>Content</div>', 'h', [], context);
console.log(autoFiltered); // "<div>Content</div>" (auto HTML escaped)
// Chain filters
const chained = dust.filter({name: 'test'}, null, ['js', 'u'], context);
// First converts to JSON, then URL encodes the resultIndividual escape functions used by filters and available for direct use.
/**
* HTML entity escape function
* @param string - String to escape
* @returns String with HTML entities escaped
*/
function escapeHtml(string: any): string;
/**
* JavaScript string escape function
* @param string - String to escape for JS
* @returns String safe for JavaScript string literals
*/
function escapeJs(string: any): string;
/**
* Safe JSON stringify function
* @param obj - Object to stringify
* @returns JSON string safe for JavaScript execution
*/
function escapeJSON(obj: any): string;Usage Examples:
// Direct use of escape functions
const htmlSafe = dust.escapeHtml('<script>alert("xss")</script>');
console.log(htmlSafe); // "<script>alert("xss")</script>"
const jsSafe = dust.escapeJs('Line 1\nLine 2\t"Quote"');
console.log(jsSafe); // "Line 1\\nLine 2\\t\\\"Quote\\\""
const jsonSafe = dust.escapeJSON({
name: 'Test',
code: '<script>alert("xss")</script>',
special: '\u2028\u2029' // Line/paragraph separators
});
console.log(jsonSafe); // Safely escaped JSON string
// Use in custom helpers
dust.helpers.safeOutput = (chunk, context, bodies, params) => {
const rawData = params.data;
const escaped = dust.escapeHtml(rawData);
return chunk.write(escaped);
};How to register custom filters for template use.
/**
* Register custom filter by adding to filters object
*/
dust.filters.customFilter = (value: any) => string;Usage Examples:
// Register a simple text transformation filter
dust.filters.capitalize = (value) => {
if (typeof value !== 'string') return value;
return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
};
// Register a number formatting filter
dust.filters.currency = (value) => {
const number = parseFloat(value);
if (isNaN(number)) return value;
return '$' + number.toFixed(2);
};
// Register a date formatting filter
dust.filters.dateFormat = (value) => {
const date = new Date(value);
if (isNaN(date.getTime())) return value;
return date.toLocaleDateString('en-US');
};
// Register a text truncation filter
dust.filters.truncate = (value) => {
if (typeof value !== 'string') return value;
return value.length > 50 ? value.substring(0, 50) + '...' : value;
};
// Template usage:
// {name|capitalize}
// {price|currency}
// {date|dateFormat}
// {description|truncate}More complex filter implementations with error handling and configuration.
// Filter with configuration
dust.filters.truncateBy = (value, length = 50) => {
if (typeof value !== 'string') return value;
const maxLength = parseInt(length) || 50;
return value.length > maxLength ? value.substring(0, maxLength) + '...' : value;
};
// Filter with multiple transformations
dust.filters.slug = (value) => {
if (typeof value !== 'string') return value;
return value
.toLowerCase()
.replace(/[^\w\s-]/g, '') // Remove special characters
.replace(/\s+/g, '-') // Replace spaces with hyphens
.replace(/-+/g, '-') // Collapse multiple hyphens
.trim('-'); // Remove leading/trailing hyphens
};
// Filter with error handling
dust.filters.safeNumber = (value) => {
try {
const number = parseFloat(value);
return isNaN(number) ? '0' : number.toString();
} catch (err) {
dust.log('Filter error in safeNumber: ' + err.message, 'ERROR');
return '0';
}
};
// Filter that uses context (advanced - requires custom implementation)
dust.filters.translate = function(value, context) {
const translations = context.get('translations') || {};
return translations[value] || value;
};Understanding how multiple filters work together in sequence.
// Filters are applied left to right
// Template: {name|capitalize|truncate}
// 1. First applies capitalize filter
// 2. Then applies truncate filter to the result
// Example filter chain execution:
const input = 'hello world this is a long string';
// Step 1: Apply capitalize
const step1 = dust.filters.capitalize(input); // "Hello world this is a long string"
// Step 2: Apply truncate
const final = dust.filters.truncate(step1); // "Hello world this is a long string..."
// Register filters that work well together
dust.filters.clean = (value) => {
if (typeof value !== 'string') return value;
return value.trim().replace(/\s+/g, ' '); // Clean whitespace
};
dust.filters.title = (value) => {
if (typeof value !== 'string') return value;
return value.replace(/\w\S*/g, (txt) =>
txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
);
};
// Template usage for chaining:
// {text|clean|title} - First clean whitespace, then title caseDust automatically applies HTML escaping by default for security.
// By default, all variable references are HTML escaped
// Template: {userInput}
// If userInput = '<script>alert("xss")</script>'
// Output: <script>alert("xss")</script>
// Disable auto-escaping with 's' filter (suppress)
// Template: {userInput|s}
// Output: <script>alert("xss")</script> (DANGEROUS!)
// Override auto-escape with specific filter
// Template: {userInput|j} - JavaScript escape instead of HTMLControl automatic escaping behavior in templates.
// The default auto-escape filter is 'h' (HTML escape)
// This is applied automatically to all references unless:
// 1. Explicitly suppressed with |s
// 2. Overridden with another filter
// Example configurations in templates:
// {value} - Auto HTML escaped
// {value|s} - No escaping (suppress)
// {value|j} - JavaScript escaped (overrides auto HTML)
// {value|h|j} - HTML escaped, then JavaScript escaped
// {value|s|h} - Suppress auto, then explicitly HTML escapeBest practices for safe filter usage.
// SAFE: Always escape user content for HTML output
// Template: <div>{userContent}</div>
// Automatically HTML escaped by default
// DANGEROUS: Suppressing escaping
// Template: <div>{userContent|s}</div>
// Only use |s for trusted content!
// SAFE: Appropriate escaping for context
// Template: <script>var data = "{jsonData|js}";</script>
// Uses JavaScript escaping for script context
// SAFE: URL context escaping
// Template: <a href="?q={query|uc}">Link</a>
// Uses URL component encoding for query parameters
// Register security-focused filters
dust.filters.stripTags = (value) => {
if (typeof value !== 'string') return value;
return value.replace(/<[^>]*>/g, ''); // Remove HTML tags
};
dust.filters.allowedTags = (value) => {
if (typeof value !== 'string') return value;
// Allow only specific safe tags
return value.replace(/<(?!\/?(?:b|i|em|strong|p|br)\b)[^>]*>/gi, '');
};Best practices for efficient filter usage.
// EFFICIENT: Simple, focused filters
dust.filters.upper = (value) => {
return typeof value === 'string' ? value.toUpperCase() : value;
};
// LESS EFFICIENT: Complex processing in filters
dust.filters.complexProcess = (value) => {
// Avoid heavy computations in filters
// Consider doing this in helpers or before rendering
return expensiveTransformation(value);
};
// GOOD: Cache expensive computations
const computationCache = new Map();
dust.filters.cachedTransform = (value) => {
if (computationCache.has(value)) {
return computationCache.get(value);
}
const result = expensiveComputation(value);
computationCache.set(value, result);
return result;
};Robust error handling patterns for custom filters.
// Robust filter with error handling
dust.filters.robustFilter = (value) => {
try {
// Type checking
if (value === null || value === undefined) {
return '';
}
// Safe conversion
const stringValue = String(value);
// Process with error handling
return processValue(stringValue);
} catch (err) {
// Log error for debugging
dust.log(`Filter error: ${err.message}`, 'ERROR');
// Return safe fallback
return String(value || '');
}
};
// Filter with validation
dust.filters.emailMask = (value) => {
if (typeof value !== 'string') return value;
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
return '[Invalid Email]';
}
// Mask email
const [local, domain] = value.split('@');
const maskedLocal = local.charAt(0) + '*'.repeat(local.length - 2) + local.charAt(local.length - 1);
return `${maskedLocal}@${domain}`;
};Patterns for testing custom filters.
// Test custom filters
function testFilters() {
const testCases = [
{ input: 'hello world', expected: 'Hello World', filter: 'title' },
{ input: 123.456, expected: '$123.46', filter: 'currency' },
{ input: '<script>alert()</script>', expected: '[sanitized]', filter: 'sanitize' }
];
testCases.forEach(testCase => {
const result = dust.filters[testCase.filter](testCase.input);
console.log(`Filter ${testCase.filter}: ${result === testCase.expected ? 'PASS' : 'FAIL'}`);
});
}
// Unit test helper for filters
function testFilter(filterName, testCases) {
const filter = dust.filters[filterName];
if (!filter) {
console.error(`Filter ${filterName} not found`);
return;
}
testCases.forEach((testCase, index) => {
const result = filter(testCase.input);
const passed = result === testCase.expected;
console.log(`Test ${index + 1}: ${passed ? 'PASS' : 'FAIL'}`);
if (!passed) {
console.log(` Expected: ${testCase.expected}`);
console.log(` Actual: ${result}`);
}
});
}Install with Tessl CLI
npx tessl i tessl/npm-dustjs-linkedin