Guide / API Reference

compile()

The highest-level entry point. Parses a 4less string, expands components, and emits HTML in one call.

import { compile } from '@dzhonragon/4less';

compile(source: string, vars?: VarsMap): string

VarsMap is Record. Pass it directly as the second argument:

const html = compile(
  `ul { for item in stack: li $item }`,
  { stack: ['TypeScript', 'React'] }
);
// → <ul><li>TypeScript</li><li>React</li></ul>

parse()

Tokenizes and parses source text into an AST. Use when you want to inspect or transform the tree before generating output.

import { parse } from '@dzhonragon/4less';

parse(source: string): AstNode[]

Returns an array of AstNode. Throws ParseError on invalid syntax.

const ast = parse(`
  component Badge { span.badge $label }
  Badge label:"stable"
`);
// ast[0].type === 'component_def'
// ast[1].type === 'component_call'

generate()

Walks an AST with a generator instance to produce output. Pair with parse() for full control over the pipeline.

import { parse, generate, HtmlGenerator } from '@dzhonragon/4less';

generate(ast: AstNode[], generator: BaseGenerator): string
const ast  = parse(`div { h1 "Hello" p "World" }`);
const html = generate(ast, new HtmlGenerator());
// → <div><h1>Hello</h1><p>World</p></div>

Generators

All generators extend BaseGenerator. Import from the main package entry point:

import {
  HtmlGenerator,
  ReactGenerator,
  VueGenerator,
  AstroGenerator,
  JsonGenerator,
} from '@dzhonragon/4less';

HtmlGenerator

Renders to plain HTML strings. Used by compile() internally.

new HtmlGenerator(options?: { vars?: VarsMap })
generate(ast, new HtmlGenerator({ vars: { name: 'Alice', admin: true } }));

ReactGenerator

Renders to React.createElement() calls. No JSX, no transpiler required.

new ReactGenerator()
generate(parse(`if show: p $msg`), new ReactGenerator());
// → show ? React.createElement('p', null, msg) : null

Dynamic attributes emit template literals:

generate(parse(`a "Go" href:"/posts/$slug"`), new ReactGenerator());
// → React.createElement('a', { href: `/posts/${slug}` }, "Go")

VueGenerator

Renders to Vue template syntax with v-for, v-if, v-else-if, and v-else directives.

new VueGenerator()
generate(parse(`ul { for item in list: li $item }`), new VueGenerator());
// → <ul><li v-for="item in list" :key="item">{{ item }}</li></ul>

Dynamic attributes use :attr bindings:

generate(parse(`a "Go" href:"/posts/$slug"`), new VueGenerator());
// → <a :href="`/posts/${slug}`">Go</a>

AstroGenerator

Renders to Astro component template syntax.

new AstroGenerator(options?: {
  frontmatter?: string;  // content placed in the --- frontmatter block
  fragment?: boolean;    // wrap multiple roots in <> (default: true)
})
generate(parse(`if show: p $msg`), new AstroGenerator());
// → {show && <p>{msg}</p>}

generate(parse(`if show: p "yes" else: p "no"`), new AstroGenerator());
// → {show ? <p>yes</p> : <p>no</p>}

Dynamic attributes emit JSX template literals:

generate(parse(`a "Go" href:"/posts/$slug"`), new AstroGenerator());
// → <a href={`/posts/${slug}`}>Go</a>

JsonGenerator

Serializes the AST to JSON. Useful for debugging, tooling, or language server integrations.

new JsonGenerator(indent?: number)  // default: 2

expandComponents()

Expands component definitions and calls, replacing them with their resolved element trees. Called automatically by compile() and generate().

import { expandComponents } from '@dzhonragon/4less';

expandComponents(ast: AstNode[]): AstNode[]

Use when you want to inspect the expanded tree before passing it to a generator.

ParseError

Thrown when source contains invalid syntax. Carries structured error locations for editor integration.

import { ParseError } from '@dzhonragon/4less';

try {
  parse(source);
} catch (e) {
  if (e instanceof ParseError) {
    for (const err of e.errors) {
      console.error(`line ${err.line}:${err.col} — ${err.message}`);
    }
  }
}

Error shape:

interface ErrorLocation {
  line:    number;
  col:     number;
  message: string;
}

class ParseError extends Error {
  errors: ErrorLocation[];
}

AstNode Types

The AST uses a discriminated union on the type field:

type AstNode =
  | ElementNode        // type: 'element'
  | LoopNode           // type: 'loop'
  | CondNode           // type: 'cond'
  | ComponentDefNode   // type: 'component_def'
  | ComponentCallNode  // type: 'component_call'

CondNode — includes negation and optional else branch:

interface CondNode {
  type:     'cond';
  negate:   boolean;       // true for `if !condition:`
  condition: string;
  body:     AstNode;
  elseBody: AstNode | null; // set when `else:` or `else-if` follows
}

ElementNode — attribute values are TextSegment[] to support $var interpolation:

interface ElementNode {
  type:       'element';
  tag:        string;
  id:         string | null;
  classes:    string[];
  attributes: Record<string, TextSegment[]>;
  text:       TextSegment[] | null;
  children:   AstNode[];
}

type TextSegment =
  | { kind: 'literal'; value: string }
  | { kind: 'var';     name: string  };