Get less variables list using less.js

2019-04-18 05:34发布

问题:

I'm using client-side less.js. Is there a way to get all variables from my less file. I know how to modify variables:

less.modifyVars({
  "@var": "#fff"
});

But I want to get all of them, like (don't work):

var variables = less.getVars();

回答1:

This is going to be an unconventional answer as it seems that this data isn't publicly exposed as part of the browser facing API.


tl;dr

  • Save a local copy of the less.js file.
  • Add this function definition somewhere

    function exposeVars(root) {
         var r=root._variables,n=Object.keys(r),m={}
         for(var k of n){m[k]=r[k].value}
             less.variables = m;
    }
    
  • Add the following call exposeVars(evaldRoot) just before return result on line ~2556.

  • Now less.variables contains all the variables from your file.

Disclaimer: Doing this is not a good idea! It's fine if you're just playing around, debugging or testing something, but don't depend on this hack for anything serious!


The basic aim here was to find the point in the source where the .less files are turned into abstract syntax trees (or some other formal structure).

Jumping straight into the source, I found a ParseTree class. It's a reasonable assumption to guess that it will contain the result of parsing the less file.

I wrote a quick test file and added it to the browser with the appropriate tag. It looks like this:

@myvar: red;
@othervar: 12px;

body {
  padding: @othervar;
  background: @myvar;
}

I've downloaded a local copy of less.js and added a breakpoint added to line 2556.

I had a poke around in the local scope to see what was available and found the variables in an object called evaldRoot.

evaldRoot = {
  _variables: {
    @myvar: {
      name: '@myvar',
      value: Color
    },
    @othervar: {
      name: '@othervar',
      value: Dimension
    }
  }
}

Next job was to work out where this data goes. It seems like the evaldRoot variable is used to generate the resulting CSS (which would make sense, as it contains information such as variables).

if (options.sourceMap) {
  sourceMapBuilder = new SourceMapBuilder(options.sourceMap);
  result.css = sourceMapBuilder.toCSS(evaldRoot, toCSSOptions, this.imports);
} else {
  result.css = evaldRoot.toCSS(toCSSOptions);
}

Whatever happens, the variables goes out of scope after it is used to generate a string of CSS as result.css.

To expose of these variables, the script needs a small modification. You'll have to expose the variables publicly somehow. Here's an example of doing that.

function exposeVars(root) {
  var varNames = Object.keys(root._variables);

  var variables = varNames.reduce(function(varMap, varName) {
    varMap[varName] = root._variables[varName].value;
  }, {});

  less.variables = variables;
}

This can be added just before the return statement with the breakpoint.

exposeVars(evaldRoot);
return result;

Now the variables will be available in a name: value object on the global less object.

You could even modify the expose function to return the variables from a call to less.getVars(). Just like your initial suggestion.

function exposeVars(root) {
  // ...
  less.getVars = function() {
    return variables;
  };
}