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/README.md +10 -6
- package/V5-STATUS.md +71 -0
- package/bin/lessc +130 -14
- package/dist/less-browser-dev.js +2550 -0
- package/dist/less-node.cjs +193 -100
- package/lib/browser-dev.js +86 -0
- package/lib/lessc-helper.js +15 -3
- package/lib/options.d.ts +39 -3
- package/lib/options.js +104 -22
- package/package.json +14 -12
package/README.md
CHANGED
|
@@ -13,10 +13,12 @@
|
|
|
13
13
|
> [!IMPORTANT]
|
|
14
14
|
> This package README is for Less 5 alpha.1. Less 5 is a Jess-powered compiler
|
|
15
15
|
> preview for early testing and is not yet a drop-in replacement for Less 4.x.
|
|
16
|
-
>
|
|
17
|
-
> variables, arithmetic, mixins,
|
|
18
|
-
>
|
|
19
|
-
>
|
|
16
|
+
> It covers Node.js `less.render()`, `less.renderFile()`, and `lessc`, plus
|
|
17
|
+
> variables, arithmetic, mixins, imports, and nested-rule output. Source maps,
|
|
18
|
+
> URL rewriting, and `compress` now work through the `less.render()` API; the
|
|
19
|
+
> legacy `@plugin` hook ABI is intentionally not carried over (function plugins
|
|
20
|
+
> are supported). See the **[Less 5 feature status](V5-STATUS.md)** for the full
|
|
21
|
+
> list of what is implemented, in progress, and intentionally not.
|
|
20
22
|
|
|
21
23
|
Less extends CSS with variables, mixins, functions, nesting, and more — then compiles to standard CSS. Write cleaner stylesheets with less code.
|
|
22
24
|
|
|
@@ -64,8 +66,10 @@ npx lessc styles.less styles.css
|
|
|
64
66
|
|
|
65
67
|
### Browser
|
|
66
68
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
A browser build ships in the package (`dist/less-browser-dev.js`) and defines
|
|
70
|
+
`window.less` with the same render API; it powers the online playground. Full
|
|
71
|
+
browser-API parity with Less 4 is still being validated — see the
|
|
72
|
+
[feature status](V5-STATUS.md).
|
|
69
73
|
|
|
70
74
|
## Why Less?
|
|
71
75
|
|
package/V5-STATUS.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Less 5 (alpha) — feature status
|
|
2
|
+
|
|
3
|
+
Less 5 is a from-scratch compiler (the [Jess](https://github.com/jesscss/jess)
|
|
4
|
+
engine) behind the same `less.render(input, options)` / `lessc` interface Less 4
|
|
5
|
+
users know. The tables below compare Less 4 with the current Less 5 alpha —
|
|
6
|
+
what's implemented, what's still in progress, and what will **intentionally
|
|
7
|
+
not** be carried over (with the reason).
|
|
8
|
+
|
|
9
|
+
> **Status:** alpha, published under the npm `alpha` dist-tag
|
|
10
|
+
> (`npm install less@alpha`). APIs and output may still change. This page is kept
|
|
11
|
+
> honest by the `packages/less` alpha test suite — if a row here drifts from
|
|
12
|
+
> actual behavior, the suite fails.
|
|
13
|
+
|
|
14
|
+
The headline change: **Less 5 preserves your authored nesting by default** (it
|
|
15
|
+
emits nested CSS) instead of always flattening. Opt into flattened output with
|
|
16
|
+
`collapseNesting`.
|
|
17
|
+
|
|
18
|
+
**Legend:** ✅ supported · ⏳ in progress · ❌ not supported (by design) · ➖ not applicable
|
|
19
|
+
|
|
20
|
+
## Language & output
|
|
21
|
+
|
|
22
|
+
| Feature | Less 4 | Less 5 | Notes |
|
|
23
|
+
| --- | :---: | :---: | --- |
|
|
24
|
+
| Variables, mixins (guards, named args), operations, functions | ✅ | ✅ | The Less builtin function library is ported. |
|
|
25
|
+
| `:extend` | ✅ | ✅ | |
|
|
26
|
+
| Nested-rule output | ➖ | ✅ | Less 4 always flattened; Less 5 **preserves** authored nesting by default. |
|
|
27
|
+
| `collapseNesting` (flatten instead) | ➖ | ✅ | `false` (default) / `'native'` (specificity-faithful) / `'compact'`. |
|
|
28
|
+
| `@media` query merging | ✅ | ❌ | Less 5 emits nested `@media` instead of rewriting to `@media (a) and (b)`. Browsers have nested `@media` far longer than native *selector* nesting, so nested output is safe — and merging can blow up combinatorially (each nested query multiplies out). |
|
|
29
|
+
| Inline JavaScript (backticks) | ✅ | ❌ | Removed. Script modules are the planned replacement for computed values; they are tracked separately from stylesheet composition below. |
|
|
30
|
+
| IE `progid:` / `filter` hacks | ✅ | ❌ | Removed. |
|
|
31
|
+
|
|
32
|
+
## Options (`less.render` API)
|
|
33
|
+
|
|
34
|
+
| Feature | Less 4 | Less 5 | Notes |
|
|
35
|
+
| --- | :---: | :---: | --- |
|
|
36
|
+
| `math` modes | ✅ | ✅ | `always` / `parens-division` (default) / `parens`. |
|
|
37
|
+
| `unitMode` (formerly `strictUnits`) | ✅ | ✅ | `loose` / `preserve` (default) / `strict`. |
|
|
38
|
+
| `compress` | ✅ | ✅ | Minified, but not byte-identical to Less 4 `-x` (nesting preserved by default). |
|
|
39
|
+
| Source maps (`sourceMap`) | ✅ | ✅ | Returns `result.map`; annotation, inline data URI, `outputSourceFiles`, and the `rootpath`/`basepath`/`url` path variants all supported. |
|
|
40
|
+
| URL rewriting (`rewriteUrls` / `rootpath` / `urlArgs`) | ✅ | ✅ | Rewrites `url(...)` references. |
|
|
41
|
+
| `globalVars` / `modifyVars` injection | ✅ | ❌ | Not supported — these throw rather than silently no-op. |
|
|
42
|
+
| `javascriptEnabled` | ✅ | ❌ | JavaScript evaluation is not supported. |
|
|
43
|
+
|
|
44
|
+
## Plugins (`@plugin`)
|
|
45
|
+
|
|
46
|
+
| Feature | Less 4 | Less 5 | Notes |
|
|
47
|
+
| --- | :---: | :---: | --- |
|
|
48
|
+
| Function plugins (`functions.add`) | ✅ | ✅ | Via the opt-in `@jesscss/plugin-less-compat` layer — the common `@plugin` shape. |
|
|
49
|
+
| npm-package imports | ✅ | ✅ | Native via `@jesscss/plugin-node-modules` (the `less-plugin-npm-import` case). |
|
|
50
|
+
| Visitor / tree-visitor ABI, full `less.tree` | ✅ | ❌ | Intentional — the Less 4 tree is not the Less 5 AST; a translation layer isn't worth it. |
|
|
51
|
+
| Pre-/post-processor hooks | ✅ | ❌ | Run PostCSS after Less; minification is native via `compress`. |
|
|
52
|
+
| File-manager hooks | ✅ | ❌ | The common case (npm import) is covered natively. |
|
|
53
|
+
| `@plugin (options)` + `registerPlugin` lifecycle | ✅ | ❌ | Deprecated Less 4 lifecycle; not built. |
|
|
54
|
+
|
|
55
|
+
## Imports, CLI & tooling
|
|
56
|
+
|
|
57
|
+
| Feature | Less 4 | Less 5 | Notes |
|
|
58
|
+
| --- | :---: | :---: | --- |
|
|
59
|
+
| Sibling / relative `@import` | ✅ | ✅ | |
|
|
60
|
+
| Remote (`http(s)`) imports | ✅ | ⏳ | Gated behind an explicit network policy; not on by default. |
|
|
61
|
+
| `@compose` stylesheet modules | ➖ | ✅ | Isolated, non-transitive modules with inferred or explicit namespaces, `as *`, and per-edge `with` or shared `set` configuration. See the [canonical Modules and Imports source](https://github.com/jesscss/jess/blob/dev/packages/docs/docs-content/docs/shared/02-Language/14-modules-and-imports.mdx). |
|
|
62
|
+
| `@use` / `@from` script and data modules | ➖ | ⏳ | Reserved for JavaScript, TypeScript, JSON, and built-in modules; Less 5 does not recognize or execute them as modules yet. See the [canonical Modules and Imports source](https://github.com/jesscss/jess/blob/dev/packages/docs/docs-content/docs/shared/02-Language/14-modules-and-imports.mdx). |
|
|
63
|
+
| Browser build (`window.less`) | ✅ | ✅ | `dist/less-browser-dev.js` ships and powers the playground; full 4.x browser-API parity is ⏳. |
|
|
64
|
+
| `lessc` CLI (compile) | ✅ | ✅ | Compiles files. |
|
|
65
|
+
| `lessc` CLI **flags** for the newer options | ✅ | ✅ | `--compress`/`-x`, `--source-map[=file]` (+ `--source-map-inline` / `-include-source` / `-rootpath` / `-basepath` / `-url`), `--rewrite-urls` / `--rootpath` / `--url-args`, `--math` are all wired. |
|
|
66
|
+
| Diagnostics (`file:line:column` + excerpt) | ➖ | ✅ | Precise diagnostics, not raw parser offsets. |
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
*Found a row that contradicts actual behavior? Please open an issue — a drift
|
|
71
|
+
between this page and the compiler is a bug.*
|
package/bin/lessc
CHANGED
|
@@ -17,28 +17,19 @@ let output = null;
|
|
|
17
17
|
let silent = false;
|
|
18
18
|
let quiet = false;
|
|
19
19
|
let verbose = false;
|
|
20
|
+
let sourceMapEnabled = false;
|
|
21
|
+
let sourceMapExplicitFile = null;
|
|
22
|
+
const sourceMapOptions = {};
|
|
20
23
|
|
|
21
24
|
const unsupportedOptions = new Map([
|
|
22
|
-
['--source-map', 'source maps are not supported'],
|
|
23
|
-
['--source-map-map-inline', 'source maps are not supported'],
|
|
24
|
-
['--source-map-include-source', 'source maps are not supported'],
|
|
25
|
-
['--source-map-rootpath', 'source maps are not supported'],
|
|
26
|
-
['--source-map-basepath', 'source maps are not supported'],
|
|
27
|
-
['--source-map-url', 'source maps are not supported'],
|
|
28
25
|
['--plugin', 'legacy lessc plugin flags are not supported'],
|
|
29
26
|
['--depends', 'dependency-only output is not supported'],
|
|
30
27
|
['--lint', 'lint-only mode is not supported'],
|
|
31
|
-
['--compress', 'compressed output is not supported'],
|
|
32
|
-
['-x', 'compressed output is not supported'],
|
|
33
28
|
['--clean-css', 'clean-css compression is not supported'],
|
|
34
|
-
['--rewrite-urls', 'URL rewriting is not supported'],
|
|
35
|
-
['--rootpath', 'URL rootpath rewriting is not supported'],
|
|
36
|
-
['--url-args', 'URL argument rewriting is not supported'],
|
|
37
29
|
['--global-var', 'global variable injection is not supported'],
|
|
38
30
|
['--modify-var', 'modify-var injection is not supported'],
|
|
39
31
|
['--js', 'JavaScript evaluation is not supported'],
|
|
40
32
|
['--no-js', 'JavaScript evaluation flags are not supported'],
|
|
41
|
-
['--strict-units', 'strict unit mode is not supported'],
|
|
42
33
|
]);
|
|
43
34
|
|
|
44
35
|
function stripTerminalFormatting(value) {
|
|
@@ -137,8 +128,87 @@ function parseArgs() {
|
|
|
137
128
|
verbose = true;
|
|
138
129
|
continue;
|
|
139
130
|
}
|
|
140
|
-
|
|
141
|
-
|
|
131
|
+
const collapseNestingMatch = arg.match(/^--collapse-nesting(?:=(.+))?$/);
|
|
132
|
+
if (collapseNestingMatch) {
|
|
133
|
+
options.collapseNesting = collapseNestingMatch[1] ?? true;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (arg === '--compress' || arg === '-x') {
|
|
137
|
+
options.compress = true;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const mathMatch = arg.match(/^--math=(.+)$/);
|
|
141
|
+
if (mathMatch) {
|
|
142
|
+
options.math = mathMatch[1];
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
// URL rewriting (forwarded to the Less plugin). `--rewrite-urls` with no
|
|
146
|
+
// value is Less 4.x's `all`.
|
|
147
|
+
const rewriteUrlsMatch = arg.match(/^--rewrite-urls(?:=(.+))?$/);
|
|
148
|
+
if (rewriteUrlsMatch) {
|
|
149
|
+
options.rewriteUrls = rewriteUrlsMatch[1] ?? 'all';
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const rootpathMatch = arg.match(/^--rootpath=(.+)$/);
|
|
153
|
+
if (rootpathMatch) {
|
|
154
|
+
options.rootpath = rootpathMatch[1];
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const urlArgsMatch = arg.match(/^--url-args=(.+)$/);
|
|
158
|
+
if (urlArgsMatch) {
|
|
159
|
+
options.urlArgs = urlArgsMatch[1];
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
// Source maps. The `--source-map-*` sub-flags are matched before the bare
|
|
163
|
+
// `--source-map[=file]` (their `$`-anchored patterns don't overlap, but the
|
|
164
|
+
// ordering documents intent). Each sub-flag also enables source maps.
|
|
165
|
+
if (arg === '--source-map-inline' || arg === '--source-map-map-inline') {
|
|
166
|
+
sourceMapEnabled = true;
|
|
167
|
+
sourceMapOptions.sourceMapFileInline = true;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (arg === '--source-map-include-source') {
|
|
171
|
+
sourceMapEnabled = true;
|
|
172
|
+
sourceMapOptions.outputSourceFiles = true;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const smRootpathMatch = arg.match(/^--source-map-rootpath=(.+)$/);
|
|
176
|
+
if (smRootpathMatch) {
|
|
177
|
+
sourceMapEnabled = true;
|
|
178
|
+
sourceMapOptions.sourceMapRootpath = smRootpathMatch[1];
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const smBasepathMatch = arg.match(/^--source-map-basepath=(.+)$/);
|
|
182
|
+
if (smBasepathMatch) {
|
|
183
|
+
sourceMapEnabled = true;
|
|
184
|
+
sourceMapOptions.sourceMapBasepath = smBasepathMatch[1];
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const smUrlMatch = arg.match(/^--source-map-url=(.+)$/);
|
|
188
|
+
if (smUrlMatch) {
|
|
189
|
+
sourceMapEnabled = true;
|
|
190
|
+
sourceMapOptions.sourceMapURL = smUrlMatch[1];
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const sourceMapMatch = arg.match(/^--source-map(?:=(.+))?$/);
|
|
194
|
+
if (sourceMapMatch) {
|
|
195
|
+
sourceMapEnabled = true;
|
|
196
|
+
if (sourceMapMatch[1]) {
|
|
197
|
+
sourceMapExplicitFile = sourceMapMatch[1];
|
|
198
|
+
}
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
// Deprecated 4.x spellings (--strict-units, --strict-units=on|off, -su=on|off)
|
|
202
|
+
// set the deprecated `strictUnits` boolean; `createLessOptions` maps it to
|
|
203
|
+
// `unitMode` and warns during compile, after the logger listener is attached.
|
|
204
|
+
const strictUnitsMatch = arg.match(/^(?:--strict-units|-su)(?:=(on|off))?$/);
|
|
205
|
+
if (strictUnitsMatch) {
|
|
206
|
+
options.strictUnits = strictUnitsMatch[1] !== 'off';
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const unitModeMatch = arg.match(/^--unit-mode=(.+)$/);
|
|
210
|
+
if (unitModeMatch) {
|
|
211
|
+
options.unitMode = unitModeMatch[1];
|
|
142
212
|
continue;
|
|
143
213
|
}
|
|
144
214
|
const optionName = arg.includes('=') ? arg.slice(0, arg.indexOf('=')) : arg;
|
|
@@ -178,6 +248,44 @@ function parseArgs() {
|
|
|
178
248
|
}
|
|
179
249
|
}
|
|
180
250
|
|
|
251
|
+
// Assemble the `sourceMap` render option from the CLI source-map flags and
|
|
252
|
+
// return the sidecar `.map` path to write (null when inlined or unset). The
|
|
253
|
+
// engine writes the `sourceMappingURL` annotation into the CSS and returns
|
|
254
|
+
// `result.map`; the CLI owns writing that map to disk, mirroring Less 4.x lessc.
|
|
255
|
+
function resolveSourceMap() {
|
|
256
|
+
if (!sourceMapEnabled) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
let mapFilePath = null;
|
|
260
|
+
if (sourceMapOptions.sourceMapFileInline !== true) {
|
|
261
|
+
if (sourceMapExplicitFile) {
|
|
262
|
+
mapFilePath = path.resolve(process.cwd(), sourceMapExplicitFile);
|
|
263
|
+
} else if (output) {
|
|
264
|
+
mapFilePath = `${output}.map`;
|
|
265
|
+
} else {
|
|
266
|
+
// Writing CSS to stdout leaves nowhere for a sidecar; inline the map.
|
|
267
|
+
sourceMapOptions.sourceMapFileInline = true;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (mapFilePath) {
|
|
271
|
+
// Annotation URL (unless --source-map-url set it) points at the map relative
|
|
272
|
+
// to the CSS output directory — so a map written to another directory still
|
|
273
|
+
// resolves (dist/app.css -> ../maps/app.map), and the same-dir common case
|
|
274
|
+
// stays a bare basename. With no output file (stdout) there is no directory
|
|
275
|
+
// to resolve against, so fall back to the basename.
|
|
276
|
+
if (sourceMapOptions.sourceMapURL === undefined) {
|
|
277
|
+
sourceMapOptions.sourceMapFilename = output
|
|
278
|
+
? path.relative(path.dirname(output), mapFilePath).split(path.sep).join('/')
|
|
279
|
+
: path.basename(mapFilePath);
|
|
280
|
+
}
|
|
281
|
+
if (output) {
|
|
282
|
+
sourceMapOptions.sourceMapOutputFilename = path.basename(output);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
options.sourceMap = sourceMapOptions;
|
|
286
|
+
return mapFilePath;
|
|
287
|
+
}
|
|
288
|
+
|
|
181
289
|
async function run() {
|
|
182
290
|
parseArgs();
|
|
183
291
|
|
|
@@ -187,6 +295,8 @@ async function run() {
|
|
|
187
295
|
printUsage();
|
|
188
296
|
}
|
|
189
297
|
|
|
298
|
+
const mapFilePath = resolveSourceMap();
|
|
299
|
+
|
|
190
300
|
less.logger.addListener({
|
|
191
301
|
info(msg) {
|
|
192
302
|
if (verbose) console.log(msg);
|
|
@@ -225,6 +335,12 @@ async function run() {
|
|
|
225
335
|
if (!silent) outputJessDiagnostics([], result.warnings || []);
|
|
226
336
|
process.stdout.write(result.css);
|
|
227
337
|
}
|
|
338
|
+
|
|
339
|
+
if (mapFilePath && result.map) {
|
|
340
|
+
fs.mkdirSync(path.dirname(mapFilePath), { recursive: true });
|
|
341
|
+
fs.writeFileSync(mapFilePath, result.map, 'utf8');
|
|
342
|
+
if (!silent) console.log(`lessc: wrote ${mapFilePath}`);
|
|
343
|
+
}
|
|
228
344
|
} catch (err) {
|
|
229
345
|
if (!silent) {
|
|
230
346
|
if (!outputJessError(err)) {
|