segmentio / myth

A CSS preprocessor that acts like a polyfill for future versions of the spec.
4.32k stars 130 forks source link

Support CSS variables inheritance #63

Closed hteumeuleu closed 9 years ago

hteumeuleu commented 10 years ago

Current version 1.0.3 doesn't support CSS variables inheritance. So if we take the following HTML and CSS code :

<div class="block">
    Root Block
</div>
<div class="sidebar">
    <div class="block">
        Sidebar Block
    </div>
</div>
:root {
    --large:10px;
}

.sidebar {
    --large:20px;
}

.block {
    padding:var(--large);
}

The expected output should be something like this :

.block {
    padding:10px;
}

.sidebar .block {
    padding:20px;
}

This way we respect CSS inheritance, but this may create other issues as it creates more specificity.

MoOx commented 10 years ago

This issue has already been discussed on rework-vars and (for now) limitation is explained here https://github.com/reworkcss/rework-vars#what-to-expect

hteumeuleu commented 10 years ago

Ah, ok, merci. Maybe the main site "marketing" speech should be changed then. Because this is clearly not like a CSS polyfill then, and it's quite misleading.

kasperisager commented 10 years ago

Bummer, variable inheritance was one of the things I was personally really looking forward to after having read the W3 spec :confused:

MoOx commented 10 years ago

We are using dynamic variables syntax to create a classic (preprocessed) variable implementation but we can't handle that since we can't guess the dom tree, so we can make relevant results. That's why rework-vars is limited to :root definition.

That being said, I guess we can make this clear. I'll try to make this clear on the doc & on the website.

The best we can probably do is to handle https://github.com/segmentio/myth/issues/58

yarray commented 9 years ago

@MoOx I'm trying to understand your explanation by constructing cases:

Change css file provided by @hteumeuleu to:

:root {
    --large:10px;
}

.sidebar {
    --large:20px;
}

div .block {
    padding:var(--large);
}

According to the specification, the output should be (all cases should be covered since we cannot guess the dom):

.block {
    padding:10px;
}

div.sidebar .block {
    padding:20px;
}

.sidebar div .block {
    padding:20px;
}

div .sidebar .block {
    padding:20px;
}

Since rules in real world css are much more complex, the generated css file will be quickly bloated.

However,

scoped variables are really nice... So, how about partial support? For example, we can interpret the scope as the topmost selector, which means the output of the changed example will be:

.block {
    padding:10px;
}

.sidebar div .block {
    padding:20px;
}

This still doesn't work for the original html (the original css works, though), but at least we can hack it:

<div class="block">
    Root Block
</div>
<div class="sidebar">
    <div>
        <div class="block">
            Sidebar Block
        </div>
    </div>
</div>

It may seem confusing at first, but it may not be that bad since the working scenario is a subset of the working scenario promised by the specification. (So after the specifications being implemented in browsers what worked will still work)

MoOx commented 9 years ago

Partial support is too dangerous. Any serious project won't do that. You can also checkout https://github.com/postcss/postcss-custom-properties/issues/1 & https://github.com/postcss/postcss-custom-properties/issues/9 to understand why it should not be done. I've tried to do that for cssnext (in the postcss plugin linked above) but it's not a game I want to play anymore.

This issue should be closed but since I'm involved in another project I'll let the owners do that :)

yarray commented 9 years ago

@MoOx Thanks for your links. Now I found what's wrong with my suggestion... The key problem is, when resolving scoped variables distances between DOM elements are critical, but it's totally unexpressible due to it's not a criteria of cascading rules.

It brings some critical problems. Suppose we have a variable x with two scoped assignments:


.a {

  --x: 0;

}

.b {

  --x: 1;

}

Then our luck depends on the rule which uses the variable, if it’s something biased like


p.a {

  <any property>: val(--x);

}

then we are lucky since it can be interpreted as a single rule:


p.a {

  <any property>: 0;

}

However, if there’s something more general, like this simple input rule:


p {

<any property>: val(--x);

}

then we are in trouble. Following will proved that we cannot use finite output rules to cover all the DOM cases:

  1. Suppose we have finite output rules which can cover all cases, which means, the selected variable value using CSS rules is unambiguous and always the same as the result of scoped evaluation.
  2. From the finite output rules, we can always find a strongest rule R to match 0, and a strongest rule R of the same strength to match 1, due to the symmetry. Notice there should reasonably no adjacent operator “>” in the rules since we cannot guess DOM.
  3. We can find DOM hierarchy which match R as well match S, also due to symmetry. we sign the DOMs without the last element p as D and E. So we get:

    R(Dp) = 0, S(Ep) = 1

    According to 1, we know that Dp ~ 0, Ep ~ 1, where ~ means “scoped evaluate to”.

  4. Consider a new DOM EDp*, then R(EDp) = 0 and S(EDp) = 1. According to 1, R and S are strongest and have the same strength, so here we cannot determine whether x in EDp should be evaluated to 0 or 1, while, though, it’s obvious that EDp ~ 0!

Seems a more formal proof is possible, but it’s already sufficient to say it’s impossible to implement such a functionality (even partially as I suggested). Really a pity… Maybe the approach taken by uncss is the only real way to go as you suggested before. The only possible “partial” implementation seems to restrict the scope to 0 or 1 levels, but that seems a bit too rough.

* Strictly speaking, EDp is not always legal, but at least it's legal for a DOM consist of only div.

ianstormtaylor commented 9 years ago

Closing since it's out of scope for Myth for now.