less 5.0.0-alpha.2 → 5.0.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Less - Leaner CSS v5.0.0-alpha.2
2
+ * Less - Leaner CSS v5.0.0-alpha.4
3
3
  * http://lesscss.org
4
4
  *
5
5
  * Copyright (c) 2009-2026, Alexis Sellier <self@cloudhead.net>
@@ -18,8 +18,8 @@ var compiler = require('@jesscss/compiler');
18
18
  var nodeModulesPlugin = require('@jesscss/plugin-node-modules');
19
19
  var lessPlugin = require('@jesscss/plugin-less');
20
20
  var pluginLessCompat = require('@jesscss/plugin-less-compat');
21
- var parseNodeVersion = require('parse-node-version');
22
21
  var core = require('@jesscss/core');
22
+ var parseNodeVersion = require('parse-node-version');
23
23
 
24
24
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
25
25
 
@@ -27,6 +27,78 @@ var nodeModulesPlugin__default = /*#__PURE__*/_interopDefaultLegacy(nodeModulesP
27
27
  var lessPlugin__default = /*#__PURE__*/_interopDefaultLegacy(lessPlugin);
28
28
  var parseNodeVersion__default = /*#__PURE__*/_interopDefaultLegacy(parseNodeVersion);
29
29
 
30
+ /**
31
+ * Less logger wired to Jess's logger singleton.
32
+ * Forwards to Jess and maintains Less-style addListener/removeListener for compatibility.
33
+ * @module less/lib/logger
34
+ */
35
+
36
+ /** @typedef {{ error?: (msg: string) => void, warn?: (msg: string) => void, info?: (msg: string) => void, debug?: (msg: string) => void }} LogListener */
37
+
38
+ /** @type {LogListener[]} */
39
+ const _listeners = [];
40
+
41
+ /** @param {'error'|'warn'|'info'|'debug'} type @param {string} msg */
42
+ function _fireEvent(type, msg) {
43
+ for (const listener of _listeners) {
44
+ const fn = listener[type];
45
+ if (fn) fn(msg);
46
+ }
47
+ }
48
+
49
+ // Wrap Jess's logger so Less listeners receive Jess's log output without
50
+ // writing directly to stderr/stdout. Library consumers should be able to catch
51
+ // `less.render()` rejections without surprise terminal output; the CLI owns
52
+ // deciding whether/how to print diagnostics.
53
+ core.logger.configure?.({
54
+ log(...args) {
55
+ _fireEvent('debug', args.map(String).join(' '));
56
+ },
57
+ info(...args) {
58
+ _fireEvent('info', args.map(String).join(' '));
59
+ },
60
+ warn(...args) {
61
+ _fireEvent('warn', args.map(String).join(' '));
62
+ },
63
+ error(...args) {
64
+ _fireEvent('error', args.map(String).join(' '));
65
+ },
66
+ });
67
+
68
+ /** Less-compatible logger backed by Jess's singleton */
69
+ const logger = {
70
+ /** @param {string} msg */
71
+ error(msg) {
72
+ _fireEvent('error', msg);
73
+ },
74
+
75
+ /** @param {string} msg */
76
+ warn(msg) {
77
+ _fireEvent('warn', msg);
78
+ },
79
+
80
+ /** @param {string} msg */
81
+ info(msg) {
82
+ _fireEvent('info', msg);
83
+ },
84
+
85
+ /** @param {string} msg */
86
+ debug(msg) {
87
+ _fireEvent('debug', msg);
88
+ },
89
+
90
+ /** @param {LogListener} listener */
91
+ addListener(listener) {
92
+ _listeners.push(listener);
93
+ },
94
+
95
+ /** @param {LogListener} listener */
96
+ removeListener(listener) {
97
+ const i = _listeners.indexOf(listener);
98
+ if (i >= 0) _listeners.splice(i, 1);
99
+ },
100
+ };
101
+
30
102
  /**
31
103
  * Options mapping between Less render options and Jess compiler config.
32
104
  * @module less/lib/options
@@ -41,7 +113,6 @@ const unsupportedAlphaOptions = new Map([
41
113
  ['sourceMapFileInline', 'source maps are not supported'],
42
114
  ['globalVars', 'global variable injection is not supported'],
43
115
  ['modifyVars', 'modify-var injection is not supported'],
44
- ['strictUnits', 'strict unit mode is not supported'],
45
116
  ['rootpath', 'URL rootpath rewriting is not supported'],
46
117
  ['rewriteUrls', 'URL rewriting is not supported'],
47
118
  ['urlArgs', 'URL argument rewriting is not supported'],
@@ -51,7 +122,9 @@ const unsupportedAlphaOptions = new Map([
51
122
 
52
123
  function validateAlphaOptions(options) {
53
124
  for (const [name, reason] of unsupportedAlphaOptions) {
54
- if (Object.prototype.hasOwnProperty.call(options, name)) {
125
+ // A falsy value is the 4.x default ("off") and requests nothing, so it is
126
+ // a no-op here; only an actual request for the feature is unsupported.
127
+ if (options[name]) {
55
128
  throw new Error(`${name} is not supported: ${reason}`);
56
129
  }
57
130
  }
@@ -107,6 +180,21 @@ function createLessOptions(options) {
107
180
  math === 2 || math === 'parens' || math === 'strict' ? 'parens' :
108
181
  'parens-division';
109
182
 
183
+ // `unitMode` is the option ('loose' | 'preserve' | 'strict'); `strictUnits`
184
+ // is its deprecated boolean alias: true → 'strict'; false means "not strict",
185
+ // i.e. the default ('preserve') — never the Less 4.x 'loose' fold, which only
186
+ // an explicit `unitMode: 'loose'` selects. Any use warns so the mapping is
187
+ // never discovered by staring at output. Left unset so the compiler default applies.
188
+ const unitMode = opts.unitMode !== undefined ? opts.unitMode
189
+ : opts.strictUnits === true ? 'strict'
190
+ : undefined;
191
+ if (opts.strictUnits !== undefined && opts.unitMode === undefined) {
192
+ logger.warn(
193
+ `strictUnits is deprecated; use unitMode. strictUnits: ${String(opts.strictUnits)} now means `
194
+ + `unitMode: '${unitMode ?? 'preserve'}'${opts.strictUnits ? '' : " (Less 4.x unit folding is unitMode: 'loose')"}`
195
+ );
196
+ }
197
+
110
198
  const plugins = [lessPlugin__default["default"]()];
111
199
  if (!skipLessCompat) {
112
200
  plugins.push(pluginLessCompat.lessCompatPlugin({ plugins: lessPlugins }));
@@ -116,6 +204,7 @@ function createLessOptions(options) {
116
204
  compile: {
117
205
  searchPaths: opts.paths || [],
118
206
  mathMode,
207
+ ...(unitMode !== undefined && { unitMode }),
119
208
  plugins,
120
209
  },
121
210
  // Less v5 preserves authored nesting unless its explicit compatibility
@@ -172,7 +261,7 @@ function mapRenderResult(result, options) {
172
261
  * @module less/lib/version
173
262
  */
174
263
 
175
- const semver = "5.0.0-alpha.2";
264
+ const semver = "5.0.0-alpha.4";
176
265
  const parsed = parseNodeVersion__default["default"](`v${semver}`);
177
266
 
178
267
  const version = {
@@ -180,78 +269,6 @@ const version = {
180
269
  array: [parsed.major, parsed.minor, parsed.patch],
181
270
  };
182
271
 
183
- /**
184
- * Less logger wired to Jess's logger singleton.
185
- * Forwards to Jess and maintains Less-style addListener/removeListener for compatibility.
186
- * @module less/lib/logger
187
- */
188
-
189
- /** @typedef {{ error?: (msg: string) => void, warn?: (msg: string) => void, info?: (msg: string) => void, debug?: (msg: string) => void }} LogListener */
190
-
191
- /** @type {LogListener[]} */
192
- const _listeners = [];
193
-
194
- /** @param {'error'|'warn'|'info'|'debug'} type @param {string} msg */
195
- function _fireEvent(type, msg) {
196
- for (const listener of _listeners) {
197
- const fn = listener[type];
198
- if (fn) fn(msg);
199
- }
200
- }
201
-
202
- // Wrap Jess's logger so Less listeners receive Jess's log output without
203
- // writing directly to stderr/stdout. Library consumers should be able to catch
204
- // `less.render()` rejections without surprise terminal output; the CLI owns
205
- // deciding whether/how to print diagnostics.
206
- core.logger.configure?.({
207
- log(...args) {
208
- _fireEvent('debug', args.map(String).join(' '));
209
- },
210
- info(...args) {
211
- _fireEvent('info', args.map(String).join(' '));
212
- },
213
- warn(...args) {
214
- _fireEvent('warn', args.map(String).join(' '));
215
- },
216
- error(...args) {
217
- _fireEvent('error', args.map(String).join(' '));
218
- },
219
- });
220
-
221
- /** Less-compatible logger backed by Jess's singleton */
222
- const logger = {
223
- /** @param {string} msg */
224
- error(msg) {
225
- _fireEvent('error', msg);
226
- },
227
-
228
- /** @param {string} msg */
229
- warn(msg) {
230
- _fireEvent('warn', msg);
231
- },
232
-
233
- /** @param {string} msg */
234
- info(msg) {
235
- _fireEvent('info', msg);
236
- },
237
-
238
- /** @param {string} msg */
239
- debug(msg) {
240
- _fireEvent('debug', msg);
241
- },
242
-
243
- /** @param {LogListener} listener */
244
- addListener(listener) {
245
- _listeners.push(listener);
246
- },
247
-
248
- /** @param {LogListener} listener */
249
- removeListener(listener) {
250
- const i = _listeners.indexOf(listener);
251
- if (i >= 0) _listeners.splice(i, 1);
252
- },
253
- };
254
-
255
272
  /**
256
273
  * Helper functions for lessc CLI.
257
274
  * Adapted from lib.bak/less-node/lessc-helper.js.
@@ -292,6 +309,8 @@ const lesscHelper = {
292
309
  console.log(' -v, --version Prints version number and exit.');
293
310
  console.log(' --verbose Be verbose.');
294
311
  console.log(' --collapse-nesting Flatten nested rules after preserving source-order cascade.');
312
+ console.log(' --unit-mode=MODE Unit handling in math: preserve (default), strict, or loose (Less 4.x guessing).');
313
+ console.log(' --strict-units[=on|off] Deprecated: on is --unit-mode=strict, off is the default (preserve).');
295
314
  console.log('');
296
315
  console.log('This release intentionally supports a smaller CLI surface.');
297
316
  console.log('Source maps, browser compilation, legacy plugin flags, lint-only mode, and');
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Less.js v5 — browser build (powered by Jess).
3
+ *
4
+ * A single-file, in-browser Less→CSS compiler. It drives `@jesscss/compiler`'s
5
+ * `renderToResult` with an inline `source` and NO `filePath`, so the Node-only
6
+ * config-discovery layer (cosmiconfig/env-paths/fs) is never reached. `@import`
7
+ * / file access is unsupported here (see build/browser-stubs/fs.js).
8
+ *
9
+ * Bundled to an IIFE that defines `window.less` with the same public shape as
10
+ * the Less 4.x browser build: `less.render(input, options?, callback?)` returns
11
+ * a Promise and also invokes the err-first callback when one is given.
12
+ *
13
+ * @module less/browser-dev
14
+ */
15
+
16
+ import { Compiler } from '@jesscss/compiler';
17
+ import { createLessOptions, mapRenderResult } from './options.js';
18
+
19
+ /* Injected at build time from packages/less/package.json. */
20
+ /* global __LESS_VERSION__ */
21
+ const semver = typeof __LESS_VERSION__ === 'string' ? __LESS_VERSION__ : '5.0.0-alpha.0';
22
+ const versionArray = semver.split('.').map((n) => parseInt(n, 10) || 0).slice(0, 3);
23
+
24
+ /**
25
+ * Build a Less-4.x-shaped error from the first Jess diagnostic. No fs fallback:
26
+ * in the browser there is no file to re-read for the source extract.
27
+ * @param {import('./options.js').JessRenderResult} result
28
+ */
29
+ function createRenderError(result) {
30
+ const diagnostic = (result?.errors || [])[0];
31
+ const error = new Error(diagnostic?.message || 'Less render failed');
32
+ error.type = diagnostic?.phase || 'Syntax';
33
+ error.filename = diagnostic?.filePath || 'input';
34
+ error.line = diagnostic?.line || 1;
35
+ error.column = diagnostic?.column || 1;
36
+ const lines = diagnostic?.lines;
37
+ error.extract = Array.isArray(lines) ? lines.map(String) : undefined;
38
+ error.jessErrors = result?.errors || [];
39
+ error.jessWarnings = result?.warnings || [];
40
+ return error;
41
+ }
42
+
43
+ /**
44
+ * Render Less source to CSS. Mirrors the Less 4.x API: `options` and `callback`
45
+ * are both optional, `callback` may be passed as the second argument, and the
46
+ * returned Promise resolves to a `{ css, warnings }` result. When a callback is
47
+ * given it is invoked err-first and the Promise is still returned.
48
+ * @param {string} input Less source
49
+ * @param {object|Function} [options] Less-style options (math, collapseNesting, plugins) — or the callback
50
+ * @param {Function} [callback] err-first `(error, result)` callback
51
+ * @returns {Promise<{ css: string, warnings?: unknown[] }>}
52
+ */
53
+ function render(input, options, callback) {
54
+ if (typeof options === 'function') {
55
+ callback = options;
56
+ options = {};
57
+ }
58
+ options = options || {};
59
+ const promise = (async () => {
60
+ const { configOptions } = createLessOptions(options);
61
+ // ponytail: fresh Compiler per call — no defaultPlugins hook, so no
62
+ // node-modules import plugin and no @jesscss/plugin-js. Single-file preview
63
+ // rarely re-renders in a hot loop; a cache map is not worth the surface.
64
+ const compiler = new Compiler(configOptions);
65
+ const result = await compiler.renderToResult(
66
+ { source: input, language: 'less', extension: '.less' },
67
+ { ...configOptions, suppressWarnings: true }
68
+ );
69
+ if (result.errors?.length) {
70
+ throw createRenderError(result);
71
+ }
72
+ return mapRenderResult(result, options);
73
+ })();
74
+ if (typeof callback === 'function') {
75
+ promise.then((result) => callback(null, result), (error) => callback(error));
76
+ }
77
+ return promise;
78
+ }
79
+
80
+ const less = {
81
+ version: versionArray,
82
+ render,
83
+ };
84
+
85
+ export default less;
86
+ export { render, versionArray as version };
@@ -38,6 +38,8 @@ const lesscHelper = {
38
38
  console.log(' -v, --version Prints version number and exit.');
39
39
  console.log(' --verbose Be verbose.');
40
40
  console.log(' --collapse-nesting Flatten nested rules after preserving source-order cascade.');
41
+ console.log(' --unit-mode=MODE Unit handling in math: preserve (default), strict, or loose (Less 4.x guessing).');
42
+ console.log(' --strict-units[=on|off] Deprecated: on is --unit-mode=strict, off is the default (preserve).');
41
43
  console.log('');
42
44
  console.log('This release intentionally supports a smaller CLI surface.');
43
45
  console.log('Source maps, browser compilation, legacy plugin flags, lint-only mode, and');
package/lib/options.js CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
  import lessPlugin from '@jesscss/plugin-less';
7
7
  import { lessCompatPlugin } from '@jesscss/plugin-less-compat';
8
+ import { logger } from './logger.js';
8
9
 
9
10
  const unsupportedAlphaOptions = new Map([
10
11
  ['sourceMap', 'source maps are not supported'],
@@ -15,7 +16,6 @@ const unsupportedAlphaOptions = new Map([
15
16
  ['sourceMapFileInline', 'source maps are not supported'],
16
17
  ['globalVars', 'global variable injection is not supported'],
17
18
  ['modifyVars', 'modify-var injection is not supported'],
18
- ['strictUnits', 'strict unit mode is not supported'],
19
19
  ['rootpath', 'URL rootpath rewriting is not supported'],
20
20
  ['rewriteUrls', 'URL rewriting is not supported'],
21
21
  ['urlArgs', 'URL argument rewriting is not supported'],
@@ -25,7 +25,9 @@ const unsupportedAlphaOptions = new Map([
25
25
 
26
26
  function validateAlphaOptions(options) {
27
27
  for (const [name, reason] of unsupportedAlphaOptions) {
28
- if (Object.prototype.hasOwnProperty.call(options, name)) {
28
+ // A falsy value is the 4.x default ("off") and requests nothing, so it is
29
+ // a no-op here; only an actual request for the feature is unsupported.
30
+ if (options[name]) {
29
31
  throw new Error(`${name} is not supported: ${reason}`);
30
32
  }
31
33
  }
@@ -81,6 +83,21 @@ export function createLessOptions(options) {
81
83
  math === 2 || math === 'parens' || math === 'strict' ? 'parens' :
82
84
  'parens-division';
83
85
 
86
+ // `unitMode` is the option ('loose' | 'preserve' | 'strict'); `strictUnits`
87
+ // is its deprecated boolean alias: true → 'strict'; false means "not strict",
88
+ // i.e. the default ('preserve') — never the Less 4.x 'loose' fold, which only
89
+ // an explicit `unitMode: 'loose'` selects. Any use warns so the mapping is
90
+ // never discovered by staring at output. Left unset so the compiler default applies.
91
+ const unitMode = opts.unitMode !== undefined ? opts.unitMode
92
+ : opts.strictUnits === true ? 'strict'
93
+ : undefined;
94
+ if (opts.strictUnits !== undefined && opts.unitMode === undefined) {
95
+ logger.warn(
96
+ `strictUnits is deprecated; use unitMode. strictUnits: ${String(opts.strictUnits)} now means `
97
+ + `unitMode: '${unitMode ?? 'preserve'}'${opts.strictUnits ? '' : " (Less 4.x unit folding is unitMode: 'loose')"}`
98
+ );
99
+ }
100
+
84
101
  const plugins = [lessPlugin()];
85
102
  if (!skipLessCompat) {
86
103
  plugins.push(lessCompatPlugin({ plugins: lessPlugins }));
@@ -90,6 +107,7 @@ export function createLessOptions(options) {
90
107
  compile: {
91
108
  searchPaths: opts.paths || [],
92
109
  mathMode,
110
+ ...(unitMode !== undefined && { unitMode }),
93
111
  plugins,
94
112
  },
95
113
  // Less v5 preserves authored nesting unless its explicit compatibility
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "less",
3
- "version": "5.0.0-alpha.2",
3
+ "version": "5.0.0-alpha.4",
4
4
  "description": "Leaner CSS",
5
5
  "homepage": "http://lesscss.org",
6
6
  "author": {
@@ -63,9 +63,10 @@
63
63
  "lint:fix": "eslint '**/*.{ts,js}' --fix",
64
64
  "typecheck": "tsc --noEmit",
65
65
  "build": "node build/rollup.js --dist",
66
+ "build:browser": "node build/browser-dev.mjs --minify",
66
67
  "benchmark": "node benchmark/benchmark-runner.cjs",
67
68
  "benchmark:all": "node benchmark/run-and-compare.mjs",
68
- "prepublishOnly": "npm run typecheck && npm run build && npm run test:lessc"
69
+ "prepublishOnly": "npm run typecheck && npm run build && npm run build:browser && npm run test:lessc"
69
70
  },
70
71
  "devDependencies": {
71
72
  "@less/test-data": "workspace:*",
@@ -83,6 +84,7 @@
83
84
  "chalk": "^4.1.2",
84
85
  "cosmiconfig": "~9.0.0",
85
86
  "cross-env": "^7.0.3",
87
+ "esbuild": "^0.25.0",
86
88
  "eslint": "^7.29.0",
87
89
  "fs-extra": "^8.1.0",
88
90
  "glob": "~11.0.3",
@@ -139,15 +141,15 @@
139
141
  "rawcurrent": "https://raw.github.com/less/less.js/v",
140
142
  "sourcearchive": "https://github.com/less/less.js/archive/v",
141
143
  "dependencies": {
142
- "@jesscss/compiler": "2.0.0-alpha.15",
143
- "@jesscss/core": "2.0.0-alpha.15",
144
- "@jesscss/plugin-less": "2.0.0-alpha.15",
145
- "@jesscss/plugin-less-compat": "2.0.0-alpha.15",
146
- "@jesscss/plugin-node-modules": "2.0.0-alpha.15",
144
+ "@jesscss/compiler": "2.0.0-alpha.17",
145
+ "@jesscss/core": "2.0.0-alpha.17",
146
+ "@jesscss/plugin-less": "2.0.0-alpha.17",
147
+ "@jesscss/plugin-less-compat": "2.0.0-alpha.17",
148
+ "@jesscss/plugin-node-modules": "2.0.0-alpha.17",
147
149
  "parse-node-version": "^1.0.1"
148
150
  },
149
151
  "peerDependencies": {
150
- "@jesscss/plugin-js": "2.0.0-alpha.15"
152
+ "@jesscss/plugin-js": "2.0.0-alpha.17"
151
153
  },
152
154
  "peerDependenciesMeta": {
153
155
  "@jesscss/plugin-js": {