Page MenuHomePhabricator

Update less.php port to support Less.js 3.13 behaviours
Open, HighPublic

Description

Rationale

There are new Less.js v3 capabilities that frontend developers can't yet use in MediaWiki, as they are released between Less.js v2.6 and v3.13. Our Less.php package currently ports Less.js v2.5.

Examples

The below is summarised from https://lesscss.org/functions/, https://lesscss.org/features/, and https://lesscss.org/ (look for text that says "added since" or "released"). As well as https://github.com/less/less.js/blob/v3.13.1/CHANGELOG.md.

Background

Currently, as of Aug 2021, less.php is supporting 2.5.3, which was released in 2016. In the meantime, there has been a Less.js v3 release and Less.js v4 release, which add various new capabilities (upstream changelog).

The Less spec is quite stable, and most upstream Less.js changes don't require any changes to our less.php to stay comliant (e.g. most change seem to be fixing JS-specific bugs, changing JS-specific features such as eval() or Node.js-based runtime plugins, or deal with JS-specific packaging for npm).

The above list are examples list changes that we do need to port. (This is not an exhaustive list. Full compliance and any fixes for upstream bugs that we copied, will come from passing the upstream test suite. Fixes for upstream bugs can be found in spec commits under https://github.com/less/less.js/commits/v3.13.0/test/less)

  • Escape expressions inside of calc(), to prevent the Less compiler from trying to compute them at compile time. Currently this just affects one call to max(), but it could affect expressions like calc( @foo + @bar ) too if we add those in the future.
  • Don't call escape() on color values. The Node.js implementation of Less allows this (even back in version 2.5.3), but the PHP implementation doesn't, and it's not officially supported according to the Less documentation. Instead, use %A to convert these color values to strings and escape them at the same time.

Acceptance criteria

  • less.php pass the upstream's test suite for Less.js v3.13, enforced by PHPUnit in CI, with a handful of known behaviour differences marked and documented (similar to current 2.5.3 spec differences).

Related Objects

StatusSubtypeAssignedTask
OpenNone
ResolvedCatrope
ResolvedVolker_E
ResolvedCatrope
ResolvedHokwelum
ResolvedHokwelum
ResolvedHokwelum
ResolvedKrinkle
ResolvedHokwelum
ResolvedHokwelum
Resolvedpmiazga
Resolvedpmiazga
ResolvedHokwelum
ResolvedKrinkle
ResolvedHokwelum
ResolvedKrinkle
ResolvedKrinkle
ResolvedHokwelum
ResolvedHokwelum
Resolvedpmiazga
ResolvedHokwelum
ResolvedHokwelum
ResolvedHokwelum
ResolvedHokwelum
ResolvedHokwelum
ResolvedHokwelum
Resolvedpmiazga
Resolvedpmiazga
ResolvedHokwelum
Resolvedpmiazga
ResolvedHokwelum
ResolvedHokwelum
ResolvedHokwelum
ResolvedHokwelum
ResolvedKrinkle
ResolvedHokwelum
ResolvedAnneT

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
Krinkle renamed this task from Update less.php to latest Less version to Update less.php to support Less.js 3.13 behaviours.Mar 23 2023, 7:04 PM
Krinkle updated the task description. (Show Details)

Task description:

  • Rescoped to not be never-ending but rather a specific major upgrade.
  • Added a acceptance critera instead of "we did the task", which is undefined. Proposing to use the upstream test suite, like we do now.
  • Escape expressions inside of calc(), to prevent the Less compiler from trying to compute them at compile time. Currently this just affects one call to max(), but it could affect expressions like calc( @foo + @bar ) too if we add those in the future.
@Krinkle wrote in task description:

The ability to automatically exclude parts of CSS calc() from Less compilation, and to let the browser do that client-side, was indeed released in Less.js v3.0.0 and is in scope of this task. However, we backported that feature in T331688 and this has been released in less.php 4.0.0 and deployed to MediaWiki already.

However, this did not fully enable the Codex example. The upstream Less.js v3 (v3.13.1) and our less.php 4.0.0 took care of math operations and not functions like max(). Those are also unsupported upstream:

fresh-node18
nobody:/Temp$ cat tmp.less 
.cdx {
    @a: 12px;
    @b: 2em;
    font-size: calc( max( @a, @b ) );
}

nobody:/Temp$ npm install less@3.13
added 14 packages, and audited 15 packages in 4s

nobody/Temp$ npx less tmp.less
ArgumentError: error evaluating function `max`: incompatible types in /Temp/tmp.less on line 4, column 22:
3     @b: 2em;
4     font-size: calc( max( @a, @b ) );
5 }

