Skip to content

Less Language Namespaces

Mária Jurčovičová edited this page Apr 3, 2015 · 44 revisions

Namespaces encapsulate a group of mixins under common name. They have two purposes:

  • avoid naming conflicts in bigger projects,
  • encapsulate/isolate a group of mixins from outside world.

First chapter explains how to define and use namespaces, what is legal and what is not legal. Second chapter deal with scoping. Less language has very permissive scoping philosophy and its approach may be surprising for those used to strongly typed or restrictive languages.

Basics

Any mixin without parameters or rulesets whose name consists of only classes .id and ids #name can be used as a namespace. All mixins nested inside a namespace belong to that namespace. You have two options if you want to access a mixin defined inside a namespace:

  • an explicit namespace > mixin(param1, ..., paramn) syntax,
  • a slightly shorter namespace mixin(param1, ..., paramn) syntax.

The symbol greater > is optional.

Simple Example

Declare namespace and use its mixins:

//declare namespace with two mixins
#ns {
  .square(@param) {
    width: @param;
    height: @param;
  }
  .color(@color) {
    color: @color;
  }
}

.box {
  //use the namespace
  #ns > .square(10px);
  #ns .color(#ff00ff);
}     

Compiles to:

.box {
  width: 10px;
  height: 10px;
  color: #ff00ff;
}

Nested Namespace Example

It is legal to nest a namespace inside another one. In addition, mixins inside the namespace are matched as usually, e.g. all matching mixins are going to be used. Following less is equivalent to the previous one:

//declare namespace with one mixin
#outer { 
  #ns {
    .square(@param) {
      width: @param;
    }
    .square(@param) {
      height: @param;
    }
  }
}

.box {
  //use the namespace
  #outer > #ns > .square(10px);
}     

Compiles to:

.box {
  width: 10px;
  height: 10px;
}

Top Level Namespace Call

Top level namespace references are legal as long as the referenced mixin contains only nested rulesets and no declarations:

#namespace {
  //mixin without declarations
  .mixin() {
    nested {
      color: blue;
    }
  }
}

//top level namespace reference
#namespace .mixin();

compiles into:

nested {
  color: blue;
}

Matching Namespaces

A namespace is valid withing the whole scope where it has been defined and can be used before it has been defined. If multiple namespaces match the reference, all of them are going to be used.

Namespace is defined after being used and split into two parts:

.box {
  //use the namespace
  #ns > .square(10px);
}     

// namespace is split into two parts - part 1
#ns {
  .square(@param) {
    width: @param;
  }
}
// namespace is split into two parts - part 2
#ns {
  .square(@param) {
    height: @param;
  }
}

compiles into:

.box {
  width: 10px;
  height: 10px;
}

Guards on Namespaces

If namespace have a guard, mixins defined by it are used only if guard condition returns true. Namespace guard is evaluated exactly the same way as guard on mixin, so next two mixins work the same way:

#sp_1 when (default()) {
  .mixin() { /* */ }
}
#sp_1  {
  .mixin() when (default()) { /* */ }
}

The default function is assumed to have the same value for all nested namespaces and mixin. Following mixin is never evaluated, one of its guards is guaranteed to be false

#sp_1 when (default()) {
  #sp_2 when (default()) {
    .mixin() when not(default()) { /* */ }
  }
}

Parametrized Mixins as Namespaces

Mixins with with mandatory parameters can not be used as a namespaces. Mixins can be used as namespaces only if all their parameters are optional.

Next less demonstrates parametrized and guarded mixins used as namespaces:

#namespace(@variable) { // mandatory parameter - can not be used as namespace
  .mixin() { mandatory: parameter;  }
}
@go: true;
#namespace(...) when (@go){ // ok
  .mixin() { works: works;  }
}
#namespace(@variable: default) when (@go){ // ok - parameter is optional
  .mixin() { optional: optional;  }
}
#namespace(...) when not(@go){  // false guard - can not be used as namespace
  .mixin() { guard: false;  }
}
.use {
  #namespace > .mixin();
}

compiles into:

