Skip to content

Feature request: the @options directive #1134

@jonschlinkert

Description

@jonschlinkert

This is something I've wanted since first using LESS, and I think I've come up with a solution that could be pretty awesome if we get some feedback and tweak it to get the kinks out.

Proposal, Part 1: The @options directive

I propose that we use the @options directive to allow setting processing options directly inside less files themselves, like this:

@options {
  // options
}

As with media queries, the @options directive wraps a declaration block which contains the options/flags for how you want your less to compile. When this directive is found, less.js reads in the options and processes all less files accordingly. If multiple @options directives are identified, less.js either throws an error or it uses the options from the last directive (I'll leave that decision to others). If the latter is chosen and multiple @options directives can exist simultaneously, my suggestion would be to process it like this:

If multiple @options directives could co-exist:

First directive identified...

@options {
  paths: "";
  rootpath: "";
  relativeUrls: "false";
  strictImports: "false";
  compress: "false";
  yuicompress: "true";
  optimization: "1";
  silent: "false";
  lint: "false";
  color: "true";
}

Second directive identified

@options {
  compress: "true"; // less.js uses `compress: "true"`, but leaves the other options in tact
}

Another suggestion would be to use !important to designate which options should not be overridden.

Why would multiple @options directives exist? A good use case is that when importing external libraries, such as Twitter Bootstrap, you may wish to override the options from that library.

Regardless of how it's actually implemented, what I find so compelling about this concept is that it paves the way for keeping the syntax and language cleaner, it keeps options closer to the less files, and forces a stronger separation of concerns between processing/compiling features and language features. To that end, I often see feature requests or Issues where the OP is asking for something to be added to the language, when really all that is needed is a new processing/compiling option. LESS is a language, but less.js is a compiler! This makes that distinction much clearer, and it allows for the user to store different options across different projects, either directly inside a single less file, in several less files, or even organized in options.less files that can be reused across projects. Whichever you prefer, it's a better approach than putting those options in a json file or javascript.

see: #850

Proposal, Part 2: Setting "Contexts" with the @options directive

With this proposal, "context" is created in a kind of similar way to setting context in "logic-less" templating languages, such as mustache. So, for instance, rather than hard-coding processing options in @import or @include directives, we would instead use "contexts" so that the choice is made by the developer, and they have the choice of setting the options in the less files themselves or on-the-fly at compiling time.

@options("default") {
  paths: "";
  rootpath: "";
  relativeUrls: "false";
  strictImports: "false";
  compress: "false";
  yuicompress: "true";
  optimization: "1";
  silent: "false";
  lint: "false";
  color: "true";
}

(and how awesome would it be to be able to use variables in paths and rootpaths now that we are storing those properties in our less files!)

A common use case is to create contexts for environments, like development and production, so that less.js (env) processes the options according to how they are defined in the less files themselves:

@options("dev") {
  compress: "false";
}

and...

@options("prod") {
  compress: "true";
}

Applying contextual options in your less files

Media queries are a great starting point for inspiration, for example:

@media (min-width: 768px) and (max-width: 979px) {
  .row {
    margin-left: -20px;
    zoom: 1;
  }
}

And CSS syntax already allows us flexibility with media queries with @import and @media, like:

@import url(example.css) screen and (color), projection and (color) { // declarations };

// and

@import url("phone.css") only screen and (max-width:400px) { // declarations };

// and

@media screen and (color), projection and (color) { // declarations }

And written in HTML, XHTML, XML, @import and @media:

<link rel="stylesheet" media="screen and (color), projection and (color)" rel="stylesheet" href="example.css">

So following convention established by @imports and media queries, here are some ideas for how we can take the contexts we created using the @options("context") directive and apply them throughout our less stylesheets:

@context("prod") {
  .some-experimental-class {
    display: none;
  }
  .sidebar {
    width: 250px;
  }
  ...
}

and...

@context("dev") {
  .some-experimental-class {
    display: block;
  }
  .sidebar {
    width: 210px;
  }
  ...
}

This gives us the choice to extend the directive in the future:

@context ("experimental") and (some-rule: true) { // declarations }

// and

@context all and (some-rule: true) { // declarations }

As with media queries, these would be equivalent:

@context all { // declarations }
@context { // declarations }

Or, for example, we could set the "contexts" from our @options on other directives like this:

@import:dev "forms.less"

// or

@import("dev") "forms.less"

// or

@import:context("dev") "forms.less"

// or

@import:options("dev") "forms.less"

Example of other Issues that could be resolved with this:

I would solve the @include issue like this:

@options("dev") {
  css: "passthrough"; // or some more elegant way of describing it
}

and...

@options("prod") {
  css: "append";
}

or maybe just...

@options("prod") {
  concatenate: "true"; // which would apply to both css and less.
}

Last, it's understood that @options could collide with custom variables, so either 1) bake it in as a reserved word and force people to not use that as a variable name, 2) allow @options: variable and @options {} to co-exist peacefully, or 3) we just use a different namespacey term than @options. I like #2 the most, alternatively #1.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions