Skip to content

Latest commit

 

History

History
288 lines (239 loc) · 11.4 KB

File metadata and controls

288 lines (239 loc) · 11.4 KB

Less.js Compilation Flow - Visual Guide

📊 Understanding Why Extend Doesn't Re-evaluate Variables

┌─────────────────────────────────────────────────────────────┐
│                    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!
}

🔍 Why Variables Don't Override

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

✅ Solution Patterns

Pattern 1: Parameterized Mixin

┌─────────────────────────────────────────────────┐
│ Define: .style(@c: red) { color: @c; }         │
└─────────────────────────────────────────────────┘
                    ↓
        ┌───────────┴───────────┐
        ↓                       ↓
┌───────────────┐       ┌───────────────┐
│ a {           │       │ .foo a {      │
│  .style();    │       │  .style(green)│
│ }             │       │ }             │
└───────────────┘       └───────────────┘
        ↓                       ↓
┌───────────────┐       ┌───────────────┐
│ a {           │       │ .foo a {      │
│  color: red;  │       │  color: green;│
│ }             │       │ }             │
└───────────────┘       └───────────────┘

Pattern 2: CSS Custom Properties

┌─────────────────────────────────────────┐
│ a { color: var(--color, red); }        │
└─────────────────────────────────────────┘
                ↓
┌─────────────────────────────────────────┐
│ .foo { --color: green; }                │
└─────────────────────────────────────────┘
                ↓
        CSS CASCADE AT RUNTIME
                ↓
┌─────────────────────────────────────────┐
│ .foo a uses --color: green ✅           │
└─────────────────────────────────────────┘

🎯 Key Principles

┌────────────────────────────────────────────┐
│  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             │
│                                            │
└────────────────────────────────────────────┘

📈 Compilation Timeline

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

🎓 Mental Model

Think of Extend as "Selector Copy-Paste"

BEFORE EXTEND:
═══════════════════════════════════════════
a { color: red; }

AFTER EXTEND:
═══════════════════════════════════════════
a { color: red; }
.foo a { color: red; }  ← Pasted!

NOT as "Selector Re-Evaluation"

❌ WRONG MENTAL MODEL:
═══════════════════════════════════════════
a { color: red; }
.foo a { color: @foo-color; }  ← Does NOT happen!

🔧 Quick Decision Tree

                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! 🖨️