lit/lit-element

Styles are not rendered in the same order as what is specified in lit-html template

frankiefu opened this issue · 1 comments

Consider this:

import { LitElement, html } from '@polymer/lit-element';
import '@polymer/iron-icon/iron-icon.js'; // 3.0.0-pre.12

const SharedStyles = html`<style>button {background: blue;}</style>`;
class MyApp extends LitElement {
  render() {
    return html`
    ${SharedStyles}
    <style>button {background: red;}</style>
    <button>Should be red</button>
    `;
  }
}

If you run the above you will see the SharedStyles style element is placed after the normal <style> element so button has background: blue instead of background: red.

Note that if you comment out Polymer3's iron-icon.js import then it works fine. Also this seems to start happening in 0.3.0 release which has ShadyCSS support.

cc @graynorton

This is a flaw with the way shady-render is using ShadyCSS to provide scoping.

When Shady DOM is in use

Shady CSS removes styles from element templates, scopes them, and puts them into the document.head. The shady-render method in lit-html calls ShadyCSS.prepareTemplate once for each lit template in the element. The templates are individually processed outside-in, and any style elements are scoped per template and are placed in the head in reverse order (which seems wrong but fixing this wouldn't help). That means regardless of how the styles are intended to be ordered in the resultant DOM, they are ordered in reverse order of how the templates are processed by lit-html. For example,

const styleA = `<style id="styleA">...</style>`
html`<style id="styleB">...</style>${styleA}`

Here styleA is always incorrectly placed before styleB regardless of where it is in the element DOM because it is in the 2nd template lit-html processes for the element and ShadyCSS puts these in reverse order.

When Shady CSS is loaded by native Shadow DOM is in use

ShadyCSS collapses all of the styles into one element that's placed at the top of the template. This breaks the case where a style is included as a part before a static style like this:

const styleA = `<style id="styleA">...</style>`
html`${styleA}<style id="styleB">...</style>`

When processed, styleB is placed before the dynamic part for styleA which is incorrect. This exact issue can be worked around by making styleB a dynamic part, but this then interacts poorly with the ShadyCSS ordering so there's no good general workaround until this is addressed.