.use {
  works: works;
  optional: optional;
}

Scoping

The last thing we need to deal with is scoping. You have to understand two things in order to properly use namespaces:

  • Which variables and mixins are available inside the namespace.
  • If multiple scopes contains namespaces with conflicting names, which one is going to be used.

Each issue is addressed in separate section.

Scope Available Inside the Namespace

Any mixin declared inside the namespace can use mixins and variables declared in both its declaration and caller scope. The declaration scope takes precedence over the caller scope.

Create a bunch of overriding variables within each scope and see which one wins:

#outer {
  #ns {
    .square(@priority1, @priority2) {
      priority1: @priority1;
      priority2: @priority2;
      priority3: @priority3;
      priority4: @priority4;
      priority5: @priority5;
      priority6: @priority6;
      @priority1: "inside mixin";
    } 
  }
  @priority1: "declaration scope";
  @priority2: "declaration scope";
  @priority3: "declaration scope";
}

.box {
  .box {
    //call the mixin
    #outer > #ns > .square("argument", "argument");
    @priority1: "caller scope";
    @priority2: "caller scope";
    @priority3: "caller scope";
    @priority4: "caller scope";
    @priority5: "caller scope";
  }     
  @priority1: "caller scope - deeper";
  @priority2: "caller scope - deeper";
  @priority3: "caller scope - deeper";
  @priority4: "caller scope - deeper";
  @priority6: "caller scope - deeper";
}

@priority1: "declaration scope - deeper";
@priority2: "declaration scope - deeper";
@priority3: "declaration scope - deeper";
@priority4: "declaration scope - deeper";

Compiles into:

.box {
  priority1: "inside mixin";
  priority2: "argument";
  priority3: "declaration scope";
  priority4: "declaration scope - deeper";
  priority5: "caller scope";
  priority6: "caller scope - deeper";
}

Note: Less.js has one known bug in handling of lazy-loaded namespaces https://github.com/cloudhead/less.js/issues/996 .

Namespace References Resolution

Namespaces references are relative to current scope. Less looks for matching namespace within current scope first. If a mixin is found, the search is stopped. The search continues into parent scope only if the last searched scope contains no matching element.

Example:

#ns {
  .mixin() {
    declaration: "global namespace";
  }
}

.box {
  #ns {
    .mixin() {
      declaration: "local namespace";
    }
  }
  //use the namespace
  #ns > .mixin();
}     

Result:

.box {
  declaration: "local namespace";
}

Unlocking Mixins

You can "import" nested mixins into namespace by calling their owner mixin. Since nested mixins act as return values, all nested mixins are copied into namespace and available from there:

.unlock(@value) { // outer mixin
  .doSomething() { // nested mixin
    declaration: @value;
  }
}

#namespace() {
  .unlock(5); // unlock doSomething mixin
}

#use-namespace { 
  #namespace > .doSomething(); // it works also with namespaces
}

compiles into:

#use-namespace {
  declaration: 5;
}

Less4j cuts the evaluation if it detects dependency cycle. Dependency cycle happens when two namespaces reference each other. For example, #space2 references mixins from #abc2 which references mixins from #space2:

#space2 {
 .test2() { 
   .nested() {
     available: not;
   }
  }
  // use and import mixins from #abc2
  #abc2 > .dependency(); 
}
#abc2 { 
  .dependency() {
    voila: voila;
    .nested();
  }
  // use and import mixins from #space2
  #space2 > .test2(); 
}

The #abc2 > .dependency() in previous example will not see the .nested mixin, cause the #space2 > .test2() call leads to cycle. Evaluation will end with error:

Errors produced by compilation of testCase
ERROR 12:6 Could not find mixin named ".nested".

Cycle detection ignores variable states, evaluation is cut whenever namespace compilation requires itself to be compiled. It stops even if the cycling would not be infinite due to guards around mixins.

Less.js requires namespaces to be defined before being used. The above situation represents error too, but on line 7:

SyntaxError: .nested is undefined in test.less on line 7, column 3:
6   }
7   #abc2 > .dependency();
8 }