com-lihaoyi / scalatags

ScalaTags is a small XML/HTML construction library for Scala.
https://com-lihaoyi.github.io/scalatags/
MIT License
758 stars 117 forks source link

Stylesheet.allClasses is unreliable #147

Closed scf37 closed 7 years ago

scf37 commented 7 years ago

Depending on context, it can return empty collection.

Expected output:

1
1

Actual output:

1
0

Environment:

Scala 2.12.0
Oracle JDK 1.8.0_101
"com.lihaoyi" %% "scalatags" % "0.6.2"

Code to reproduce:

import scalatags.Text.all._
import scalatags.stylesheet.StyleSheet

object STags {
  def main(args: Array[String]): Unit = {

    println(TestCss.allClasses.length)
    foo(TestCss)
  }

  def foo(c: StyleSheet): Unit = {
    println(c.allClasses.length)
  }
}

object TestCss extends StyleSheet {

  val body = cls(
    backgroundColor := "red",
    height := 125,
    &.hover(
      backgroundColor := "blue"
    )
  )
}
lihaoyi commented 7 years ago

I have bumped into this too. The root cause in my case seems to be incremental compilation being unreliable, and wouldn't be surprised if other things could cause it too

chipsenkbeil commented 7 years ago

I think that I am hitting this as well. Cleaning and deleting my target directory to force a full recompile didn't affect the issue.

Is there an issue with calling allClasses when using a reference to the stylesheet and not the stylesheet instance itself? My stylesheets are objects.

package org.scaladebugger.docs.styles

import scalatags.Text.all._
import scalatags.stylesheet._

/**
 * Contains stylesheet implicits.
 */
object Implicits {
  implicit class StyleSheetWrapper(private val styleSheet: StyleSheet) {
    /**
     * Transforms the stylesheet into a <style> tag.
     *
     * @return The style tag representing this stylesheet
     */
    def toStyleTag: Modifier = {
      val styleText = styleSheet.styleSheetText
      println("STYLE TEXT: " + styleText) // Returns empty string
      println("CLASSES: " + styleSheet.allClasses.map(_.name)) // Returns empty seq
      tag("style")(styleText)
    }
  }

  implicit class StringWrapper(private val string: String) {
    /**
     * Transforms the string into into a <style> tag.
     *
     * @return The style tag containing the raw text
     */
    def toStyleTag: Modifier = tag("style")(string)
  }
}
lihaoyi commented 7 years ago

So here's the problem: .styleSheetText relies on a macro in order to discover the val foo: Cls definitions within the body of the StyleSheet, as runtime reflection cannot be used in Scala.js. Unfortunately, when you are treating them as a styleSheet: StyleSheet, all those val foo: Cls declarations are not visible, and thus .styleSheetText returns nothing.

The immediate solution is to call .styleSheetText directly on the specific StyleSheet object, and not after it's been typed as generic StyleSheet. A separate fix would be to ensure every stylesheet has a val myStyleSheetText: String = styleSheetText defined within it, so the macro sees the "full" type of the StyleSheet, and then externally refer to it via myStyleSheetText.

This can already be done, but we should probably enforce it by making .styleSheetText protected so you are forced to call it within your class.

This would also solve the problems with incremental compilation not working properly when accessing .styleSheetText in a separate file.

chipsenkbeil commented 7 years ago

@lihaoyi any way to prevent the error is a good way to me. Documenting what you just described in this issue also seems like a good idea.

lihaoyi commented 7 years ago

Fixed by d68682e9264b7e1bd394360c137532b2c83b5fdd

lihaoyi commented 7 years ago

See the new initStyleSheet() syntax in http://www.lihaoyi.com/scalatags/#CSSStylesheets, which introduces a bit of boilerplate but should avoid this problem entirely

chipsenkbeil commented 7 years ago

Excellent! I'll grab the new release and try it out when I have some time.