The above changed in Less.js v4, which is outside this task's scope. A minimal patch to support it as-is is welcome, but otherwise we'l get to that later.

  • Don't call escape() on color values. The Node.js implementation of Less allows this (even back in version 2.5.3), but the PHP implementation doesn't, and it's not officially supported according to the Less documentation. Instead, use %A to convert these color values to strings and escape them at the same time.

I've traced this in the upstream codebase to, as it happens, exactly Less.js v2.5.2.

.cdx-fmt {
    @color: #000000;
    @escaped-color: %( '%A', @color );
    background-image: 'data:image/svg+xml;utf8,<svg fill="@{escaped-color}"></svg>';
}

.cdx-esc {
    @color: #000000;
    @escaped-color: escape( @color );
    background-image: 'data:image/svg+xml;utf8,<svg fill="@{escaped-color}"></svg>';
}

http://ecomfe.github.io/est/fiddle/#version=2.5.1&autoprefix=false&est=false&autorun=false

Produces:

.cdx-esc {
  background-image: 'data:image/svg+xml;utf8,<svg fill="undefined"></svg>';
}

Fixing this is in scope of this task, and indeed should have been fixed already.

Krinkle renamed this task from Update less.php to support Less.js 3.13 behaviours to Update less.php port to support Less.js 3.13 behaviours.Oct 24 2023, 2:20 AM
Krinkle updated the task description. (Show Details)
  • Don't call escape() on color values. The Node.js implementation of Less allows this (even back in version 2.5.3), but the PHP implementation doesn't, and it's not officially supported according to the Less documentation. Instead, use %A to convert these color values to strings and escape them at the same time.

I've traced this in the upstream codebase to, as it happens, exactly Less.js v2.5.2.

.cdx-fmt {
    @color: #000000;
    @escaped-color: %( '%A', @color );
    background-image: 'data:image/svg+xml;utf8,<svg fill="@{escaped-color}"></svg>';
}

.cdx-esc {
    @color: #000000;
    @escaped-color: escape( @color );
    background-image: 'data:image/svg+xml;utf8,<svg fill="@{escaped-color}"></svg>';
}

http://ecomfe.github.io/est/fiddle/#version=2.5.1&autoprefix=false&est=false&autorun=false

Produces:

.cdx-esc {
  background-image: 'data:image/svg+xml;utf8,<svg fill="undefined"></svg>';
}

Fixing this is in scope of this task, and indeed should have been fixed already.

It doesn't appear to be fixed yet. In current master:

catrope:~/git/less.php (master)$ ./bin/lessc -
.foo {
@color-black: #000000;
color: @color-black;
@foo1: %('%A', @color-black );
color: @foo1;
@foo2: escape( @color-black );
color: @foo2;
}

