@fearlessfara/velocits
v1.2.0
Published
TypeScript Apache Velocity engine with 1:1 Java compatibility
Maintainers
Readme
VelociTS
A TypeScript implementation of the Apache Velocity Template Language (VTL) with Java compatibility. Built for both Node.js and browser environments using modern TypeScript best practices.
Features
- Full VTL Support: Complete Apache Velocity Template Language implementation - all directives (
#set,#if,#foreach,#break,#stop), expressions, and variable references - File-Based Templates: Load and cache templates from filesystem using resource loaders (Node.js only)
- Java-Compatible API: Feature parity with Apache Velocity Java engine - same methods, same behavior, tested against Java reference implementation
- Configuration System: Complete properties/options support with all Java RuntimeConstants
- Stream Support: Callback-based output (Writer equivalent) and async input (Reader equivalent)
- Universal: Works in Node.js and browsers (UMD, ESM)
- Type-Safe: Written in TypeScript with strict type checking
- Zero Dependencies: Only runtime dependency is Chevrotain for parsing
Installation
npm install @fearlessfara/velocitsUsage
Node.js / ESM
import { VelocityEngine } from '@fearlessfara/velocits';
const engine = new VelocityEngine();
const output = engine.render('Hello, $name!', { name: 'World' });
console.log(output); // "Hello, World!"Browser (UMD)
<script src="https://unpkg.com/@fearlessfara/velocits"></script>
<script>
const engine = new VelociTS.VelocityEngine();
const output = engine.render('Hello, $name!', { name: 'World' });
console.log(output); // "Hello, World!"
</script>String Templates
import { VelocityEngine } from '@fearlessfara/velocits';
const engine = new VelocityEngine();
const template = `
#set($items = ["apple", "banana", "cherry"])
#foreach($item in $items)
- $item
#end
`;
const output = engine.render(template, {});
console.log(output);
// Output:
// - apple
// - banana
// - cherryFile-Based Templates (Node.js only)
import { VelocityEngine, RuntimeConstants } from '@fearlessfara/velocits';
// Configure engine with file resource loader
const engine = new VelocityEngine({
fileResourceLoaderPath: './templates',
fileResourceLoaderCache: true
});
engine.init();
// Load and render a template file
const output = engine.mergeTemplate('welcome.vtl', {
name: 'World',
items: ['one', 'two', 'three']
});
// Or get the template object first
const template = engine.getTemplate('welcome.vtl');
const output2 = template.merge({ name: 'User' });Using RuntimeConstants
import { VelocityEngine, RuntimeConstants } from '@fearlessfara/velocits';
const engine = new VelocityEngine();
// Set properties using RuntimeConstants
engine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, './templates');
engine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_CACHE, true);
engine.setProperty(RuntimeConstants.INPUT_ENCODING, 'UTF-8');
engine.setProperty(RuntimeConstants.SPACE_GOBBLING, 'lines');
// Or use setProperties with a Map
const props = new Map();
props.set(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, './templates');
props.set(RuntimeConstants.FILE_RESOURCE_LOADER_CACHE, true);
engine.setProperties(props);
engine.init();
// Check if a template exists
if (engine.resourceExists('template.vtl')) {
const output = engine.mergeTemplate('template.vtl', context);
}Stream Support (Writer/Reader Equivalents)
import { VelocityEngine } from '@fearlessfara/velocits';
const engine = new VelocityEngine();
// Callback-based output (Java Writer equivalent)
let output = '';
engine.evaluate(
{ name: 'World' },
'Hello, $name!',
'myTemplate',
(chunk) => { output += chunk; }
);
// Async input support (Java Reader equivalent)
const result = await engine.evaluateReader(
{ message: 'Async content' },
async () => {
// Load template from async source
const templateContent = await fetchTemplate();
return templateContent;
}
);
// Combined: async input + callback output
await engine.evaluateReader(
context,
async () => await loadTemplate(),
'logTag',
(chunk) => process.stdout.write(chunk)
);Registering Utility Methods and Objects
Similar to Java Velocity's context.put() functionality, you can register utility classes, objects, and helper methods in the context for use in templates. This is useful for providing common utilities like formatting, calculations, or custom business logic.
Static Utility Classes
import { VelocityEngine } from '@fearlessfara/velocits';
// Define a utility class with static methods
class MathUtil {
static add(a: number, b: number): number {
return a + b;
}
static max(...numbers: number[]): number {
return Math.max(...numbers);
}
}
const engine = new VelocityEngine();
// Register the class in the context (like Java's context.put("MathUtil", MathUtil.class))
const context = {
MathUtil: MathUtil,
x: 5,
y: 10
};
const template = 'Result: $MathUtil.add($x, $y)';
const output = engine.render(template, context);
// Output: "Result: 15"Instance-Based Utility Objects
// Define a utility class
class StringUtil {
capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
truncate(str: string, maxLen: number): string {
return str.length > maxLen ? str.substring(0, maxLen) + '...' : str;
}
}
// Create an instance and register it
const util = new StringUtil();
const context = {
util: util,
name: 'velocity'
};
const template = 'Upper: $util.capitalize($name)';
const output = engine.render(template, context);
// Output: "Upper: Velocity"Custom Utility Objects with Inline Functions
// Register an object with utility methods
const context = {
util: {
formatCurrency: (amount: number) => '$' + amount.toFixed(2),
join: (arr: any[], separator: string) => arr.join(separator),
isEven: (n: number) => n % 2 === 0
},
price: 42.50,
items: ['apple', 'banana', 'cherry']
};
const template = `
Price: $util.formatCurrency($price)
Items: $util.join($items, ", ")
`;
const output = engine.render(template, context);
// Output:
// Price: $42.50
// Items: apple, banana, cherryBuilt-in JavaScript Objects
// You can use JavaScript's built-in objects like Math
const context = {
Math: Math,
number: 2.7
};
const template = 'Ceiling: $Math.ceil($number)';
const output = engine.render(template, context);
// Output: "Ceiling: 3"Using Utilities in Control Structures
Utility methods work seamlessly with VTL directives like #if and #foreach:
const context = {
util: {
isEven: (n: number) => n % 2 === 0,
square: (n: number) => n * n
},
numbers: [1, 2, 3, 4, 5]
};
const template = `
#foreach($n in $numbers)
$n: #if($util.isEven($n))even, square = $util.square($n)#else odd#end
#end
`;
const output = engine.render(template, context);
// Output:
// 1: odd
// 2: even, square = 4
// 3: odd
// 4: even, square = 16
// 5: oddFor more examples, see src/examples/utilityRegistration.ts.
Supported Features
Template Language
- Variables:
$variable,$!silent,${formal} - Directives:
#set($var = value)- Variable assignment#if/#elseif/#else/#end- Conditional logic#foreach($item in $list)/#end- Iteration#break- Break from loops#stop- Stop template rendering
- Expressions: Literals, member access, method calls, arrays, maps, operators
- Type Coercion: Follows Apache Velocity semantics for truthiness and type conversion
- Scoping: Proper variable scoping with foreach loop variables
Engine Features (Java API Parity)
- File-Based Templates (Node.js only):
getTemplate(name, encoding?)- Load and parse template filesgetTemplateAsync(name, encoding?)- Async versionmergeTemplate(name, context)- Load and render in one stepresourceExists(name)- Check if template file exists
- Stream Support:
evaluate()with callback - Stream output (Writer equivalent)evaluateReader()- Async input support (Reader equivalent)
- Resource Loaders:
FileResourceLoader- Load templates from the filesystemStringResourceLoader- In-memory string templatesaddResourceLoader()/getResourceLoader()- Custom loaders- ResourceLoader interface for implementing custom loaders
- Configuration (all Java RuntimeConstants):
setProperty(key, value)- Set individual propertiesaddProperty(key, value)- Add to array propertiesgetProperty(key)/clearProperty(key)- Manage propertiessetProperties(map)- Bulk configuration from MapsetPropertiesFromFile(path)- Load from .properties fileinit()/init(properties)/init(propertiesFile)- Initialize engine- 60+ configuration constants matching Java implementation
- Caching: Template caching with configurable TTL and size limits
- Encoding: Configurable character encoding (default UTF-8)
- Application Attributes: Cross-component communication via
setApplicationAttribute()/getApplicationAttribute()
Development
# Install dependencies
npm install
# Initialize git submodule for Apache Velocity reference
git submodule update --init
# Build the library
npm run build
# Run comparison tests against Java implementation
npm test
# Development mode (watch)
npm run devTesting
This library uses a comprehensive test harness that compares TypeScript output against the Apache Velocity Java reference implementation to ensure 100% compatibility.
# Run all comparison tests
npm test
# Run a specific test
npm run test:velocity:single <test-name>Test cases are located in tests/velocity/<test-name>/:
template.vtl- Velocity templateinput.json- Context variables
API Reference
VelocityEngine
class VelocityEngine {
constructor(config?: VelocityEngineConfig | string)
// Initialization
init(): void
init(properties: Map<string, any>): void
init(propertiesFile: string): void
reset(): void
// String template rendering
render(template: string, context?: object): string
evaluate(context: object, template: string, logTag?: string, outputCallback?: (chunk: string) => void): string | boolean
evaluateReader(context: object, templateContent: string | (() => Promise<string>), logTag?: string, outputCallback?: (chunk: string) => void): Promise<string | boolean>
// File-based templates (Node.js only)
getTemplate(name: string, encoding?: string): Template
getTemplateAsync(name: string, encoding?: string): Promise<Template>
mergeTemplate(name: string, context: object): string
mergeTemplate(name: string, encoding: string | null, context: object): string
resourceExists(name: string): boolean
// Configuration
setProperty(key: string, value: any): void
getProperty(key: string): any
addProperty(key: string, value: any): void
clearProperty(key: string): void
setProperties(properties: Map<string, any>): void
setPropertiesFromFile(path: string): void
// Application attributes
setApplicationAttribute(key: string, value: any): void
getApplicationAttribute(key: string): any
// Resource loaders
addResourceLoader(name: string, loader: ResourceLoader): void
getResourceLoader(name: string): ResourceLoader | null
}Template
class Template {
constructor(name: string, encoding?: string, spaceGobbling?: SpaceGobblingMode)
getName(): string
getEncoding(): string
getLastModified(): number
process(): Promise<boolean>
processSync(): boolean
merge(context?: object): string
isProcessed(): boolean
setResourceLoader(loader: ResourceLoader): void
}RuntimeConstants
All Apache Velocity runtime constants are available:
import { RuntimeConstants } from '@fearlessfara/velocits';
RuntimeConstants.FILE_RESOURCE_LOADER_PATH
RuntimeConstants.FILE_RESOURCE_LOADER_CACHE
RuntimeConstants.INPUT_ENCODING
RuntimeConstants.SPACE_GOBBLING
RuntimeConstants.RUNTIME_STRING_INTERNING
RuntimeConstants.MAX_NUMBER_LOOPS
// ... and many moreProject Structure
src/
engine.ts - Main VelocityEngine class
template.ts - Template class for file-based templates
parser/ - Chevrotain parser and AST
runtime/ - Evaluator, scope, string builder, constants
resource/ - Resource loaders (File, String)
lexer/ - Token definitions
tools/
compare-velocity/ - Test harness for Java/TS comparison
tests/
velocity/ - Test cases (template.vtl + input.json)
file-support/ - File loader tests
vendor/
velocity-engine/ - Apache Velocity Java reference (git submodule)TypeScript Configuration
This library is built with strict TypeScript settings for maximum type safety:
strict: truenoImplicitAny: truenoImplicitReturns: truenoUnusedLocals: truenoUnusedParameters: trueexactOptionalPropertyTypes: truenoUncheckedIndexedAccess: true
Contributing
See CONTRIBUTING.md for guidelines.
License
MIT
