less / less.js

Less. The dynamic stylesheet language.
http://lesscss.org
Apache License 2.0
17.02k stars 3.41k forks source link

Custom function plugin with detached ruleset not outputting without setting the ruleset as root #3619

Closed tedwash closed 3 years ago

tedwash commented 3 years ago

Hi, I'm not sure how to word this exactly. I'm trying to write a plugin that provides a function that can rewrite class selectors. I'm taking a detached ruleset, modifying the child selectors with a visitor, and returning the modified ruleset to be used where the function was invoked.

Repo: https://github.com/tedwash/less-plugin-scoper/blob/main/src/less/plugin/scoped-function.js#L55

Simplified example (minus the visitors) LESS

@plugin "./plugin/scoped-function.js";

.wrapper {
    scoped({
        .foo {
            color: orange;
        }
    });
}

expected output

.wrapper .aBCd-foo { color: orange }

actual output (with ruleset.root=true)

.aBCd-foo { color: orange }

actual output (with ruleset.root=false)

scoped-function.js

function scoped(detachedRuleset) {
    const ruleset = detachedRuleset.callEval(this.context);

    // Have to set this to true or nothing will be rendered
    ruleset.root = true;
    return ruleset;
}

module.exports = {
    install: function (_less, _pluginManager, functions) {
        functions.add('scoped', scoped);
    },
};

Coincidentally I also get an error when trying to import a file that uses this custom function.

tedwash commented 3 years ago

Figured it out. Since there was no selector on the base ruleset, it would only generate css if it was forced into being the root node, which caused all sorts of quirks.

I added an empty selector to the ruleset:

const noopSelector = new less.tree.Selector([
    new less.tree.Element(new less.tree.Combinator(''), '&')
]);
return new less.tree.Ruleset([noopSelector], ruleset.rules);

The top level declarations and mixins were collected and appended to a new sub-ruleset.

To visualize the change: function call

.scoped({
    color: purple;

    .foo {
        color: green;
    }
});

detached ruleset value

{
    color: purple;

    .foo {
        color: green;
    }
}

new value


& {
    .aBCd {
        color: purple;
    }

    .aBCd-foo {
        color: green;
    }
}