PHP Warning:  Undefined property: Less_Tree_Color::$value in /home/catrope/git/less.php/lib/Less/Functions.php on line 503
PHP Stack trace:
PHP   1. {main}() /home/catrope/git/less.php/bin/lessc:0
PHP   2. Less_Parser->getCss() /home/catrope/git/less.php/bin/lessc:189
PHP   3. Less_Tree_Ruleset->compile($env = class Less_Environment { public $currentFileInfo = ['entryPath' => '', 'entryUri' => '', 'rootpath' => '', 'currentDirectory' => '', 'currentUri' => 'anonymous-file-0.less', 'filename' => 'anonymous-file-0.less', 'uri_root' => '']; public $importMultiple = FALSE; public $frames = [0 => class Less_Tree_Ruleset { ... }, 1 => class Less_Tree_Ruleset { ... }]; public $mediaBlocks = []; public $mediaPath = []; public $functions = [] }) /home/catrope/git/less.php/lib/Less/Parser.php:182
PHP   4. Less_Tree_Ruleset->compile($env = class Less_Environment { public $currentFileInfo = ['entryPath' => '', 'entryUri' => '', 'rootpath' => '', 'currentDirectory' => '', 'currentUri' => 'anonymous-file-0.less', 'filename' => 'anonymous-file-0.less', 'uri_root' => '']; public $importMultiple = FALSE; public $frames = [0 => class Less_Tree_Ruleset { ... }, 1 => class Less_Tree_Ruleset { ... }]; public $mediaBlocks = []; public $mediaPath = []; public $functions = [] }) /home/catrope/git/less.php/lib/Less/Tree/Ruleset.php:96
PHP   5. Less_Tree_Rule->compile($env = class Less_Environment { public $currentFileInfo = ['entryPath' => '', 'entryUri' => '', 'rootpath' => '', 'currentDirectory' => '', 'currentUri' => 'anonymous-file-0.less', 'filename' => 'anonymous-file-0.less', 'uri_root' => '']; public $importMultiple = FALSE; public $frames = [0 => class Less_Tree_Ruleset { ... }, 1 => class Less_Tree_Ruleset { ... }]; public $mediaBlocks = []; public $mediaPath = []; public $functions = [] }) /home/catrope/git/less.php/lib/Less/Tree/Ruleset.php:96
PHP   6. Less_Tree_Value->compile($env = class Less_Environment { public $currentFileInfo = ['entryPath' => '', 'entryUri' => '', 'rootpath' => '', 'currentDirectory' => '', 'currentUri' => 'anonymous-file-0.less', 'filename' => 'anonymous-file-0.less', 'uri_root' => '']; public $importMultiple = FALSE; public $frames = [0 => class Less_Tree_Ruleset { ... }, 1 => class Less_Tree_Ruleset { ... }]; public $mediaBlocks = []; public $mediaPath = []; public $functions = [] }) /home/catrope/git/less.php/lib/Less/Tree/Rule.php:82
PHP   7. Less_Tree_Expression->compile($env = class Less_Environment { public $currentFileInfo = ['entryPath' => '', 'entryUri' => '', 'rootpath' => '', 'currentDirectory' => '', 'currentUri' => 'anonymous-file-0.less', 'filename' => 'anonymous-file-0.less', 'uri_root' => '']; public $importMultiple = FALSE; public $frames = [0 => class Less_Tree_Ruleset { ... }, 1 => class Less_Tree_Ruleset { ... }]; public $mediaBlocks = []; public $mediaPath = []; public $functions = [] }) /home/catrope/git/less.php/lib/Less/Tree/Value.php:24
PHP   8. Less_Tree_Call->compile($env = class Less_Environment { public $currentFileInfo = ['entryPath' => '', 'entryUri' => '', 'rootpath' => '', 'currentDirectory' => '', 'currentUri' => 'anonymous-file-0.less', 'filename' => 'anonymous-file-0.less', 'uri_root' => '']; public $importMultiple = FALSE; public $frames = [0 => class Less_Tree_Ruleset { ... }, 1 => class Less_Tree_Ruleset { ... }]; public $mediaBlocks = []; public $mediaPath = []; public $functions = [] }) /home/catrope/git/less.php/lib/Less/Tree/Expression.php:45
PHP   9. Less_Functions->escape($str = class Less_Tree_Color { public $parensInOp = FALSE; public $extendOnEveryPath = NULL; public $allExtends = NULL; public $rgb = [0 => 0, 1 => 0, 2 => 0]; public $alpha = 1; public $isTransparentKeyword = NULL }) /home/catrope/git/less.php/lib/Less/Tree/Call.php:84
.foo {
  color: #000000;
  color: '%23000000';
  color: ;
}
Krinkle triaged this task as High priority.Jan 5 2024, 2:21 PM

Change 1005040 had a related patch set uploaded (by D3r1ck01; author: Derick Alangi):

[mediawiki/vendor@master] Update wikimedia/less.php from v4.1.1 to v4.2.0

https://gerrit.wikimedia.org/r/1005040

Change 1006166 had a related patch set uploaded (by D3r1ck01; author: Derick Alangi):

[mediawiki/core@master] composer.json: Use `wikimedia/less.php` version 4.2.1

https://gerrit.wikimedia.org/r/1006166

Change 1005040 merged by jenkins-bot:

[mediawiki/vendor@master] Update `wikimedia/less.php` from version 4.1.1 to 4.2.1

https://gerrit.wikimedia.org/r/1005040

Change 1006166 merged by jenkins-bot:

[mediawiki/core@master] composer.json: Use `wikimedia/less.php` version 4.2.1

https://gerrit.wikimedia.org/r/1006166

I can confirm that the bug I mentioned in T288498#9286018 is fixed in version 4.2.1 of Less.php.

Change #1023945 had a related patch set uploaded (by Krinkle; author: Bartosz Dziewoński):

[mediawiki/libs/less.php@master] Remove backtick evaluation inside quoted strings

https://gerrit.wikimedia.org/r/1023945

Change #1026571 had a related patch set uploaded (by Krinkle; author: Krinkle):

[mediawiki/libs/less.php@master] test: Enable lessjs-3.13.1/compression fixture

https://gerrit.wikimedia.org/r/1026571

Change #1026571 merged by jenkins-bot:

[mediawiki/libs/less.php@master] test: Enable lessjs-3.13.1/compression fixture

https://gerrit.wikimedia.org/r/1026571

@larissagaulia @Krinkle - I have a suggestion to rename this ticket to "Update less.php port to fully support Less.js 2.5.3 behaviours", and put in the description that the acceptance criteria is that all test fixtures PHP version can support from 2.5.3 have to pass.
Then use this ticket description and create a new ticket to support the less-3.13.

