What's new? | Help | Directory | Sign in
Google
                
Search
for
Updated Oct 10 (45 hours ago) by bobv@google.com
Labels: Phase-Implementation, Type-Library
CssResource  
Compile-time CSS processing

See also the CssResourceCookbook

Goals

Non-Goals

Overview

  1. Write a CSS file, with or without GWT-specific extensions
  2. If GWT-specific extensions are used, define a custom subtype of CssResource
  3. Declare a method that returns CssResource or a subtype in an ImmutableResourceBundle
  4. When the bundle type is generated with GWT.create() a Java expression that evaluates to the contents of the stylesheets will be created
    • Except in the simplest case where the Java expression is a string literal, it is generally not the case that a CSS file could be generated into the module output
  5. At runtime, call CssResource.getText() to retrieve the contents of the stylesheet

Use Cases

  • Constants (Working):
  • @def small 1px;
    @def black #000;
    border: small solid black;
  • Runtime substitution (Working)
  • @eval userBackground com.module.UserPreferences.getUserBackground();
    div {
      background: userBackground;
    }
    
    otherDiv {
      offset-left: value('imageResource.getWidth', 'px');
    }
  • Compile-time import (Not planned)
  • @import cssResourceFunctionName;
    • What does this mean?
      • Is this a preprocessor-like #include? If so, it is problematic to have an n:1 relationship?
      • Is this like a Java static import where we just inherit metadata like @def or @url?
    • To what extent is this actually necessary? The point of the @import statement is to allow for source and deployment modularization. Source modularization can be achieved by using multiple CssResource interfaces and we're only concerned with static resources in the Bundle.
    • The @import statement will only work for other CssResources, not for URLs at runtime, since the .gwt.xml or StyleInjector can be used in those cases.
  • Runtime structural changes (Working)
  • /* Runtime evaluation in a static context */
    @if com.module.Foo.staticBooleanFunction() {
      ... css rules ...
    }
    
    /* Compile-time evaluation */
    @if <deferred-binding-property> <space-separated list of values> {
      ... css rules ...
    }
    @if user.agent safari gecko1_8 { ... }
    @if locale en { ... }
    
    /* Negation is supported */
    @if !user.agent ie6 opera {
      ...
    }
    
    /* Chaining is also supported */
    @if true {
    } @elif false {
    } @else {
    }
    • This allows for more advanced skinning / theming / browser quirk handling by allowing for structural changes in the CSS.
    • The contents of an @if block can be anything that would be a top-level rule in a CSS stylesheet.
    • @if blocks can be arbitrarily nested.
    • What does it mean to have an @def or @eval in an @if block? Easy to make this work for property-based @if statements; would have to generate pretty gnarly runtime code to handle the expression-based @if statement. Could have block-level scoping; but this seems like a dubious use-case.
    • If the function in the first form can be statically evaluated by the compiler in a permutation, there is no runtime cost. The second form will never have a runtime cost because it is evaluated during compilation.
  • CSS Image Sprites (Working):
  • @sprite .mySpriteClass {gwt-image: "imageAccessor"; other: property;} => generates =>
      .mySpriteClass {
        background-image: url(gen.png);
        clip: ...;
        width: 27px;
        height: 42px;
        other: property;
      }
    class MyCssResource extends CssResource {
      String mySpriteClass();
    }
    
    class MyResources extends ImmutableResourceBundle {
      @Resource("my.css")
      MyCssResource css();
    
      @Resource("some.png")
      ImageResource imageAccessor();
    
      @Resource("some.png")
      @ImageOptions(repeatStyle=RepeatStyle.Horizontal)
      ImageResource repeatingImage();
    }
    • @sprite is sensitive to the FooBundle in which the CSSResource is declared; a sibling ImageResource method named in the @sprite declaration will be used to compose the background sprite.
    • @sprite entries will be expanded to static CSS rules, possibly with data: urls.
    • The expansion is sensitive to any RepeatStyle value defined on the ImageResource accessor function. The appropriate repeat-x or repeat-y properties will be added to the @sprite selector.
    • Any CSS selector can be specified for @sprite.
    • Support for IE6 isn't feasible in this format, because structural changes to the DOM are necessary to implement a "windowing" effect. Once it's possible to distinguish ie6 and ie7 in user.agent, we could revisit support for ie6. In the current implementation, the ie6 code won't render correctly, although is a purely cosmetic issue.
  • References to DataResources (Working)
  • @url myBackgroundUrl siblingDataResource;
    
    .myClass {
      background: myBackgroundUrl repeat-x;
    }
    • The identifier will be expanded to url('some_url')
  • BiDi (Open RFC):
    • Can this be accomplished using just the skinning support and some pre-cooked recipes?
    • Perhaps a magic @if predicate
    • @if :rtl {
        ... ...
      }
      
      @if !:rtl {
        ... ...
      }
    • Perhaps a magic substitution property value
    • direction: auto;
    • Perhaps use 'special pseudo-classes'
    • .myClass:rtl{}
      div:rtl{} => generates =>
        .myClass {
          direction: <correct value>
          text-align: <correct value>
        }
  • Compile-time name checking (Working):
  • java:
        class Resources {
          MyCSSResource myCSSResource();
        }
        class MyCSSResource extends CSSResource {
          Sprite mySpriteClass();
          String someOtherClass();
          String hookClass();
        }
        myWidget.addStyleName(resource.mySpriteClass());
    
    css:
        @sprite mySpriteClass mySpriteImage;
        .someOtherClass {
          /* ... */
        }
        .hookClass{} /* Empty and stripped, but left for future expansion */
    • The function just returns the CSS class name, but verifies that the CSS class exists in the stylesheet.
      • TODO: Add an @external annotation to allow a "binding" to a site-wide css file
    • No typos.
    • Possibility of using obfuscated names for programmatically-accessible class names. For obfuscation, we'll use a Adler32 checksum of the source css file expressed in base36 as a prefix (7 chars). The developer can override this with an annotation.
  • Implementation fix-ups (Not planned):
  •     div:hover { }
           Could use top-level event handler to fake on IE
        div:focus { }
           Problematic -- focus/blur don't bubble

