LESS parser for PostCSS that enables PostCSS transformations and plugins to work directly with LESS source code without compilation.
npx @tessl/cli install tessl/npm-postcss-less@6.0.0postcss-less is a PostCSS syntax parser specifically designed for LESS stylesheets. It enables PostCSS transformations and plugins to work directly with LESS source code without compilation, serving as a bridge between LESS syntax and the PostCSS ecosystem.
npm install postcss-lesspostcss ^8.3.5const syntax = require('postcss-less');For ES6 modules:
import syntax from 'postcss-less';Individual imports:
const { parse, stringify, nodeToString } = require('postcss-less');const postcss = require('postcss');
const syntax = require('postcss-less');
// Parse LESS code with PostCSS
const lessCode = `
// This is an inline comment
@primary-color: #428bca;
@secondary-color: lighten(@primary-color, 20%);
.header {
color: @primary-color;
.nav {
background: @secondary-color;
}
}
.mixin-example() {
border-radius: 4px;
}
.button {
.mixin-example();
&:hover {
color: darken(@primary-color, 10%);
}
}
`;
// Process with PostCSS and postcss-less syntax
postcss([
// Add your PostCSS plugins here
])
.process(lessCode, { syntax })
.then(function (result) {
console.log(result.css); // Processed LESS code
});postcss-less extends the PostCSS parser and stringifier to handle LESS-specific syntax:
Parse LESS source code into a PostCSS-compatible AST with LESS-specific node properties.
/**
* Parse LESS source code into PostCSS AST
* @param {string} less - LESS source code to parse
* @param {Object} options - PostCSS Input options (from, map, etc.)
* @returns {Root} PostCSS Root AST node with LESS-specific extensions
*/
function parse(less, options);Usage Example:
const syntax = require('postcss-less');
const lessCode = '@color: red; .test { color: @color; }';
const root = syntax.parse(lessCode, { from: 'input.less' });
// Access LESS-specific node properties
root.walkAtRules((rule) => {
if (rule.variable) {
console.log(`Variable: ${rule.name} = ${rule.value}`);
}
});Convert PostCSS AST nodes back to LESS syntax strings.
/**
* Convert PostCSS AST nodes to LESS syntax strings
* @param {Node} node - PostCSS AST node to stringify
* @param {Function} builder - String builder callback function
*/
function stringify(node, builder);Usage Example:
const syntax = require('postcss-less');
const lessCode = '.mixin() { color: red; }';
const root = syntax.parse(lessCode);
syntax.stringify(root, (str) => {
process.stdout.write(str);
});Convert individual AST nodes to their string representation.
/**
* Convert a single AST node to its string representation
* @param {Node} node - PostCSS AST node to convert
* @returns {string} String representation of the node
*/
function nodeToString(node);Usage Example:
const syntax = require('postcss-less');
const lessCode = '@var: 10px; .test { margin: @var; }';
const root = syntax.parse(lessCode);
root.walkAtRules((rule) => {
if (rule.variable) {
const nodeStr = syntax.nodeToString(rule);
console.log(`Variable node: ${nodeStr}`);
}
});LESS variables (@variable-name: value;) are parsed with special properties:
// Variable AtRule node properties
interface VariableAtRule extends AtRule {
variable: true; // Marks node as LESS variable
name: string; // Variable name (without @)
value: string; // Variable value
}LESS @import statements with options are supported:
// Import AtRule node properties
interface ImportAtRule extends AtRule {
import: true; // Marks node as import
filename: string; // Imported filename
options?: string; // Import options (reference, inline, etc.)
}Example:
@import (reference) "variables.less";
@import "mixins.less";LESS mixins (.mixin() and #mixin) are parsed with special properties:
// Mixin AtRule node properties
interface MixinAtRule extends AtRule {
mixin: true; // Marks node as mixin call
important?: true; // Present if mixin uses !important
}LESS each functions and other function calls are supported:
// Function AtRule node properties
interface FunctionAtRule extends AtRule {
function: true; // Marks node as function call
params: string; // Function parameters
}Example:
each(@list, {
.column-@{value} {
width: 100% / @length;
}
});LESS variable interpolation (@{variable}) is processed during parsing to handle dynamic property names and values.
Double-slash comments (//) are supported with special properties:
// Inline Comment node properties
interface InlineComment extends Comment {
inline: true; // Marks comment as inline
raws: {
begin: '//'; // Comment delimiter
left: string; // Whitespace before text
right: string; // Whitespace after text
};
}LESS :extend() syntax is recognized on both rules and declarations:
// Extend Rule/Declaration properties
interface ExtendNode extends Rule | Declaration {
extend: true; // Marks node as using extend
}postcss-less follows PostCSS error handling patterns. Parse errors throw CssSyntaxError:
const postcss = require('postcss');
const syntax = require('postcss-less');
const CssSyntaxError = require('postcss/lib/css-syntax-error');
try {
const result = await postcss().process('.@{]', { syntax });
} catch (error) {
if (error instanceof CssSyntaxError) {
console.log(`Parse error at line ${error.line}, column ${error.column}: ${error.message}`);
}
}Use as a syntax plugin with PostCSS processors:
const postcss = require('postcss');
const autoprefixer = require('autoprefixer');
const syntax = require('postcss-less');
// Apply autoprefixer to LESS code
postcss([autoprefixer])
.process(lessCode, {
syntax,
from: 'input.less',
to: 'output.less'
})
.then(result => {
console.log(result.css); // LESS code with vendor prefixes
});// Main syntax object
interface PostcssLessSyntax {
parse: (less: string, options?: ProcessOptions) => Root;
stringify: (node: Node, builder: (str: string) => void) => void;
nodeToString: (node: Node) => string;
}
// PostCSS types (from postcss package)
interface ProcessOptions {
from?: string;
to?: string;
map?: SourceMapOptions | boolean;
parser?: Parser;
stringifier?: Stringifier;
}
interface Root extends Container {
type: 'root';
walk(callback: (node: Node) => boolean | void): Root;
walkAtRules(callback: (atRule: AtRule) => boolean | void): Root;
walkComments(callback: (comment: Comment) => boolean | void): Root;
walkDecls(callback: (decl: Declaration) => boolean | void): Root;
walkRules(callback: (rule: Rule) => boolean | void): Root;
}
interface AtRule extends Container {
type: 'atrule';
name: string;
params: string;
// LESS extensions
import?: boolean;
filename?: string;
options?: string;
variable?: boolean;
value?: string;
mixin?: boolean;
function?: boolean;
important?: boolean;
}
interface Comment extends Node {
type: 'comment';
text: string;
// LESS extensions
inline?: boolean;
}
interface Rule extends Container {
type: 'rule';
selector: string;
// LESS extensions
extend?: boolean;
}
interface Declaration extends Node {
type: 'decl';
prop: string;
value: string;
// LESS extensions
extend?: boolean;
}