My rationale is that most of the work we did circled 2.5.3 fixtures. Now I think we finally have a 100% parity (with what's possible) with 2.5.3 - it would be good to mark that as a milestone. That would give a good clarity on the work that is done, and what is remaining. IMHO achieving full 2.5.3 parity is a great moment to celebrate.

Plenty of work we did brought us closer to 3.13, but most of the work is based on test/assets/less-2.5.3js, not 3.x version.

Change #1034195 had a related patch set uploaded (by 沈澄心; author: 沈澄心):

[mediawiki/libs/less.php@master] update Less_Parser::parseEntitiesKeyword() to support some Less.js 3.13 behaviours

https://gerrit.wikimedia.org/r/1034195

Krinkle edited subtasks, added: T365481: Update less.php port to complete support Less.js 2.5.3 behaviours; removed: T364871: Add support for SVG image size , T363728: Unexpected compilation clamp() with mixed units, T363076: [Regression] Mixin copies rules from unrelated block, T362647: Investigate css differences in import-reference.less test, T362343: Fix failing less-2.5.3/css.less test, T362341: Fix failing less-2.5.3/variables.less test, T361035: [Regression] CSS import with variable name no longer works, T360065: [lessjs-2.5.3 legacy.less] Fix preservation of units in some cases, T358256: "-@example/2" fails due to ParseError, T357160: Treat single and double quotes as equal in "when" operator, T356706: Comments in @font-face should stay between rules, T354895: Fix comment nodes in functions & Add optional relative amounts for color functions, T353289: Less_Exception_Compiler: error evaluating function `color` argument must be a string, T353147: [urls.less fixture] Investigate @font-face difference in urls.less test case, T353146: [import.less fixture] Importing files with url() in nested files doesn’t import files, T353144: [css-guards] Fix parsing of "when" conditions after selectors with a pseudo classes (e.g. "a:hover"), T353143: [detached-rulesets] Support ruleset objects as default values of a mixin parameter, T353142: [mixins-interpolated] Fix defining of mixin named with interpolated unquoted number, T353141: [mixins-important] Support "!important" for mixins that have nested parameters., T353133: Investigate test case for "${in}${terpolotation}" and "iterated-interpolation" (import-interpolation.less test case) , T353132: Introduce commentStore and handle comments in skipWhitespace method, T353131: Add support for comments between property and value, T352911: Improve whitespace preservation before ";" or "!" (semicolon, bang), T352897: Fix ParseError in mixin-args.less test case, T352867: [mixins-guards] Investigate when() match failures for "greater than" and "less then", T352866: Preserve color keywords and short hex notation as-is, T352862: [css-3.less test case] Add support for /deep/ selector, T352859: Investigate missing selectors in selectors.less test case, T352830: Fix Undefined property: Less_Tree_Color::$value , T352829: Investigate memory error in mixins.less test case, T349580: Add Less.js v2.5.3 spec to less.php test suite, T332923: `@supports` ruleset doesn't include the selector.Tue, May 21, 2:26 PM

@pmiazga Agreed, with one minor difference. I'd prefer not to change the goal of the current task as this was filed by specific people with a specific rationale in relation to future Codex usage, which I'd rather preserve in-place. I've instead subdivided the work to recognise this milestone as T365481: Update less.php port to complete support Less.js 2.5.3 behaviours, which we can close once T364871: Add support for SVG image size is resolved.

We can then cut the last Less.php 4.x release and focus back on this task for remaining of the quarter. How much we can get done will depend on the unknowns we run into, but I'm hopeful we achieve these two releases:

  • Publish Less.php 5.0, to start off the new series. This would essentially be a minimal release only the breaking changes (new maths/strictMaths behaviour to reflect upstream Less.js 3 and Less.js 4), removal of deprecated features, and any changes to default parser options (users can opt-in/out as they see fit).
  • Publish one Less.php 5.x with a handful of Less 3.13 features of our choosing.

That works too. Thank you for handling that @Krinkle

Change #1036271 had a related patch set uploaded (by Hokwelum; author: Hokwelum):

[mediawiki/libs/less.php@master] Less.js-3.13.1 Source code

https://gerrit.wikimedia.org/r/1036271

Change #1036271 merged by jenkins-bot:

[mediawiki/libs/less.php@master] Add copy of Less.js 3.13.1 source code

https://gerrit.wikimedia.org/r/1036271

Change #1023945 merged by jenkins-bot:

[mediawiki/libs/less.php@master] Remove backtick evaluation inside quoted strings

https://gerrit.wikimedia.org/r/1023945