┌─────────────────────────────────────────────────────────────┐
│ LESS.JS COMPILATION │
└─────────────────────────────────────────────────────────────┘
INPUT: Less Code
═══════════════════════════════════════════════════════════════
@color: red;
a {
color: @color;
}
.foo {
&:extend(a all);
@color: green;
}
PHASE 1: PARSING
───────────────────────────────────────────────────────────────
├─ Parse Less syntax into AST (Abstract Syntax Tree)
├─ Line comments (//) now stripped ✅ NEW FIX
└─ Block comments (/* */) preserved
AST Structure:
Variable: @color = red (global scope)
Ruleset: a
├─ Declaration: color = Variable(@color)
Ruleset: .foo
├─ Extend: (a all)
└─ Variable: @color = green (local scope)
PHASE 2: EVALUATION ⚡
───────────────────────────────────────────────────────────────
├─ Resolve all variables in their scope
├─ Evaluate expressions
├─ Expand mixins
└─ Build frame stack for scope resolution
Frame Stack During Evaluation:
┌──────────────────┐
│ Global Frame │ @color: red
├──────────────────┤
│ Ruleset 'a' │ Looks up @color → finds 'red'
│ │ color: red ✅
└──────────────────┘
┌──────────────────┐
│ Global Frame │ @color: red
├──────────────────┤
│ Ruleset '.foo' │ @color: green (local)
│ │ (but 'a' already evaluated!)
└──────────────────┘
After Evaluation:
Ruleset: a { color: red; } ← Already evaluated!
Ruleset: .foo {
Extend: (a all)
@color: green ← Only affects .foo's scope
}
PHASE 3: EXTEND VISITOR 🔄
───────────────────────────────────────────────────────────────
├─ Find all :extend() directives
├─ Match target selectors
├─ COPY evaluated declarations (no re-evaluation!)
└─ Create new selector paths
Extend Process:
1. Find extend: .foo extends (a all)
2. Find target: a { color: red; }
3. Copy selector: Create .foo a
4. Copy declarations: Use ALREADY EVALUATED color: red
5. Result: .foo a { color: red; }
❌ Does NOT re-evaluate with @color: green
✅ Just copies the selector and existing declarations
PHASE 4: TO-CSS VISITOR
───────────────────────────────────────────────────────────────
├─ Convert AST to CSS strings
├─ Remove invisible nodes
└─ Apply compression if enabled
OUTPUT: CSS
═══════════════════════════════════════════════════════════════
a,
.foo a {
color: red; ← Both have red, not green!
}
EVALUATION HAPPENS FIRST
═════════════════════════════════════════════════════════════
Time: T1
┌────────────┐
│ @color: red│ (Global)
└────────────┘
↓
┌────────────────────┐
│ a { color: red; } │ ← Evaluated NOW
└────────────────────┘
↓
┌────────────────────┐
│ .foo { ... } │
│ @color: green │ ← Only for .foo's scope
└────────────────────┘
EXTEND HAPPENS SECOND
═════════════════════════════════════════════════════════════
Time: T2
┌────────────────────┐
│ a { color: red; } │ ← Already done!
└────────────────────┘
↓
[EXTEND]
↓
┌────────────────────┐
│ Copy selector "a" │
│ to ".foo a" │
└────────────────────┘
↓
┌───────────────────────┐
│ .foo a { color: red; }│ ← Copies evaluated value
└───────────────────────┘
❌ Does NOT go back and re-evaluate with @color: green
┌─────────────────────────────────────────────────┐
│ Define: .style(@c: red) { color: @c; } │
└─────────────────────────────────────────────────┘
↓
┌───────────┴───────────┐
↓ ↓
┌───────────────┐ ┌───────────────┐
│ a { │ │ .foo a { │
│ .style(); │ │ .style(green)│
│ } │ │ } │
└───────────────┘ └───────────────┘
↓ ↓
┌───────────────┐ ┌───────────────┐
│ a { │ │ .foo a { │
│ color: red; │ │ color: green;│
│ } │ │ } │
└───────────────┘ └───────────────┘
┌─────────────────────────────────────────┐
│ a { color: var(--color, red); } │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ .foo { --color: green; } │
└─────────────────────────────────────────┘
↓
CSS CASCADE AT RUNTIME
↓
┌─────────────────────────────────────────┐
│ .foo a uses --color: green ✅ │
└─────────────────────────────────────────┘
┌────────────────────────────────────────────┐
│ LESS.JS COMPILATION PRINCIPLES │
├────────────────────────────────────────────┤
│ │
│ 1️⃣ Variables are scoped │
│ Scope = frame stack during eval │
│ │
│ 2️⃣ Evaluation happens FIRST │
│ All variables resolved to values │
│ │
│ 3️⃣ Extend happens SECOND │
│ Copies selectors + evaluated values │
│ │
│ 4️⃣ No re-evaluation in extend │
│ Extend works on selectors, not values │
│ │
│ 5️⃣ Use mixins for value customization │
│ Pass values as parameters │
│ │
└────────────────────────────────────────────┘
0ms 50ms 100ms 150ms 200ms 250ms
│────────│────────│────────│────────│────────│
│ │ │ │ │ │
PARSE EVAL EXTEND TO-CSS OUTPUT
│ │ │ │ │
• Read • Vars • Match • Gen • Write
syntax resolve target CSS file
•Exprs selector string
• Build eval • Copy • Remove
AST • Mixins rules hidden
expand • Create
• Strip • Frame paths
//cmts stack
@color ❌ Cannot
resolved re-eval
HERE! here
BEFORE EXTEND:
═══════════════════════════════════════════
a { color: red; }
AFTER EXTEND:
═══════════════════════════════════════════
a { color: red; }
.foo a { color: red; } ← Pasted!
❌ WRONG MENTAL MODEL:
═══════════════════════════════════════════
a { color: red; }
.foo a { color: @foo-color; } ← Does NOT happen!
Need to override values?
│
┌───────────────┼───────────────┐
│ │ │
Few values Many values Runtime theme
│ │ │
↓ ↓ ↓
Explicit Parameterized CSS Custom
Override Mixin Properties
│ │ │
↓ ↓ ↓
.foo a { .style(@c) { --color: X;
color: X; color: @c; var(--color)
} }
Visual Guide Version: 1.0
Date: October 19, 2025
Tip: Print this page for your team! 🖨️