less 5.0.0-alpha.1 → 5.0.0-alpha.10

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.
package/lib/options.js CHANGED
@@ -5,27 +5,19 @@
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
- ['sourceMap', 'source maps are not supported'],
11
- ['sourceMapFilename', 'source maps are not supported'],
12
- ['sourceMapRootpath', 'source maps are not supported'],
13
- ['sourceMapBasepath', 'source maps are not supported'],
14
- ['sourceMapURL', 'source maps are not supported'],
15
- ['sourceMapFileInline', 'source maps are not supported'],
16
11
  ['globalVars', 'global variable injection is not supported'],
17
12
  ['modifyVars', 'modify-var injection is not supported'],
18
- ['strictUnits', 'strict unit mode is not supported'],
19
- ['rootpath', 'URL rootpath rewriting is not supported'],
20
- ['rewriteUrls', 'URL rewriting is not supported'],
21
- ['urlArgs', 'URL argument rewriting is not supported'],
22
13
  ['javascriptEnabled', 'JavaScript evaluation is not supported'],
23
- ['compress', 'compressed output is not supported'],
24
14
  ]);
25
15
 
26
16
  function validateAlphaOptions(options) {
27
17
  for (const [name, reason] of unsupportedAlphaOptions) {
28
- if (Object.prototype.hasOwnProperty.call(options, name)) {
18
+ // A falsy value is the 4.x default ("off") and requests nothing, so it is
19
+ // a no-op here; only an actual request for the feature is unsupported.
20
+ if (options[name]) {
29
21
  throw new Error(`${name} is not supported: ${reason}`);
30
22
  }
31
23
  }
@@ -62,6 +54,55 @@ function stableStringify(value, seen = new WeakSet()) {
62
54
  return `{${entries.join(',')}}`;
63
55
  }
64
56
 
57
+ /**
58
+ * `collapseNesting` accepts `false` (keep authored nesting — the v5 default),
59
+ * `'native'` (CSS Nesting desugaring: parent wrapped in `:is()`, child selector
60
+ * lists distributed, specificity-faithful), `'compact'` (like `'native'` but
61
+ * also folds same-combinator descendant runs into one `:is()`), or the
62
+ * deprecated boolean `true` (alias for `'native'`). Anything else is rejected.
63
+ * @param {unknown} value
64
+ * @returns {boolean|'native'|'compact'}
65
+ */
66
+ function resolveCollapseNesting(value) {
67
+ if (value === false || value === true || value === 'native' || value === 'compact') {
68
+ return value;
69
+ }
70
+ throw new Error(
71
+ `collapseNesting must be false, 'native', 'compact', or true `
72
+ + `(deprecated alias for 'native'); got ${JSON.stringify(value)}`
73
+ );
74
+ }
75
+
76
+ /**
77
+ * Build the compiler's `output.sourceMap` value from Less options. Source maps
78
+ * are enabled when `sourceMap` is truthy; the object form (or the flat legacy
79
+ * `sourceMap*` options) configures the details. Returns `undefined` when no
80
+ * source map is requested.
81
+ * @param {import('./options.js').LessRenderOptions} opts
82
+ * @returns {undefined | true | Record<string, unknown>}
83
+ */
84
+ function resolveSourceMap(opts) {
85
+ if (!opts.sourceMap) {
86
+ return undefined;
87
+ }
88
+ const config = (typeof opts.sourceMap === 'object' && opts.sourceMap !== null)
89
+ ? { ...opts.sourceMap }
90
+ : {};
91
+ // Fold the flat legacy `sourceMap*` options into the object form the compiler
92
+ // reads; an explicit object sub-option wins over its flat alias.
93
+ const flat = [
94
+ 'sourceMapURL', 'sourceMapFilename', 'sourceMapFullFilename',
95
+ 'sourceMapRootpath', 'sourceMapBasepath', 'sourceMapFileInline',
96
+ 'sourceMapOutputFilename', 'outputSourceFiles', 'disableSourcemapAnnotation'
97
+ ];
98
+ for (const key of flat) {
99
+ if (opts[key] !== undefined && config[key] === undefined) {
100
+ config[key] = opts[key];
101
+ }
102
+ }
103
+ return Object.keys(config).length > 0 ? config : true;
104
+ }
105
+
65
106
  /**
66
107
  * Map Less render options to Jess compiler config.
67
108
  * @param {import('./options.js').LessRenderOptions} [options] Less-style options
@@ -81,26 +122,60 @@ export function createLessOptions(options) {
81
122
  math === 2 || math === 'parens' || math === 'strict' ? 'parens' :
82
123
  'parens-division';
83
124
 
84
- const plugins = [lessPlugin()];
125
+ // `unitMode` is the option ('loose' | 'preserve' | 'strict'); `strictUnits`
126
+ // is its deprecated boolean alias: true → 'strict'; false means "not strict",
127
+ // i.e. the default ('preserve') — never the Less 4.x 'loose' fold, which only
128
+ // an explicit `unitMode: 'loose'` selects. Any use warns so the mapping is
129
+ // never discovered by staring at output. Left unset so the compiler default applies.
130
+ const unitMode = opts.unitMode !== undefined ? opts.unitMode
131
+ : opts.strictUnits === true ? 'strict'
132
+ : undefined;
133
+ if (opts.strictUnits !== undefined && opts.unitMode === undefined) {
134
+ logger.warn(
135
+ `strictUnits is deprecated; use unitMode. strictUnits: ${String(opts.strictUnits)} now means `
136
+ + `unitMode: '${unitMode ?? 'preserve'}'${opts.strictUnits ? '' : " (Less 4.x unit folding is unitMode: 'loose')"}`
137
+ );
138
+ }
139
+
140
+ // URL rewriting lives on the Less plugin (it rewrites `url(...)` during
141
+ // serialization), not in `output`. Only forward keys the caller set so the
142
+ // plugin's own v5 defaults apply otherwise.
143
+ const lessPluginOptions = {};
144
+ if (opts.rootpath !== undefined) lessPluginOptions.rootpath = opts.rootpath;
145
+ if (opts.rewriteUrls !== undefined) lessPluginOptions.rewriteUrls = opts.rewriteUrls;
146
+ if (opts.urlArgs !== undefined) lessPluginOptions.urlArgs = opts.urlArgs;
147
+
148
+ const plugins = [lessPlugin(lessPluginOptions)];
85
149
  if (!skipLessCompat) {
86
150
  plugins.push(lessCompatPlugin({ plugins: lessPlugins }));
87
151
  }
88
152
 
153
+ // The projection/serialization options the compiler reads off `output`:
154
+ // `collapseNesting` (nesting flatten mode), `compress` (minified output), and
155
+ // `sourceMap`. Emitted as a single file-less array entry so an explicit render
156
+ // option overrides a file-local styles.config for every key (the config merge
157
+ // appends it as the override default); left `{}` when the caller set none.
158
+ const outputEntry = {};
159
+ if (opts.collapseNesting !== undefined) {
160
+ outputEntry.collapseNesting = resolveCollapseNesting(opts.collapseNesting);
161
+ }
162
+ if (opts.compress !== undefined) {
163
+ outputEntry.compress = opts.compress;
164
+ }
165
+ const sourceMap = resolveSourceMap(opts);
166
+ if (sourceMap !== undefined) {
167
+ outputEntry.sourceMap = sourceMap;
168
+ }
169
+ const output = Object.keys(outputEntry).length > 0 ? [outputEntry] : {};
170
+
89
171
  const configOptions = {
90
172
  compile: {
91
173
  searchPaths: opts.paths || [],
92
174
  mathMode,
175
+ ...(unitMode !== undefined && { unitMode }),
93
176
  plugins,
94
177
  },
95
- // Less v5 preserves authored nesting unless its explicit compatibility
96
- // switch requests flattened CSS. Keep that public Less option at the
97
- // wrapper boundary; Jess owns the one renderer and its output mode.
98
- // A file's styles.config may use an output array. A file-less output entry
99
- // is the compiler's documented per-render override for that shape, so an
100
- // explicit public Less option remains authoritative over fixture config.
101
- output: Object.prototype.hasOwnProperty.call(opts, 'collapseNesting')
102
- ? [{ collapseNesting: opts.collapseNesting === true }]
103
- : {},
178
+ output,
104
179
  language: {},
105
180
  };
106
181
 
@@ -129,6 +204,13 @@ export function mapRenderResult(result, options) {
129
204
  css: result.css ?? '',
130
205
  };
131
206
 
207
+ // Less 4.x returns the source map as `result.map` (a JSON string) when one was
208
+ // requested; forward it so `less.render(src, { sourceMap: true })` behaves the
209
+ // same. The compiler writes the `sourceMappingURL` annotation into `css` itself.
210
+ if (result.map !== undefined) {
211
+ out.map = result.map;
212
+ }
213
+
132
214
  if (result.imports && Array.isArray(result.imports)) {
133
215
  out.imports = result.imports;
134
216
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "less",
3
- "version": "5.0.0-alpha.1",
3
+ "version": "5.0.0-alpha.10",
4
4
  "description": "Leaner CSS",
5
5
  "homepage": "http://lesscss.org",
6
6
  "author": {
@@ -45,14 +45,15 @@
45
45
  "!lib/**/*.map",
46
46
  "dist",
47
47
  "index.cjs",
48
- "README.md"
48
+ "README.md",
49
+ "V5-STATUS.md"
49
50
  ],
50
51
  "engines": {
51
52
  "node": "^20.19.0 || >=22.12.0"
52
53
  },
53
54
  "scripts": {
54
55
  "quicktest": "npm run test:legacy-node",
55
- "test:alpha": "npm run typecheck && npm run build && npm run test:lessc && node test/jess-alpha-fast-path.mjs && node test/alpha-support.mjs && node test/alpha-fixtures.mjs",
56
+ "test:alpha": "npm run typecheck && npm run build && npm run test:lessc && node test/jess-alpha-fast-path.mjs && node test/alpha-support.mjs && node test/alpha-sourcemaps.mjs && node test/alpha-fixtures.mjs",
56
57
  "test:lessc": "node test/lessc-alpha.mjs",
57
58
  "test": "npm run test:alpha",
58
59
  "test:module": "node test/test-es6.js && node test/test-cjs.cjs",
@@ -63,9 +64,10 @@
63
64
  "lint:fix": "eslint '**/*.{ts,js}' --fix",
64
65
  "typecheck": "tsc --noEmit",
65
66
  "build": "node build/rollup.js --dist",
67
+ "build:browser": "node build/browser-dev.mjs --minify",
66
68
  "benchmark": "node benchmark/benchmark-runner.cjs",
67
69
  "benchmark:all": "node benchmark/run-and-compare.mjs",
68
- "prepublishOnly": "npm run typecheck && npm run build && npm run test:lessc"
70
+ "prepublishOnly": "npm run typecheck && npm run build && npm run build:browser && npm run test:lessc"
69
71
  },
70
72
  "devDependencies": {
71
73
  "@less/test-data": "workspace:*",
@@ -83,6 +85,7 @@
83
85
  "chalk": "^4.1.2",
84
86
  "cosmiconfig": "~9.0.0",
85
87
  "cross-env": "^7.0.3",
88
+ "esbuild": "^0.25.0",
86
89
  "eslint": "^7.29.0",
87
90
  "fs-extra": "^8.1.0",
88
91
  "glob": "~11.0.3",
@@ -139,16 +142,15 @@
139
142
  "rawcurrent": "https://raw.github.com/less/less.js/v",
140
143
  "sourcearchive": "https://github.com/less/less.js/archive/v",
141
144
  "dependencies": {
142
- "@jesscss/compiler": "2.0.0-alpha.11",
143
- "@jesscss/core": "2.0.0-alpha.11",
144
- "@jesscss/plugin-less": "2.0.0-alpha.11",
145
- "@jesscss/plugin-less-compat": "2.0.0-alpha.11",
146
- "@jesscss/plugin-node-modules": "2.0.0-alpha.11",
147
- "parse-node-version": "^1.0.1",
148
- "parseman": "^0.41.0"
145
+ "@jesscss/compiler": "2.0.0-alpha.23",
146
+ "@jesscss/core": "2.0.0-alpha.23",
147
+ "@jesscss/plugin-less": "2.0.0-alpha.23",
148
+ "@jesscss/plugin-less-compat": "2.0.0-alpha.23",
149
+ "@jesscss/plugin-node-modules": "2.0.0-alpha.23",
150
+ "parse-node-version": "^1.0.1"
149
151
  },
150
152
  "peerDependencies": {
151
- "@jesscss/plugin-js": "2.0.0-alpha.11"
153
+ "@jesscss/plugin-js": "2.0.0-alpha.23"
152
154
  },
153
155
  "peerDependenciesMeta": {
154
156
  "@jesscss/plugin-js": {