A simple, expressive and safe Shopify / Github Pages compatible template engine in pure JavaScript.
npx @tessl/cli install tessl/npm-liquidjs@10.21.0LiquidJS is a simple, expressive and safe Shopify/GitHub Pages compatible template engine in pure JavaScript. It provides a complete implementation of the Shopify Liquid templating language, enabling developers to render dynamic templates with full compatibility across Node.js, browsers, and CLI environments.
npm install liquidjsimport { Liquid } from "liquidjs";For specific imports:
import { Liquid, Context, Template, LiquidOptions } from "liquidjs";CommonJS:
const { Liquid } = require("liquidjs");import { Liquid } from "liquidjs";
// Create engine instance
const engine = new Liquid();
// Parse and render template
const template = `Hello {{ name | capitalize }}!`;
const result = await engine.parseAndRender(template, { name: "world" });
// Result: "Hello World!"
// Parse template once, render multiple times
const tpl = engine.parse(`
{% for user in users %}
<li>{{ user.name }} ({{ user.age }})</li>
{% endfor %}
`);
const html = await engine.render(tpl, {
users: [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 }
]
});LiquidJS is built around several key components:
Main Liquid class providing parsing, rendering, and template management functionality. Supports both sync/async operations and multiple output formats.
class Liquid {
constructor(opts?: LiquidOptions);
// Parsing
parse(html: string, filepath?: string): Template[];
parseFile(file: string, lookupType?: LookupType): Promise<Template[]>;
parseFileSync(file: string, lookupType?: LookupType): Template[];
// Rendering
render(tpl: Template[], scope?: object, renderOptions?: RenderOptions): Promise<any>;
renderSync(tpl: Template[], scope?: object, renderOptions?: RenderOptions): any;
renderToNodeStream(tpl: Template[], scope?: object, renderOptions?: RenderOptions): NodeJS.ReadableStream;
// Parse and render combined
parseAndRender(html: string, scope?: Context | object, renderOptions?: RenderOptions): Promise<any>;
parseAndRenderSync(html: string, scope?: Context | object, renderOptions?: RenderOptions): any;
// File operations
renderFile(file: string, ctx?: Context | object, renderFileOptions?: RenderFileOptions): Promise<any>;
renderFileSync(file: string, ctx?: Context | object, renderFileOptions?: RenderFileOptions): any;
}Comprehensive configuration system for customizing engine behavior, file handling, template processing, and performance settings.
interface LiquidOptions {
// File system
root?: string | string[];
partials?: string | string[];
layouts?: string | string[];
fs?: FS;
// Template processing
strictFilters?: boolean;
strictVariables?: boolean;
dynamicPartials?: boolean;
jekyllInclude?: boolean;
// Output control
outputEscape?: 'escape' | 'json' | ((value: any) => string);
keepOutputType?: boolean;
// Performance limits
parseLimit?: number;
renderLimit?: number;
memoryLimit?: number;
}Comprehensive collection of 80+ built-in filters for data transformation, organized into categories including array manipulation, string processing, mathematical operations, date formatting, HTML processing, and URL handling.
// Array filters
join(array: any[], separator?: string): string;
map(array: any[], property: string): any[];
where(array: any[], property: string, value?: any): any[];
sort(array: any[], property?: string): any[];
// String filters
append(string: string, suffix: string): string;
capitalize(string: string): string;
downcase(string: string): string;
replace(string: string, search: string, replacement: string): string;
// Math filters
abs(number: number): number;
plus(number: number, operand: number): number;
minus(number: number, operand: number): number;
times(number: number, operand: number): number;Complete set of 21 built-in tags providing control flow, variable assignment, template inclusion, and content manipulation capabilities.
// Control flow
{% if condition %}...{% endif %}
{% unless condition %}...{% endunless %}
{% case variable %}{% when value %}...{% endcase %}
{% for item in array %}...{% endfor %}
// Variable assignment
{% assign variable = value %}
{% capture variable %}...{% endcapture %}
{% increment variable %}
{% decrement variable %}
// Template inclusion
{% include 'template' %}
{% render 'template' with data %}
{% layout 'template' %}Variable management and scoping system providing controlled access to template data with support for nested scopes and variable resolution.
class Context {
constructor(scope?: object, options?: NormalizedFullOptions, renderOptions?: RenderOptions);
get(path: string): any;
set(path: string, value: any): void;
push(scope: object): Context;
pop(): Context | undefined;
getAll(): object;
environments: Scope[];
}
interface Scope {
[key: string]: any;
}System for registering custom filters and tags to extend LiquidJS functionality with domain-specific template processing capabilities.
// Custom filters
registerFilter(name: string, filter: FilterImplOptions): void;
// Custom tags
registerTag(name: string, tag: TagClass | TagImplOptions): void;
// Plugin system
plugin(plugin: (this: Liquid, L: typeof Liquid) => void): void;
interface FilterImplOptions {
(this: FilterImpl, value: any, ...args: any[]): any;
}
interface TagImplOptions {
parse?(token: TagToken, remainTokens: TopLevelToken[]): void;
render?(ctx: Context, emitter: Emitter): any;
}Comprehensive template analysis functionality for extracting variable dependencies, performing static analysis, and understanding template requirements without rendering.
// Basic variable analysis
variables(template: string | Template[], options?: StaticAnalysisOptions): Promise<string[]>;
variablesSync(template: string | Template[], options?: StaticAnalysisOptions): string[];
fullVariables(template: string | Template[], options?: StaticAnalysisOptions): Promise<string[]>;
fullVariablesSync(template: string | Template[], options?: StaticAnalysisOptions): string[];
// Variable segments analysis
variableSegments(template: string | Template[], options?: StaticAnalysisOptions): Promise<SegmentArray[]>;
variableSegmentsSync(template: string | Template[], options?: StaticAnalysisOptions): SegmentArray[];
// Global variable analysis
globalVariables(template: string | Template[], options?: StaticAnalysisOptions): Promise<string[]>;
globalVariablesSync(template: string | Template[], options?: StaticAnalysisOptions): string[];
globalFullVariables(template: string | Template[], options?: StaticAnalysisOptions): Promise<string[]>;
globalFullVariablesSync(template: string | Template[], options?: StaticAnalysisOptions): string[];
globalVariableSegments(template: string | Template[], options?: StaticAnalysisOptions): Promise<SegmentArray[]>;
globalVariableSegmentsSync(template: string | Template[], options?: StaticAnalysisOptions): SegmentArray[];
// Comprehensive analysis
analyze(template: Template[], options?: StaticAnalysisOptions): Promise<StaticAnalysis>;
analyzeSync(template: Template[], options?: StaticAnalysisOptions): StaticAnalysis;
parseAndAnalyze(html: string, filename?: string, options?: StaticAnalysisOptions): Promise<StaticAnalysis>;
parseAndAnalyzeSync(html: string, filename?: string, options?: StaticAnalysisOptions): StaticAnalysis;
interface StaticAnalysis {
variables: Variables;
globals: Variables;
locals: Variables;
}File system abstraction and template loading capabilities supporting both file-based and in-memory template sources.
interface FS {
exists(file: string): boolean | Promise<boolean>;
readFile(file: string): string | Promise<string>;
dirname(file: string): string;
sep: string;
}
// Template loading
parseFile(file: string, lookupType?: LookupType): Promise<Template[]>;
renderFile(file: string, ctx?: Context | object, renderFileOptions?: RenderFileOptions): Promise<any>;
enum LookupType {
Root = 'fs',
Partials = 'partials',
Layouts = 'layouts'
}interface Template {
token: Token;
render(ctx: Context, emitter: Emitter): any;
}
interface RenderOptions {
sync?: boolean;
globals?: object;
strictVariables?: boolean;
ownPropertyOnly?: boolean;
}
interface RenderFileOptions extends RenderOptions {
lookupType?: LookupType;
}
type FilterImplOptions = (this: FilterImpl, value: any, ...args: any[]) => any;
type TagClass = new (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) => Tag;class LiquidError extends Error {
originalError?: Error;
token?: Token;
}
class ParseError extends LiquidError {}
class RenderError extends LiquidError {}
class UndefinedVariableError extends LiquidError {}
class TokenizationError extends LiquidError {}
class AssertionError extends LiquidError {}