Optimizations

Basic minification

Basic minification of the CSS input results in the minimum number of bytes required to retain the original structure of the input. In general, this means that comments, unnecessary whitespace, and empty rules are removed.

.div {
  /* This is the default background color */
  background: blue;
}
.empty {}

would be transformed into

.div{background:blue;}

Selector merging

Rules with identical selectors can be merged together.

.div {prop: value;}
.div {foo: bar;}

becomes

.div {prop:value;foo:bar;} 

However, it is necessary that the original semantic ordering of the properties within the CSS is preserved. To ensure that all selector merges are correct, we impose the restriction that no rule can be promoted over another if the two rules define a common property. We consider border and border-top to be equivalent properties, however padding-left and padding-right are not equivalent.

Thus

.a {background: green;}
.b {border: thin solid blue;}
.a {border-top: thin solid red;}

cannot be merged because an element whose CSS class matches both .a and .b would be rendered differently based on the exactly order of the CSS rules.

When working with @if statements, it is preferable to work with the form that operates on deferred-binding properties because the CSS compiler can evaluate these rules statically, before the merge optimizations. Consider the following:

.a {
  background: red;
}

@if user.agent safari {
  .a {
    \-webkit-border-radius: 5px;
  }
} @else {
  .a {
    background: url('picture_of_border.png');
  }
}

In the safari permutation, the rule becomes .a{background:red;\-webkit-border-radius:5px;} while in other permutations, the background property is merged.

Property merging

Rules with identical properties can be merged together.

.a {background: blue;}
.b {background: blue;}

can be transformed into

.a,.b{background:blue;}

Promotion of rules follows the previously-established rule of not promoting a rule over other rules with common properties.

Levers and Knobs

Implementation Details

Scope

Scoping of obfuscated class names is defined by the return type of the CssResource accessor method in the resource bundle. Each distinct return type will return a wholly separate collection of values for String accessor methods.

interface A extends CssResource {
  String foo();
}

interface B extends A {
  String foo();
}

interface C extends A {
  String foo();
}

interface D extends C {
  // Intentionally not defining foo()
}

