Less. The dynamic stylesheet language.

  • By Less
  • Last update: Jan 9, 2023
  • Comments: 17

Github Actions CI Downloads Twitter Follow


Chat with Less.js users and contributors

This is the Less.js monorepo, managed via Lerna.

More information

For general information on the language, configuration options or usage visit lesscss.org.

Here are other resources for using Less.js:

Contributing

Please read CONTRIBUTING.md. Add unit tests for any new or changed functionality. Lint and test your code using Grunt.

Reporting Issues

Before opening any issue, please search for existing issues and read the Issue Guidelines, written by Nicolas Gallagher. After that if you find a bug or would like to make feature request, please open a new issue.

Please report documentation issues in the documentation project.

Development

Read Developing Less.

Release History

See the changelog

Contributors

Code Contributors

This project exists thanks to all the people who contribute. [Contribute].

License

Copyright (c) 2009-2017 Alexis Sellier & The Core Less Team Licensed under the Apache License.

Github

https://github.com/less/less.js

Comments(17)

  • 1

    The way Extend works - more complicated selectors and change in matching

    From @DesignByOnyx

    I have finally gotten around to testing this and wanted to post my initial findings. I have been using the alpha version less-1.4.0-alpha.js on the client-side only (no command line yet - not that it should be any different really).

    So far, everything works as I would expect except for two major drawbacks which can be explained with one example. Take the following code which is located in one of my root LESS files:

    .container { margin: 0 auto; }
     .container.positioned { position: absolute }
    
    @media screen and (min-width: 30em) {
        .container.positioned { left: 50%; }
    }
    @media screen and (min-width: 30em) and (max-width: 48em) {
        .container { width: 30em; }
        .container.positioned { margin-left: -15em; }
    }
    @media screen and (min-width: 48em) and (max-width: 60em) {
        .container { width: 48em; }
        .container.positioned { margin-left: -24em; }
    }
    @media screen and (min-width: 60em) {
         .container { width: 60em; }
         .container.positioned { margin-left: -30em; }
    }
    

    Issue 1 - styles defined within media queries do not get extended. Anything trying to extend the .container class only gets the margin: 0 auto styles.

    .some-element:extend(.container);
    

    Issue 2 - compound selectors do not get extended. However, the first participant DOES get extended. For example, the following incorrectly extends the .container styles but not the intended .container.positioned styles.

    .some-element:extend(.container.positioned);
    

    I wish I could provide a solution. Hope this helps.

  • 2

    Allow parametrized mixins as detached rulesets to form 'lambdas'

    It seems that currently LESS only supports 'pure' rulesets to be passed along as mixin arguments or stored in variables as detached rulesets.

    I suggest to extend this support to incorporate parametrized mixins, essentially giving LESS the capability to work with lambdas.

    E.g. One would be able to write

    .list {
      .forEach(foo bar baz, (@item, @index) {
        @i : (@index + 1);
        > li:nth-child(@{i}):before {
          content : "@{item}";
        }
      });
    }
    

    where .forEach is defined as

    .forEach(@list, @lambda) {
      @n : length(@list);
    
      .for(0)
      .for(@index) {}
      .for(@index) when (@index < @n) {
        @lambda(extract(@list, @index), @index);
        .for(@index + 1);
      }
    }
    

    Lambda mixin support would also neatly resolve recurring issues with function return arguments and the ugly hack where variables 'bubble up' to parent scope if said variables are as of yet undefined in said parent scope.

    The suggested practice could become to adopt continuation style programming; passing 'return values' along into a lambda mixin to continue down the scope chain. This kind of mechanism is more transparent to users, less brittle by avoiding issues with potential variable name collisions and just fits in better with the overall functional programming paradigms that the LESS syntax is built on.

    [EDIT] Having just had a look at the way detached rulesets and calls are implemented in the AST, I think very little needs to happen to make this work. Even on the parser side of things, it seems fairly simple to just parse an optional block of mixin.args before blockRuleset in the detachedRuleset parser function and pass the arguments along to the tree.DetachedRuleset node instance. (The tree.DetachedRuleset would need to be extended with the params evaluation from tree.mixin.Definition, ofcourse.)

  • 3

    variable in @import statement

    The lastest version introduced the possibility to "access the value of an abstract property from a string using { } operators" (section "String interpolation" in http://lesscss.org/#-string-interpolation).

    But the problem is that it doesn't work in import statements. It would be great to write code such like this :

    //define the template @mainTemplate: "BaseTheme";

    //import the template @import "_templates/@{mainTemplate}/Config/_ConfigTemplateDefault.less";

    I don't know if it is possible to resolve this issue, but it would be great !

  • 4

    How to handle Maths

    1. We decided on strict maths going forward but there is general unease about forcing () around every calculation
    2. I don't think we want to change things massively or go back to the drawing board

    See #1872

    Possibility to add another case for calc which like font, with strict mode off, essentially turns strict mode on for a rule ? Too many exceptions in the future?

    @seven-phases-max : Well, there're a lot of other possibilities, e.g. ./ or require parens for division (e.g. 1/2->1/2 but (1/2)->0.5) etc... Also, the "special cases" (e.g. properties where x/y can appear as shorthand) are not so rare (starting at padding/margin and ending with background/border-radius and eventually there can be more) so we just can't hardcode them all like it's done for font (and because of that I think that the current font "workaround" is just a temporary and quite dirty kludge that ideally should be removed too).

  • 5

    Version 3.10.x uses significantly more memory and is significantly slower than 3.9.0

    Our builds recently started failing because we run about 80 Less builds in parallel during our project's build process and the new version of Less.js uses so much memory that Node crashes. We traced the crash to upgrading from Less.js from 3.9.0 to 3.10.3.

    I changed our Less script to compile the files sequentially (building 2 files at a time) and sampled Node's memory usage during the process and got the following results:

    less graph

    Less.js seems to use 130% more memory now and takes about 100% longer to compile for us.

    Just wondering if you've benchmarked Less.js and whether you see similar results

  • 6

    Public variables on namespaces

    I realize this isn't a new topic but it I think the time has come to consider implementing public variables on namespaces. Many prominent libraries for LESS (bootstrap, hat, etc.), have emerged, each with dozens of configuration variables which could very well overlap and conflict with each other.

    Currently, namespaces support private variables, the only way to get at them is via mixins within the namespace and those mixins can then be used externally; Sort of like a closure:

    #ns {
        @size: 10px;
        .box() {
            width: @size;
            height: @size;
        }
    }
    
    .icon {
        #ns > .box();
    }
    

    Which yields:

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

    However, I think it would be very handy to do the following:

    #ns {
        @size: 10px;
    }
    
    .icon {
        width: #ns > @size;
        height: #ns > @size;
    }
    

    As well as update those variables within the namespace:

    #ns > @size: 20px;
    
  • 7

    Add Sass like extend

    Sass extend is here. http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#extend

    More simple syntax, use +.

    .foo {
      width: 100px;
    }
    .bar {
      +.foo;
    }
    

    converted this.

    .foo, bar {
      width: 100px;
    }
    

    See also test code.

  • 8

    Support file globbing for @imports

    See: https://github.com/isaacs/node-glob, and ~~https://github.com/isaacs/minimatch~~ https://github.com/jonschlinkert/micromatch

    I use these often in projects. It would be pretty awesome to be able to do the following and not have to specify individual files:

    @import "mixins/*.less";
    @import "components/**/*.less"; 
    

    After only a few minutes of using these patterns they become second nature. It might even help with some of the other import issues.


    Implemented via plugin: less-plugin-glob.

  • 9

    Import options

    We have decided that the best way to handle the import bugs is by having options.

    Given the scope of the bugs I feel strongly that the options need to be inline with the actual import statement

    I suggest removing @import-once and @import-multiple and allowing options to be passed to the @import statement

    Note that import can already be followed by media statements e.g.

    @import "file.css" (min-width:400px);
    

    The options I propose we support are

    1. treat as less or treat as css - people have to add ?.css onto the end of url's at the moment to treat as css - a bit of a hack
    2. import-multiple to replace @import-multiple though not so important if we want to drop
    3. whether to keep the import in place or import it - at the moment we keep css imports inline, but it would be nice to be able to include in css files (treated as an anymous node)
    4. The ability to include a less file, but not output anything - just make the classes available as mixins.

    so here are some options.

     @import (multiple: true, less: true, include: true) "file.less" (min-width:400px);
    

    downsides are its a bit confusing with the media query syntax. we could also put the options second and mix them with the media query, defining our own special media query options essentially, but I don't like that in case we conflict in the future with css.

    from @jonschlinkert

    @options (multiple: true, less: true, include: true) {
        @import "file.less" (min-width: 400px);
    }
    

    and variations on the above, such as using closer media query syntax like

    @import (multiple: true) and (less: true) "file.less";
    

    I initially disliked @jonschlinkert's options idea, but it does actually allow for setting defaults.. what I don't like is that it looks a bit verbose.

    we could also assume :true and have

    @import (multiple, less) "file.less" (min-width:400px);
    

    and I am open to any other suggestions.

  • 10

    Mixins should accept LESS blocks

    It would be helpful if mixins had access to a content block passed to them, so one could encapsulate media queries or browser hacks in a central place and reference them symbolically.

    This is basically the "Passing Content Blocks to a Mixin" feature of SASS: http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#mixin-content

    In LESS this would mean something like:

    .mobile() {
      @media all and (max-device-width: 480px) {
        @content
      }
    }
    .big-desktop-button {
      ...
      .mobile {
        display:none;
      }
    }
    
  • 11

    Class constructor FileManager cannot be invoked without 'new'.

    It seems that v3.10.0 breaks my build, webpack logs below:

    ERROR in ./src/pages/score/components/current/no-join/index.less
    Module build failed (from ./node_modules/[email protected]@mini-css-extract-plugin/dist/loader.js):
    ModuleBuildError: Module build failed (from ./node_modules/[email protected]@less-loader/dist/cjs.js):
    
    
    Class constructor FileManager cannot be invoked without 'new'
          in undefined (line undefined, column undefined)
        at runLoaders (/home/admin/build/node_modules/[email protected]@webpack/lib/NormalModule.js:313:20)
        at /home/admin/build/node_modules/[email protected]@loader-runner/lib/LoaderRunner.js:367:11
        at /home/admin/build/node_modules/[email protected]@loader-runner/lib/LoaderRunner.js:233:18
        at context.callback (/home/admin/build/node_modules/[email protected]@loader-runner/lib/LoaderRunner.js:111:13)
    

    Everything is ok when downgrade to v3.9.0, please investigate this issue and hope to fix it as soon as possible.

  • 12

    Remove PhantomJS stuff

    I saw this and though, nobody is using phantomjs. it's a headless browser that no real user is using. it was based on webkit and the development have been discontinued. think you should remove this kind of things and instead use something like puppeteer or something.

    https://github.com/less/less.js/blob/eefe33a47f6fdcc228817df7435a1770ce9e51ea/packages/less/src/less-browser/index.js#L49-L56

    PhantomJS is depricated and should not be used anymore.

  • 13

    Global Variables with a literal "." in the value causes the variable to be undefined

    To reproduce: This can be reproduced using the CLI or the programmatic API. Assuming that LESS is already installed, it is most straightforward to reproduce using the CLI:

    echo "body { color: @buildVersion; }" | npx lessc --global-var="buildVersion=a.hello" -
    

    On the other hand, removing the literal period allows this to work:

    echo "body { color: @buildVersion; }" | npx lessc --global-var="buildVersion=hello" -
    

    LESS Code:

    body { color: @buildVersion; }
    

    Current behavior: The LESS compiler throws an error that the global variable is undefined.

    Expected behavior: The LESS compiler should not throw an error and the global variable should be defined.

    Environment information:

    • less version: 3.5.0 - 4.1.3 (current)
    • nodejs version: 16.13.2
    • operating system: Windows

    Appears to have been introduced in 3.5.0. Version 3.0.4 appears to be working fine.

  • 14

    Support for recently added CSS functionality container-queries

    As pointed out by @treponat, it seems support for @container queries should be implemented in less.

    By tomorrow (12/12/2022) Firefox will release 108 with support for container queries, completing all the major browsers support for the feature.

    Discussed in https://github.com/less/less.js/discussions/3759

    Originally posted by treponat November 3, 2022 Recently some of the browser started to support a new feature that would allow to create responsive design based on the parent element rather than client's viewport. https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries It would be wonderful if LESS would also start to fully support this feature. Currently you can use container functionality in .less files, but the hierarchical structure of css blocks is not working as expected for the container syntax. for example

    .widget.discoverresults, .widget.repositoriesresults {
            container-type: inline-size;
            @container (max-width: 350px) {           
                        .cite {
                            .wdr-authors {
                                display: none;
                             }
                    }
             }
    }
        
    

    would produce this css blocks

    .widget.discoverresults,
    .widget.repositoriesresults {
      container-type: inline-size;
    }
    @container (max-width: 350px) {
      .cite .wdr-authors {
        display: none;
      }
    }
    

    instead of

    .widget.discoverresults,
    .widget.repositoriesresults {
    	container-type: inline-size;
    }
    
    @container (max-width: 350px) {
    
    	.widget.discoverresults,
    	.widget.repositoriesresults {
    		.cite .wdr-authors {
    			display: none;
    		}
    	}
    }
    
  • 15

    Nested @media blocks are combined in a non-compliant way.

    According to the CSS spec, media type (screen, print etc) must be first on the list and all the other conditions (for example (max-width: 800px)) must follow after the media type.

    To reproduce:

    @media (max-width: 800px) {
      @media screen {
        .selector {color: black}
      }
    }
    

    Current behavior:

    Less compiler combines nested media conditions in a way that is not compliant with the spec, putting media type between conditions or after them.

    @media (max-width: 800px) and screen {
      .selector {
        color: black;
      }
    }
    

    Expected behavior:

    Media type should be put into the first position in the media query, like so:

    @media screen and (max-width: 800px) {
      .selector {
        color: black;
      }
    }
    

    Environment information:

    • less version: 4.1.3, including the official playground
    • nodejs version: any
    • operating system: any

    Additional notes

    Even though it might seem trivial to re-order the example above to produce correct results, it's not always possible since the inner @media screen might come from a third party (I'm working with antd) that I can't modify.

    It seems like most browsers don't mind media conditions being out of spec (or I didn't observe the effects), however this issue prevents me from using https://parceljs.org/ which implements pretty strict standards checking and chokes at the less output, producing Unexpected token Ident("screen"). The fixed snippet where media-type comes first is processed correctly by parcel.

  • 16

    allow image-size accept Url parameter

    What: image-size, image-width, image-height can accept parameter like url(a.png)

    Why: I can write code like

    .xxx{
         background: url(a.png);
         width: image-width($background);
    }
    

    else I have to write:

    @img: 'a.png'
    .xxx{
         background: url(@img);
         width: image-width(@img);
    }
    

    How:

    Checklist:

    • [ ] Documentation
    • [x] Added/updated unit tests
    • [x] Code complete
  • 17

    fix faulty source map generation with variables in selectors

    Note to reviewer: There are two potential solutions here, one that fixes the specific issue linked, and one that might be a bit more robust. Filter down to the first commit for the bandaid fix, second commit for the more robust one, third commit for added test. I recommend hiding whitespace changes in the GitHub UI – my editor removed a bunch of unnecessary trailing spaces

    PR Template

    What:

    Fixes https://github.com/less/less.js/issues/3567

    Source maps are pointing to the wrong files, and it seems that it points at different files on different runs, causing non-deterministic source map output.

    Checklist:

    • [x] Documentation - N/A
    • [x] Added/updated unit tests
    • [x] Code complete

    Why and How are included in the below description, which details the problem in the code and the proposed solutions.

    Investigation and the problems

    Bear with me a bit, this was my first venture into the less source code 🙂

    We were seeing that we got weird source map output, mapping to files that shouldn't even appear in the source maps (e.g. reference imports from which nothing was used). After digging, there are a couple of problems that are causing this whole issue.

    Background information

    I was able to narrow down my search a bit by finding when this bug was introduced. It was released in 3.5.0-beta.2, and the bug was introduced in https://github.com/less/less.js/pull/3227.

    We were seeing this happen when a selector included a variable, which directed me to this bit: https://github.com/less/less.js/pull/3227/files#diff-d8c0204835f49ae90096efe1e2d0d80868e0e6214bfd4c960a097eb20cc14ec9R67-R83

    It looks like what this is doing is recreating the selector CSS with the "variableCurly" node replaced with the variable's value. Then it parses that newly created CSS, and replaces the old Selector node(s) with the new one(s). It looks like the code is trying to preserve source information by passing in the fileInfo and index of the original selectors to parseNode: https://github.com/less/less.js/blob/eefe33a47f6fdcc228817df7435a1770ce9e51ea/packages/less/src/less/tree/ruleset.js#L85-L86

    For a little more context, my understanding is that parseNode is used for creating new nodes after the original input files are parsed.

    First problem

    In parseNode, the provided currentIndex and fileInfo are added to the newly created node(s) here: https://github.com/less/less.js/blob/eefe33a47f6fdcc228817df7435a1770ce9e51ea/packages/less/src/less/parser/parser.js#L111-L112

    These lines assume that result is a tree node. However, in some cases, result is actually an array of nodes. So when it tries to add the source info from the old selectors to the new ones, it's actually just setting _index and _fileInfo properties on the array itself, so it doesn't actually ever make it to the source maps. In this case, the parseList is ["selectors"], so result is an array of Selector tree nodes.

    Second problem

    Selector nodes have an elements array, containing the elements that make up the selector. When a Selector is added to the output, it actually does so by generating the CSS for each of its elements: https://github.com/less/less.js/blob/eefe33a47f6fdcc228817df7435a1770ce9e51ea/packages/less/src/less/tree/selector.js#L135-L138

    This means that if the _fileInfo or _index for the Selector's elements is incorrect, then the output source map will be incorrect. That also means that even if the _fileInfo and _index were correctly set on the Selector(s) rather than the array containing the Selector(s), the source maps would still be incorrect on the Elements that make up the selector. The source info for the elements gets set here: https://github.com/less/less.js/blob/eefe33a47f6fdcc228817df7435a1770ce9e51ea/packages/less/src/less/parser/parser.js#L1299

    There are two problems here:

    1. The currentIndex passed into parseNode doesn't get added to the created Elements index
    2. That fileInfo is not the fileInfo that gets passed into parseNode. It's the fileInfo that's given to the Parser when it's instantiated: https://github.com/less/less.js/blob/eefe33a47f6fdcc228817df7435a1770ce9e51ea/packages/less/src/less/parser/parser.js#L41

    The proposed solution(s)

    I have two proposals for how we can solve this. One is more of a bandaid fix, and the other is (I believe) a bit more robust. Both solutions include the fix for where _fileInfo and _index get set on an array.

    1. Bandaid fix by going through the elements for each selector returned by parseNode, and update the _fileInfo and add the currentIndex to the _index for each one.
    2. Instead of calling this.parse.parseNode and relying on parseNode to update the _index and _fileInfo of the created nodes, create a new Parser object each time we want to call parseNode. This removes the currentIndex and fileInfo params for parseNode, and adds a currentIndex param to the Parser itself. All nodes created by the parser will add currentIndex (which defaults to 0) to the index from parserInput. This way, we can ensure that any nodes created with parseNode will have the correct source information.

    The first proposed solution can be seen by filtering to the first commit, and the second proposed solution can be seen by filtering to both the first and second commits. The third commit adds a test to ensure that selectors with variables in them have properly generated source maps. I recommend hiding whitespace changes in the GitHub UI – my editor removed a bunch of unnecessary trailing spaces.