Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions config/eslint/base.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module.exports = {
'parser': '@typescript-eslint/parser',
'parserOptions': {
'ecmaVersion': 2022,
'sourceType': 'module'
},
'plugins': ['@typescript-eslint'],
'extends': [
'eslint:recommended'
],
'env': {
'browser': true,
'node': true,
'mocha': true
},
'rules': {
'indent': ['error', 4, { 'SwitchCase': 1 }],
'no-empty': ['error', { 'allowEmptyCatch': true }],
'quotes': ['error', 'single', { 'avoidEscape': true }],
/**
* The codebase uses some while(true) statements.
* Refactor to remove this rule.
*/
'no-constant-condition': 0,
/**
* Less combines assignments with conditionals sometimes
*/
'no-cond-assign': 0,
'no-multiple-empty-lines': 'error'
}
};
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"description": "Less monorepo",
"homepage": "http://lesscss.org",
"scripts": {
"lint": "eslint packages/less --ext .js,.ts",
"lint:fix": "eslint packages/less --ext .js,.ts --fix",
"publish": "node scripts/bump-and-publish.js",
"publish:dry-run": "DRY_RUN=true node scripts/bump-and-publish.js",
"publish:beta": "node scripts/publish-beta.js",
Expand All @@ -28,7 +30,10 @@
"url": "https://github.com/less/less.js.git"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.28.0",
"@typescript-eslint/parser": "^4.28.0",
"all-contributors-cli": "~6.26.1",
"eslint": "^7.29.0",
"github-changes": "^1.1.2",
"husky": "~9.1.7",
"npm-run-all": "^4.1.5",
Expand Down
64 changes: 19 additions & 45 deletions packages/less/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -1,63 +1,37 @@
module.exports = {
'parser': '@typescript-eslint/parser',
'extends': 'eslint:recommended',
'parserOptions': {
'ecmaVersion': 2018,
'sourceType': 'module'
},
'plugins': ['@typescript-eslint'],
'env': {
'browser': true,
'node': true,
'mocha': true
},
'globals': {},
'rules': {
indent: ['error', 4, {
SwitchCase: 1
}],
'no-empty': ['error', { 'allowEmptyCatch': true }],
quotes: ['error', 'single', {
avoidEscape: true
}],
/**
* The codebase uses some while(true) statements.
* Refactor to remove this rule.
*/
'no-constant-condition': 0,
/**
* Less combines assignments with conditionals sometimes
*/
'no-cond-assign': 0,
/**
* @todo - remove when some kind of code style (XO?) is added
*/
'no-multiple-empty-lines': 'error'
},
'extends': ['../../config/eslint/base.cjs'],
'overrides': [
{
files: ['*.ts'],
extends: ['plugin:@typescript-eslint/recommended'],
'extends': ['plugin:@typescript-eslint/recommended'],
rules: {
/**
* Suppress until Less has better-defined types
* @see https://github.com/less/less.js/discussions/3786
*/
'@typescript-eslint/no-explicit-any': 0
}
},
{
files: ['lib/**/*.{js,ts}'],
rules: {
'no-unused-vars': 0,
'no-redeclare': 0
}
},
{
files: ['benchmark/**/*.{js,ts}', 'build/**/*.{js,ts}', 'scripts/**/*.{js,ts}'],
rules: {
'no-unused-vars': 0,
'no-redeclare': 0,
'no-undef': 0
}
},
{
files: ['test/**/*.{js,ts}', 'benchmark/index.js'],
/**
* @todo - fix later
*/
rules: {
'no-undef': 0,
'no-useless-escape': 0,
'no-unused-vars': 0,
'no-redeclare': 0,
'@typescript-eslint/no-unused-vars': 0
}
},
}
]
}
};
184 changes: 71 additions & 113 deletions packages/less/benchmark/benchmark-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,58 +14,58 @@ var extraOpts = {};

// Parse --key=value options from remaining args
for (var ai = 5; ai < process.argv.length; ai++) {
var optMatch = process.argv[ai].match(/^--([a-z-]+)=(.*)$/);
if (optMatch) { extraOpts[optMatch[1]] = optMatch[2]; }
var optMatch = process.argv[ai].match(/^--([a-z-]+)=(.*)$/);
if (optMatch) { extraOpts[optMatch[1]] = optMatch[2]; }
}

if (!file) {
console.error('Usage: node benchmark-runner.js <file.less> [runs] [warmup]');
process.exit(1);
console.error('Usage: node benchmark-runner.js <file.less> [runs] [warmup]');
process.exit(1);
}

// Find Less compiler - try multiple paths for different version eras
var less;
var lessPath = '';
var tryPaths = [
// v4.x monorepo (after build)
'./packages/less',
// v3.x / v2.x (lib in repo)
'.',
'./lib/less-node',
// Fallback
'less'
// v4.x monorepo (after build)
'./packages/less',
// v3.x / v2.x (lib in repo)
'.',
'./lib/less-node',
// Fallback
'less'
];

for (var i = 0; i < tryPaths.length; i++) {
try {
var p = tryPaths[i];
// Use path.resolve for relative paths, but keep bare package names for Node resolution
var mod = require(p.startsWith('.') ? path.resolve(p) : p);
// Handle both direct export and .default (ESM interop)
less = mod && mod.default ? mod.default : mod;
if (less && (less.render || less.parse)) {
lessPath = p;
break;
}
less = null;
} catch (e) {
try {
var p = tryPaths[i];
// Use path.resolve for relative paths, but keep bare package names for Node resolution
var mod = require(p.startsWith('.') ? path.resolve(p) : p);
// Handle both direct export and .default (ESM interop)
less = mod && mod.default ? mod.default : mod;
if (less && (less.render || less.parse)) {
lessPath = p;
break;
}
less = null;
} catch (e) {
// try next
}
}
}

