Asynchronous templates for the browser and server (LinkedIn fork)
Template parsing functionality that converts Dust template source code into Abstract Syntax Trees (AST) using a PEG.js generated parser.
Parses Dust template source into an Abstract Syntax Tree for compilation or analysis.
/**
* Parses template source into Abstract Syntax Tree
* @param source - Dust template source code
* @returns Abstract Syntax Tree object representation
* @throws SyntaxError with detailed location information for invalid syntax
*/
function parse(source: string): ASTNode;
interface ASTNode {
/** Node type identifier */
[0]: string;
/** Node attributes and properties */
[1]?: any;
/** Child nodes array */
[2]?: ASTNode[];
}Usage Examples:
const dust = require('dustjs-linkedin');
// Basic template parsing
const source = 'Hello {name}!';
const ast = dust.parse(source);
console.log(ast);
// Output: ['body', {}, [['buffer', 'Hello '], ['reference', ['path', ['key', 'name']], [], {}], ['buffer', '!']]]
// Complex template with sections
const complexSource = `
{#users}
<li>{name} - {role}</li>
{:else}
<li>No users</li>
{/users}
`;
try {
const complexAst = dust.parse(complexSource);
console.log('AST nodes:', complexAst[2].length);
console.log('First node type:', complexAst[2][0][0]);
} catch (err) {
console.error('Parse error:', err.message);
}Understanding the structure of parsed AST nodes for template analysis and manipulation.
// Common AST node types:
// Body node (root container)
type BodyNode = ['body', {}, ASTNode[]];
// Buffer node (static text)
type BufferNode = ['buffer', string];
// Reference node (variable substitution)
type ReferenceNode = ['reference', PathNode, FilterNode[], ParamsNode];
// Section node (conditional/loop)
type SectionNode = ['section', PathNode, ContextNode, ParamsNode, BodyNode];
// Exists/NotExists nodes (conditional checks)
type ExistsNode = ['exists', PathNode, BodyNode];
type NotExistsNode = ['notexists', PathNode, BodyNode];
// Block node (template inheritance)
type BlockNode = ['block', KeyNode, BodyNode];
// Partial node (template inclusion)
type PartialNode = ['partial', PathNode | InlineNode, ContextNode, ParamsNode];
// Helper node (custom function call)
type HelperNode = ['helper', KeyNode, ContextNode, ParamsNode, BodyNode];
// Path node (variable path)
type PathNode = ['path', KeyNode[], FiltersNode[]];
// Key node (identifier)
type KeyNode = ['key', string];
// Inline node (literal value)
type InlineNode = ['inline', string];Usage Examples:
// Analyze AST structure
function analyzeTemplate(source) {
const ast = dust.parse(source);
function walkNodes(node, depth = 0) {
const indent = ' '.repeat(depth);
console.log(`${indent}Node: ${node[0]}`);
if (node[0] === 'reference') {
const path = node[1];
console.log(`${indent} Variable: ${path[1][0][1]}`); // Extract key name
}
if (node[0] === 'section') {
const path = node[1];
console.log(`${indent} Section: ${path[1][0][1]}`); // Extract section name
}
// Walk child nodes
if (node[2] && Array.isArray(node[2])) {
node[2].forEach(child => walkNodes(child, depth + 1));
}
}
walkNodes(ast);
}
// Analyze a template
analyzeTemplate('{#users}{name}{/users}');
// Output:
// Node: body
// Node: section
// Section: users
// Node: body
// Node: reference
// Variable: nameComprehensive error handling with detailed location information for debugging template syntax issues.
interface ParseSyntaxError extends SyntaxError {
/** Error message describing the syntax issue */
message: string;
/** Expected tokens or syntax elements */
expected: any[];
/** Actually found token or character */
found: string;
/** Location information for the error */
location: {
start: { offset: number; line: number; column: number };
end: { offset: number; line: number; column: number };
};
/** Error type name */
name: 'SyntaxError';
}Usage Examples:
// Handle parsing errors with location information
function parseWithErrorHandling(source, templateName) {
try {
return dust.parse(source);
} catch (err) {
if (err instanceof SyntaxError && err.location) {
console.error(`Parse error in template "${templateName}":
Message: ${err.message}
Line: ${err.location.start.line}
Column: ${err.location.start.column}
Expected: ${JSON.stringify(err.expected)}
Found: ${err.found}
Position: ${err.location.start.offset}`);
// Show source context
const lines = source.split('\n');
const errorLine = lines[err.location.start.line - 1];
const pointer = ' '.repeat(err.location.start.column - 1) + '^';
console.error(`Source: ${errorLine}`);
console.error(` ${pointer}`);
} else {
console.error('Unexpected parse error:', err);
}
throw err;
}
}
// Usage with error handling
try {
parseWithErrorHandling('{#unclosed', 'my-template');
} catch (err) {
// Error details logged above, handle appropriately
}Advanced AST manipulation for template transformation and analysis.
/**
* Filter and transform AST nodes during compilation
* @param context - Compilation context
* @param node - AST node to filter
* @returns Transformed AST node
*/
function filterNode(context: any, node: ASTNode): ASTNode;Usage Examples:
// Custom AST transformation
function transformTemplate(source) {
// Parse to AST
const ast = dust.parse(source);
// Apply transformations using compiler
const context = {}; // Compilation context
const transformedAst = dust.compiler.filterNode(context, ast);
// Compile back to executable code
const code = dust.compiler.compileNode(context, transformedAst);
return code;
}
// Custom node visitor
function visitNodes(node, visitor) {
// Visit current node
visitor(node);
// Visit child nodes recursively
if (node[2] && Array.isArray(node[2])) {
node[2].forEach(child => visitNodes(child, visitor));
}
}
// Example: Find all variable references
function findReferences(source) {
const ast = dust.parse(source);
const references = [];
visitNodes(ast, (node) => {
if (node[0] === 'reference') {
const path = node[1];
if (path && path[1] && path[1][0] && path[1][0][1]) {
references.push(path[1][0][1]); // Extract variable name
}
}
});
return references;
}
// Find variables in template
const variables = findReferences('Hello {name}! You have {count} messages.');
console.log(variables); // ['name', 'count']Understanding how different Dust template syntax elements are represented in the AST:
// Variable references: {name}
const refAst = dust.parse('{name}');
// ['body', {}, [['reference', ['path', ['key', 'name']], [], {}]]]
// Sections: {#items}...{/items}
const sectionAst = dust.parse('{#items}Item: {name}{/items}');
// ['body', {}, [['section', ['path', ['key', 'items']], [...], {}]]]
// Conditionals: {?exists}...{/exists}
const existsAst = dust.parse('{?exists}Content{/exists}');
// ['body', {}, [['exists', ['path', ['key', 'exists']], [...]]]]
// Filters: {name|capitalize}
const filteredAst = dust.parse('{name|capitalize}');
// Reference node with filters array populated
// Parameters: {#section param="value"}...{/section}
const paramAst = dust.parse('{#section param="value"}Content{/section}');
// Section node with params object containing parameter definitions
// Blocks: {+title}Default Title{/title}
const blockAst = dust.parse('{+title}Default{/title}');
// ['body', {}, [['block', ['key', 'title'], [...]]]]
// Partials: {>"template" data=context /}
const partialAst = dust.parse('{>"template"/}');
// ['body', {}, [['partial', ['inline', 'template'], [...], {}]]]
// Helpers: {@helper param="value"}...{/helper}
const helperAst = dust.parse('{@helper param="value"}Content{/helper}');
// ['body', {}, [['helper', ['key', 'helper'], [...], {...}, [...]]]]The parser understands Dust template context and nesting rules:
// Nested sections are properly parsed
const nested = dust.parse(`
{#users}
{name}
{#posts}
{title}
{/posts}
{/users}
`);
// Alternative section syntax
const alternative = dust.parse(`
{#users}
Active user: {name}
{:else}
No users found
{/users}
`);
// Complex path references
const paths = dust.parse('{user.profile.name} and {items[0].title}');The parser is optimized for template compilation performance:
// For performance-critical applications, cache parsed ASTs
const astCache = new Map();
function getCachedAst(source, templateName) {
if (astCache.has(templateName)) {
return astCache.get(templateName);
}
const ast = dust.parse(source);
astCache.set(templateName, ast);
return ast;
}Install with Tessl CLI
npx tessl i tessl/npm-dustjs-linkedin