interface Resources {
  A a();
  A a2();
  B b();
  C c();
  D d();
  D d2();

It will be true that a().foo() != b().foo() != c().foo() != d().foo(). However, a().foo() == a2().foo() and d().foo() == d2().foo().

Shared scopes

In the case of "stateful" CSS classes like focused or enabled, it is convenient to allow for certain String accessor functions to return the same value, regardless of the CssResource type returned from the accessor method.

@Shared
interface FocusCss extends CssResource {
  String focused();
  String unfocused();
}

interface A extends FocusCss {
  String widget();
}

interface B extends FocusCss {
  String widget();
}

interface C extends B {
  // Intentionally empty
}

interface Resources {
  A a();
  B b();
  C c();
  FocusCss f();
}

In this example, a().focused() == b().focused() == c().focused == f().focused(). However, a().widget() != b().widget != c.widget(), as in the previous example.

The short version is that if distinct CSS types need to share obfuscated class names, the CssResource subtypes to which they are attached must share a common supertype that defines accessors for those names and has the @Shared annotation.

Imported scopes

The Java type system can be somewhat ambiguous when it comes to multiple inheritance of interfaces that define methods with identical signatures, although there exist a number of cases where it is necessary to refer to multiple, unrelated CssResource types. Consider the case of a Tree that contains Checkboxes.

@ImportedWithPrefix("tree")
interface TreeCss extends CssResource {
  String widget();
}

@ImportedWithPrefix("checkbox")
interface CbCss extends CssResource {
  String widget();
}

interface MyCss extends CssResource {
  String other();
}

interface Resources {
  @Import({TreeCss.class, CbCss.class})
  MyCss css();
}
/* Now we can write a descendant selector using the prefixes defined on the CssResource types */
.tree-widget .checkbox-widget {
  color: red;
}

.other {
  something: else;
}

Composing a "TreeCbCss" interface would be insufficient because consumers of the TreeCss interface and CbCss interface would receive the same value from the widget method. Moreover, the use of just .widget in the associated CSS file would also be insufficient without the use of some kind of class selector prefix. The prefix is defined on the CssResource type (instead of on the CssResource accessor method) In the interest of uniformity across all CSS files that import a given scope. It is a compile-time error to import multiple classes that have the same prefix or simple name.

The case of shared scopes could be handled solely with importing scopes, however this form is somewhat more verbose and relationships between unrelated scopes is less common than the use of stateful selectors.

Example: StackPanel inside a StackPanel

This is a use-case that is currently impossible to style correctly in GWT.

// Assume this interface is provided by the UI library
interface StackPanelCss extends CssResource {
  String widget();
  // and many more class names
}

// App code defines the following interfaces:

@ImportedWithPrefix("inner")
interface StackPanelInner extends StackPanelCss {
  // Empty interface
}

interface StackPanelOuter extends StackPanelCss {
  // Empty interface
}

interface Resources {
  @Resource("stackPanel.css")
  @Strict
  StackPanelInner inner();

  @Import(StackPanelInner.class)
  @Resource("stackPanel.css", "outer.css")
  @Strict
  StackPanelOuter outer();
}

The file stackPanel.css defines the basic structure of any given stackPanel:

.widget .title {}
.widget .content {}
/* Other stuff to make a StackPanel work */

The outer() method can continue to use the base stackPanel.css file, because the accessor methods defined in StackPanelCss are mapped into the default (no-prefix) namespace. The inner StackPanel's style members are also available, but in the inner prefix. Here's what outer.css might contain:

.widget {color: red;}

.inner-widget {
  color: blue;
  font-size: smaller;
}

Strict scoping

In the normal case, any class selectors that do not match String accessor functions are left unobfuscated in the compiled output.

interface MyCssResource extends CssResource {
  String foo();
}

interface Resources {
  @Strict
  @Resource("my.css")
  MyCssResource css();
}
/* This is ok */
.foo {}

/* This would generate a compile error in @Strict mode */
.other {}

The @Strict annotation can be applied to a CssResource accessor function to make it a compile-time error to have any unobfuscated class selectors in the CSS file. This additional level of restriction is recommended for library-oriented resource bundles in order to avoid inadvertently polluting the global CSS namespace.

Important open questions


Comment by svramu, Aug 15, 2008

Interesting and happy that the side line talk I heard somewhere about CSS optimization as a part of GWT compilation is coming to life.

Does this cover the following use cases? (I also minimally checked out, http://code.google.com/p/google-web-toolkit-incubator/wiki/UiBinder)

The developer has one java object which s/he have to show in more than one different UI. The fields and data behavior will remain same in different GUI But s/he have a good HTML/CSS designer who can generate more than one static screen. The developer would just cut and slice the HTML, and insert ids in right places Then use something like HTMLPanel to insert the dynamic server-bound GWT components/composites inside this designer provided layout (with CSS), by id(?). The developer wants the option of upfront or the lazy loading of the layouts in runtime Of course with the hashed names to avail the browser cache, and still dynamic updates (like normal gwt html/js). While doing this, it would be nice if the CSS is 'validated' as much is possible from static information, and also unified (clubbing many css file), and also minified. The developer would just like to annotate the basic java class, and turn it into a GUI for the given layout (name based mapping with compiler and extra annotation to check).

How much can the new UiBinder and CssResource direction help in this? Though this looks like a complex use case, it was straight for me as I can see that a graphics designer generates better static HTML layout than me. So I would like to off load all my layout concerns and concentrate only on component and server interaction.

I practically and crudely did this the first time with HTMLPanel, and rpc to fetch the html template. But surely that was not optimal and too much boiler plate code, also not hashed and cached.

Now I'm considering generator to get it this way. As I see it is few days work (need to learn with the good 1.5 showcase example for generator). The benefits are.

# CSS can be CSS and stay declarative, unlike some IF I see above (not sure if that is unavoidable) # Normal flow, and anyway required as a the lowest denominator.

Would love some feedback on how I can use the current time better, in anticipation of an effective solution for the above.

Comment by svramu, Aug 15, 2008

Oh sorry, I messed the formatting. Why is still wiki like formatting used, why not rich editor as in GWT?


Sign in to add a comment