if (!less) {
console.error(JSON.stringify({ error: 'Could not find Less compiler', tried: tryPaths }));
process.exit(2);
console.error(JSON.stringify({ error: 'Could not find Less compiler', tried: tryPaths }));
process.exit(2);
}

// Determine version
var version = 'unknown';
if (less.version) {
if (Array.isArray(less.version)) {
version = less.version.join('.');
} else {
version = String(less.version);
}
if (Array.isArray(less.version)) {
version = less.version.join('.');
} else {
version = String(less.version);
}
}

var filePath = path.resolve(file);
Expand All @@ -78,98 +78,56 @@ var parseTimes = [];
var completed = 0;
var errors = [];

/**
* Returns the current high-resolution time in milliseconds.
* @returns {number} Current time in ms, with sub-ms precision.
*/
function hrNow() {
var hr = process.hrtime();
return hr[0] * 1000 + hr[1] / 1e6;
var hr = process.hrtime();
return hr[0] * 1000 + hr[1] / 1e6;
}

/**
* Runs the Less compiler against the input file exactly once, recording
* the elapsed time. Pushes to renderTimes on success; records errors.
* @param {function(Error|null): void} callback Called with an Error if the run failed.
* @returns {void}
*/
function runOnce(callback) {
var start = hrNow();
var opts = {
filename: filePath,
paths: [fileDir]
};
// Forward extra options (e.g. --math=always)
for (var key in extraOpts) { opts[key] = extraOpts[key]; }
less.render(data, opts, function (err, output) {
var end = hrNow();
if (err) {
errors.push({ run: completed, error: err.message || String(err) });
callback(err);
return;
}
renderTimes.push(end - start);
completed++;
callback(null);
});
}

/**
* Invokes runOnce repeatedly until totalRuns has been reached, then
* reports results. Bails early after 4 errors to avoid hanging on a broken Less.
* @param {number} i Current iteration counter.
* @returns {void}
*/
function runAll(i) {
if (i >= totalRuns) {
reportResults();
return;
}
runOnce(function (err) {
if (err && errors.length > 3) {
// Too many errors, bail
reportResults();
return;
}
runAll(i + 1);
});
}

/**
* Computes summary statistics for a list of timing samples, optionally
* skipping the warmup window.
* @param {number[]} times Elapsed-time samples in milliseconds.
* @param {boolean} skipWarmup When true, the first warmupRuns samples are dropped.
* @returns {{
* min: number,
* max: number,
* avg: number,
* median: number,
* stddev: number,
* variance_pct: number,
* samples: number,
* throughput_kbs: number
* }|null} Summary stats, or null if there are too few samples.
*/
function analyze(times, skipWarmup) {
var start = skipWarmup ? warmupRuns : 0;
if (times.length <= start) return null;
var effective = times.slice(start);
var total = 0, min = Infinity, max = 0;
for (var i = 0; i < effective.length; i++) {
total += effective[i];
min = Math.min(min, effective[i]);
max = Math.max(max, effective[i]);
}
var avg = total / effective.length;

// Median
var sorted = effective.slice().sort(function (a, b) { return a - b; });
var mid = Math.floor(sorted.length / 2);
var median = sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;

// Standard deviation and coefficient of variation
var sumSqDiff = 0;
for (var i = 0; i < effective.length; i++) {
sumSqDiff += (effective[i] - avg) * (effective[i] - avg);
}
var stddev = Math.sqrt(sumSqDiff / effective.length);
var variancePct = avg === 0 ? 0 : (stddev / avg) * 100;

return {
min: Math.round(min * 100) / 100,
max: Math.round(max * 100) / 100,
avg: Math.round(avg * 100) / 100,
median: Math.round(median * 100) / 100,
stddev: Math.round(stddev * 100) / 100,
variance_pct: Math.round(variancePct * 100) / 100,
samples: effective.length,
throughput_kbs: Math.round(1000 / avg * data.length / 1024)
};
}

/**
* Emits the final benchmark result as JSON to stdout. Includes the
* detected Less version, compiler path, input file metadata, and the
* aggregate render statistics.
* @returns {void}
*/
function reportResults() {
var result = {
version: version,
lessPath: lessPath,
file: path.basename(file),
fileSize: data.length,
fileSizeKB: Math.round(data.length / 1024 * 10) / 10,
totalRuns: totalRuns,
warmupRuns: warmupRuns,
completedRuns: completed,
errors: errors.length > 0 ? errors : undefined,
render: analyze(renderTimes, true)
};
console.log(JSON.stringify(result));
}

runAll(0);
2 changes: 1 addition & 1 deletion packages/less/build/rollup.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function moduleShim() {
},
load(id) {
if (id === '\0module') {
return `export function createRequire() { return require; }`;
return 'export function createRequire() { return require; }';
}
return null;
}
Expand Down
6 changes: 3 additions & 3 deletions packages/less/lib/less-node/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class SourceMapGeneratorFallback {
toJSON(){
return null;
}
};
}

export default {
encodeBase64: function encodeBase64(str) {
Expand All @@ -19,9 +19,9 @@ export default {
mimeLookup: function (filename) {
try {
const mimeModule = require('mime');
return mimeModule ? mimeModule.lookup(filename) : "application/octet-stream";
return mimeModule ? mimeModule.lookup(filename) : 'application/octet-stream';
} catch (e) {
return "application/octet-stream";
return 'application/octet-stream';
}
},
charsetLookup: function (mime) {
Expand Down
Loading
Loading