客户端捆绑包

已部署的 GWT 应用程序中的资源可以大致分为三种类别:永远不缓存的资源(.nocache.js)、永远缓存的资源(.cache.html)以及其他所有资源(myapp.css)。ClientBundle 接口将其他所有资源类别中的条目移到永远缓存的资源类别中。

  1. 概览
  2. DataResource
  3. TextResource 和 ExternalTextResource
  4. ImageResource
  5. GwtCreateResource
  6. CssResource
  7. CssResourceCookbook

概览

目标

  • 不再需要担心您的应用程序是否获得了程序资源的正确内容。
  • 减少由中间代理服务器引起的非确定性。
  • 为程序资源启用更积极的缓存头。
  • 通过在编译期间执行一致性检查,消除物理文件名与 Java 代码中的常量之间的不匹配。
  • 在浏览器和大小合适的情况下,使用“data:” URL、JSON 捆绑包或其他将资源嵌入到已编译 JS 中的方法来消除不必要的往返。
  • 为添加新的资源类型提供可扩展的设计。
  • 确保多个 ClientBundle 资源函数引用相同内容不会造成任何损失。

非目标

  • 提供文件系统抽象

示例

要使用 ClientBundle,请在您的 gwt.xml 文件中添加一个 inherits 标签

<inherits name="com.google.gwt.resources.Resources" />

如果您编写了此接口


public interface MyResources extends ClientBundle { public static final MyResources INSTANCE = GWT.create(MyResources.class); @Source("my.css") public CssResource css(); @Source("config.xml") public TextResource initialConfiguration(); @Source("manual.pdf") public DataResource ownersManual(); }

然后您可以这样说

  MyResources.INSTANCE.css().ensureInjected();

  // Display the manual file in an iframe
  new Frame(MyResources.INSTANCE.ownersManual().getSafeUri().asString());

I18N

ClientBundle 与 GWT 的 I18N 模块兼容。

假设您定义了一个资源

@Source("default.txt")
public TextResource defaultText();

对于 locale 延迟绑定属性的每个可能值,ClientBundle 生成器将以类似于 Java 的 ResourceBundle 的方式查找指定文件名的变体。

假设 locale 设置为 fr_FR。生成器将按以下顺序查找文件

  1. default_fr_FR.txt
  2. default_fr.txt
  3. default.txt

这将对所有资源类型都能很好地工作,这将允许您提供其他资源的本地化版本,例如 ownersManual_en.pdfownersManual_fr.pdf

可插拔资源生成

每个 ResourcePrototype 的子类型都必须定义一个 @ResourceGeneratorType 注解,其值为扩展 ResourceGenerator 的具体 Java 类。ResourceGenerator 的实例负责累积(或捆绑)传入的资源数据,以及少量的代码生成以组装 ClientBundle 类的具体实现。ResourceGenerator 子类的实现者可以预期在 ClientBundle 接口中,对于给定类型的资源,只创建一个 ResourceGenerator

ResourceGenerator 上的方法按以下顺序调用

  1. initResourceGenerator 提供一个 ResourceContext
  2. prepareResourceGenerator 预期要处理的每个 JMethod 调用
  3. createFields 允许 ResourceGenerator 在类级别添加代码
  4. createAssignment 为每个 JMethod 调用。生成的代码应适合用作赋值表达式的右侧。
  5. finish 在所有赋值都应写入后调用。

ResourceGenerators 预计将使用 ResourceGeneratorUtil 类。

杠杆和旋钮

  • ClientBundle.enableInlining 是一个延迟绑定属性,可用于禁用在会支持将资源数据内联到已编译 JS 中的浏览器中使用“data:” URL。* ClientBundle.enableRenaming 是一个配置属性,它将禁用使用强命名缓存文件。

资源类型

这些资源类型是 ClientBundle 中定义的方法的有效返回类型

DataResource

DataResource 是最简单的资源类型,提供了一个 URL,通过该 URL 可以运行时检索文件的原始内容。提供的主要优化是根据文件的原始内容自动重命名文件,以便浏览器能够对生成的 URL 进行强缓存。非常小的文件可能会被转换为“data:” URL(在支持它们的浏览器中)。

interface Resources extends ClientBundle {
  Resources INSTANCE = GWT.create(Resources.class);

  @Source("mycursor.cur")
  DataResource customCursor();
}

// Elsewhere
someDiv.getStyle().setProperty("cursor", "url(" + Resources.INSTANCE.customCursor().getUrl() + ")");

不适合内联到已编译的 JavaScript 中作为“data:” URL 的资源将被发射到编译输出中,并使用基于文件内容的强名称。例如,foo.pdf 将被赋予类似于 ABC1234.cache.pdf 的名称。Web 服务器应配置为使用公开可缓存的头和远期 Expires 头来提供与 *.cache.* glob 匹配的所有文件。

TextResource 和 ExternalTextResource

相关的资源类型 TextResourceExternalTextResource 提供对静态文本内容的访问。这两者之间的主要区别是前者将文本实习到已编译的 JavaScript 中,而后者将相关的文本资源捆绑到一个单独的文件中,该文件是异步访问的。

interface Resources extends ClientBundle {
  Resources INSTANCE = GWT.create(Resources.class);

  @Source("a.txt")
  TextResource synchronous();

  @Source("b.txt")
  ExternalTextResource asynchronous();
}

// Using a TextResource
myTextArea.setInnerText(Resources.INSTANCE.synchronous().getText());

// Using an ExternalTextResource
Resources.INSTANCE.asynchronous().getText(new ResourceCallback<TextResource>() {
  public void onError(ResourceException e) { ... }
  public void onSuccess(TextResource r) {
    myTextArea.setInnerText(r.getText());
  }
});

ImageResource

本节介绍 ImageResource 如何调整图像数据的编译时处理,并在运行时提供对图像数据的有效访问。

目标

  • 以最有效的方式提供运行时对图像数据的访问* 不要将 ImageResource API 绑定到特定的小部件框架

非目标

  • 提供通用的图像处理框架。

概览

  1. 定义一个包含一个或多个 ImageResource 访问器的 ClientBundle。对于每个访问器方法,请添加一个 @Source 注解,并使用要添加到程序中的新图像的路径。ClientBundle 生成器将您接口中定义的所有图像组合到一个优化后的图像中。
interface Resources extends ClientBundle {
  @Source("logo.png")
  ImageResource logo();

  @Source("arrow.png")
  @ImageOptions(flipRtl = true)
  ImageResource pointer();
}
  1. 通过调用 GWT.create() 来实例化 ClientBundle
  2. 实例化一个或多个 Image 小部件,或与 CssResource @sprite 指令一起使用。

例如,代码

Resources resources = GWT.create(Resources.class);
Image img = new Image(resources.logo());

导致 GWT 加载为 Resources 生成的合成图像,然后创建一个 Image,该 Image 是仅针对 logo 图像的正确子区域。

ImageOptions

@ImageOptions 注解可以应用于 ImageResource 访问器方法,以调整图像数据的编译时处理

  • flipRtl 是一个布尔值,当 LocaleInfo.isRTL() 返回 true 时,它会导致图像在其 Y 轴上镜像。
  • repeatStyle 是一个枚举值,它与 @sprite 指令结合使用,以指示图像打算被平铺。

支持的格式

ImageBundleBuilder 使用 Java ImageIO 框架读取图像数据。这包括对所有常见 Web 图像格式的支持。

ImageResource 支持动画 GIF 文件。虽然图像数据不会被合并到图像条中,但该资源仍然通过更大的 ClientBundle 框架以浏览器优化的方式提供服务。

从 ImageBundle 转换

将现有代码转换为使用 ImageResource 只需要进行最小的更改。

  • ImageBundle 变为 ClientBundle
  • AbstractImagePrototype 变为 ImageResource
  • AbstractImagePrototype.createImage() 变为 new Image(imageResource)
  • 可以通过调用 AbstractImagePrototype.create(imageResource) 来提供 API 包装器,以继续使用 AbstractImagePrototype 上的其他方法。

GwtCreateResource

GwtCreateResourceClientBundle 与任何其他(资源)类型(可默认实例化)之间的桥接类型。GwtCreateResource 的实例充当其他类型的工厂。

interface Resources extends ClientBundle {
  Resources INSTANCE = GWT.create(Resources.class);

  @ClassType(SomeClass.class)
  GwtCreateResource<ReturnType> factory();
}

// Elsewhere
ReturnType obj = Resources.INSTANCE.factory().create();

虽然以上等同于

ReturnType obj = GWT.<ReturnType> create(SomeClass.class);

但它允许使用类不知道传递到 GWT.create() 的特定类字面量。只要该类型可默认实例化,就不需要为 SomeClass 指定特定的延迟绑定规则。

CssResource

本节介绍 CssResource 以及 CSS 的编译时处理。

  1. 目标
  2. 概览
  3. 功能
  4. 优化
  5. 杠杆和旋钮
  6. 选择器混淆细节

另请参阅 CssResourceCookbookStyleInjector

目标

  • 主要
    • 与非 GWT 感知 CSS 解析器兼容(即任何扩展都应为有效的 CSS 语法)
    • 这并不意味着,如果你只是在浏览器中显示样式表,该样式表就一定有意义
    • 语法验证
    • 压缩
    • 利用 GWT 编译器
      • 为不同的浏览器自动提供不同的 CSS
      • 内容的静态评估
  • 次要
    • 基本 CSS 模块化
      • 通过依赖注入 API 风格
      • 只有在需要时,小部件才能注入其自身的 CSS
    • 双向(Janus 风格?)
    • CSS 图像条
    • “改进 CSS”
      • 常量
      • 简单表达式
  • 第三级
    • 运行时操作(StyleElement.setEnabled() 处理许多情况)
    • 编译时类名检查(Java/CSS)
    • 混淆

非目标

  • 服务器端操作
    • CssResource 中的所有功能都必须仅用编译时和运行时代码实现。任何功能都不能依赖于服务器端代码的运行时支持。

概述

  1. 编写 CSS 文件,无论是否使用 GWT 特定的扩展
  2. 如果使用了 GWT 特定的扩展,则定义 CssResource 的自定义子类型
  3. 在 ClientBundle 中声明一个返回 CssResource 或子类型的方法
  4. 使用 GWT.create() 生成捆绑包类型时,将创建评估为样式表内容的 Java 表达式
    • 除了 Java 表达式是字符串字面量的最简单情况外,通常情况下,CSS 文件无法生成到模块输出中
  5. 在运行时,调用 CssResource.ensureInjected() 以注入样式表的内容
    • 此方法可以安全地调用多次,因为后续调用将成为无操作
    • 建议的模式是在各种小部件类型的静态初始化器中调用 ensureInjected()

功能

常量

@def small 1px;
@def black #000;
border: small solid black;
  • 解析规则使得难以使用定界符来进行替换
  • 重新定义内置尺寸允许用户编写纯 CSS 来草拟样式,然后对其进行微调。
  • 建议用户使用大写名称,类似于静态 final 成员。
  • 任何合法的属性值或表达式都可以在 @def 中使用
  • 定义单个数值的 @def 规则可以通过类似于混淆类名的方式进行访问,方法是在 CssResource 类型上定义一个返回原始数值的访问器方法。
interface MyResources extends CssResource {
  int small();
}
  • 调用 small() 将返回 1

  • @def` 规则也可以作为字符串访问。你可以使用以下方法检索上面的两个定义:

interface MyResources extends CssResource {
  String small();
  String black();
}
  • 调用 small() 返回“1px”
  • 调用 black() 返回“#000”
  • 生成器不允许你声明与类同名的 @def 规则,除非你使用 @ClassName 注解来注解用于检索类的方法。
@def myIdent 10px;
.myIdent {
  ...
}

interface MyResources extends CssResource {
  String myIdent();

  @ClassName("myIdent")
  String myIdentClass();
}
  • 调用 myIdent() 返回 @def 值“10px”
  • 调用 myIdentClass() 返回 .myIdent 的混淆类名

运行时替换

@eval userBackground com.module.UserPreferences.getUserBackground();
div {
  background: userBackground;
}
  • 在注入样式表时,为评估静态方法提供运行时支持。如果我们允许对样式元素进行编程操作,则将来可以添加触发/动态更新。

  • 如果用户定义的函数可以被编译器静态评估,那么特定 CssResource 的实现应该缩减为一个字符串字面量。

  • 这允许轻松支持非结构化皮肤更改。

值函数


.myDiv { offset-left: value('imageResource.getWidth', 'px'); }
  • value() 函数接受一系列用点分隔的标识符和可选的后缀。标识符被解释为零参数方法调用,使用传递到 GWT.create() 的接口作为根命名空间。通过只允许零参数方法,生成器不需要尝试执行类型检查。唯一需要的验证是确保方法序列存在。链中可能存在任意多个标识符。
  • value() 函数可以与 @def 结合使用
@def SPRITE_WIDTH value('imageResource.getWidth', 'px')

.selector {
  width: SPRITE_WIDTH;
}

字面量函数

一些用户代理使用不符合 CSS 语法的属性值。literal() 函数的存在是为了允许使用这些非标准属性值。

div-with-literal {
  top: literal("expression(document.compatMode==\"CSS1Compat\" ? documentElement.scrollTop : document.body.scrollTop \\ 2)");
}

请注意,有必要对反斜杠 (\) 和双引号 (") 字符进行转义。

条件 CSS

/* 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 {
}
  • 这允许通过允许 CSS 中的结构性更改来实现更高级的皮肤/主题/浏览器怪癖处理。
  • @if 块的内容可以是 CSS 样式表中任何顶层规则。
  • @if 块可以任意嵌套。
  • @if 块中使用 @def 或 @eval 的含义是什么?对于基于属性的 @if 语句,很容易实现这一点;对于基于表达式的 @if 语句,则必须生成非常复杂的运行时代码才能处理。可以使用块级作用域;但这似乎是一个可疑的用例。
  • 如果第一种形式中的函数可以在编译器在某个排列中静态评估,那么就不会产生运行时成本。第二种形式永远不会产生运行时成本,因为它是在编译期间评估的。

图像精灵


@sprite .mySpriteClass {gwt-image: "imageAccessor"; other: property;} => generates => .mySpriteClass { background-image: url(gen.png); clip: ...; width: 27px; height: 42px; other: property; }
interface MyCssResource extends CssResource {
  String mySpriteClass();
}

class MyResources extends ClientBundle {
  @Source("my.css")
  MyCssResource css();

  @Source("some.png")
  ImageResource imageAccessor();

  @Source("some.png")
  @ImageOptions(repeatStyle=RepeatStyle.Horizontal)
  ImageResource repeatingImage();
}
  • @sprite 对声明 CssResource 的 FooBundle 很敏感;在 @sprite 声明中命名的同级 ImageResource 方法将用于组合背景精灵。
  • @sprite 条目将扩展为静态 CSS 规则,可能包含 data: url。
  • 扩展对 ImageResource 访问器函数上定义的任何 RepeatStyle 值都很敏感。适当的 repeat-xrepeat-y 属性将添加到 @sprite 选择器。
  • 可以为 @sprite 指定任何 CSS 选择器。
  • 无法以这种格式支持 IE6,因为必须对 DOM 进行结构性更改才能实现“窗口”效果。一旦能够区分 ie6 和 ie7 的 user.agent,我们就可以重新考虑对 ie6 的支持。在当前实现中,ie6 代码将无法正确呈现,虽然这是一个纯粹的视觉问题。

对数据资源的引用

/* @url <constant name> <DataResource method name> */
@url myCursorUrl fancyCursorResource;

.myClass {
  cursor: myCursorUrl, pointer;
}
interface MyResources extends ClientBundle {
  @Source("myCursor.cur")
  DataResource fancyCursorResource();

  @Source("my.css")
  CssResource css();
}
  • 标识符将根据 DataResource.getUrl() 的返回值扩展为 url('some_url')

RTL 支持

  • CssResource 支持在编译时将 CSS 代码自动转换为从右到左的变体。
  • 使用 RTL 变体由 com.google.gwt.i18n.client.LocaleInfo.getCurrentLocale().isRTL() 确定
  • 应用的转换
    • leftright 属性被翻转。
    • 任何具有 leftright 值的属性都会被翻转:clear float text-align page-break-before page-break-after
    • background/background-position 属性被翻转。用百分比表示的附件被镜像:40% 变成 60%
    • margin padding border-color border-styleborder-width 四值属性被翻转:1px 2px 3px 4px 变成 1px 4px 3px 2px
    • 任何 xyz-rightxzy-right-abc 属性都被翻转为 xzy-leftxzy-left-abc
    • body 选择器上的 direction 属性将从 ltr 翻转为 rtl;在任何其他选择器上,direction 属性保持不变
    • 当光标属性具有 resize 值时,它将被翻转:ne-resize 变成 nw-resize
  • 可以通过将 CSS 部分包含在 @noflip 块中来将其从自动翻转中排除
@noflip {
  .selector {
    left: 10;
  }
}
  • 使用基于像素的偏移量的 background 属性值,例如 background-position: 4px 10px;,将不会被自动转换。
    • 四值 CSS3 background-position 属性将被 RTL 支持自动翻转
background-position: left 4px top 10px;
*   For CSS2 browsers, it will be necessary to use an `@sprite` rule:
@sprite .bgImage {
        gwt-image: 'background-image';
        position: absolute;
        left: 4px;
        top: 10px;
    }
  • ImageResources 可以通过使用 @ImageOptions 注解在 RTL 上下文中自动翻转
@Source("icon128.png")
@ImageOptions(flipRtl = true)
ImageResource logo();

选择器混淆


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 */
  • 该函数仅返回 CSS 类名,但会验证 CSS 类是否存在于样式表中。
  • 通过接口访问类名可确保使用 CssResource 的代码中不会出现任何拼写错误。
  • 为了混淆,我们将使用源 css 文件的 Adler32 校验和(以 36 进制表示)作为前缀(7 个字符)。开发者可以使用 CssResource.obfuscationPrefix 延迟绑定属性来覆盖它。
  • <set-configuration-property name="CssResource.obfuscationPrefix" value="empty" /> 可用于最短的选择器名称,但这仅在 GWT 模块完全控制页面时才推荐。

  • @external at-rule 可用于有选择地禁用对命名选择器的混淆;有关更多详细信息,请参阅 外部和遗留范围

优化

基本压缩

对 CSS 输入进行基本压缩会生成保留输入原始结构所需的最小字节数。通常,这意味着注释、不必要的空格和空规则将被删除。

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

将被转换为


.div{background:blue;}

选择器合并

具有相同选择器的规则可以合并在一起。

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

变成


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

但是,必须保留 CSS 中属性的原始语义顺序。为了确保所有选择器合并都是正确的,我们施加了限制,即 **如果两个规则定义了共同属性,则任何规则都不能被提升到另一个规则之上**。我们认为 borderborder-top 是等效的属性,但 padding-leftpadding-right 不是等效的属性。

因此

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

无法合并,因为 CSS 类同时匹配 .a.b 的元素将根据 CSS 规则的确切顺序进行不同的呈现。

在使用@if语句时,最好使用操作延迟绑定属性的表单,因为 CSS 编译器可以在合并优化之前静态地评估这些规则。请考虑以下内容

.a {
  background: red;
}

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

在 Safari 排列中,规则变为.a{background:red;\-webkit-border-radius:5px;},而在其他排列中,background属性被合并。

属性合并

具有相同属性的规则可以合并在一起。

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

可以转换成

.a,.b{background:blue;}

规则的提升遵循先前确立的规则,即不将规则提升到具有共同属性的其他规则之上。

杠杆和旋钮

  • 配置属性CssResource.style可以设置为pretty,这将禁用类名混淆,并将 CSS 内容格式化为漂亮打印。将其与ClientBundle.enableInlining值为false结合使用,以生成适合客户端编辑的 CSS 表达式。
  • 配置属性CssResource.mergeEnabled可以设置为false以禁用重新排序规则的修改。这应该被视为一种临时措施,直到合并逻辑得到充分验证。
  • 为了允许客户端调整有效(即排列特定)样式规则,可以将CssResource.getText()的值存储到一个文本区域中。连接一些 UI 操作以将文本区域的内容传递到StyleInjector.setContents()中,以覆盖原始的注入样式表。

选择器混淆细节

严格范围

在正常情况下,任何与字符串访问器函数不匹配的类选择器都是错误。可以通过在 CSS 访问器方法中添加@NotStrict注释来禁用此行为。启用@NotStrict行为仅推荐用于从外部 CSS 文件迁移到CssResource的应用程序。

interface MyCssResource extends CssResource {
  String foo();
}

interface Resources {
  @Source("my.css")
  @CssResource.NotStrict
  MyCssResource css();
}
/* This is ok */
.foo {}

/* This would normally generate a compile error in strict mode */
.other {}

范围

混淆类名的范围由资源包中CssResource访问器方法的返回类型定义。每个不同的返回类型将为字符串访问器方法返回一个完全独立的值集合。

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();

a().foo() != b().foo() != c().foo() != d().foo()将为真。但a().foo() == a2().foo()且d().foo() == d2().foo()。

共享范围

在“有状态”CSS 类(如focusedenabled)的情况下,允许某些字符串访问器函数返回相同的值很方便,无论从访问器方法返回的CssResource类型如何。

@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();
}

在此示例中,a().focused() == b().focused() == c().focused == f().focused()。但a().widget() != b().widget != c.widget(),如前例所示。

简而言之,如果不同的 CSS 类型需要共享混淆的类名,则附加它们的CssResource子类型必须共享一个共同的超类型,该超类型定义了这些名称的访问器,并具有@Shared注释。

导入范围

在涉及定义具有相同签名的多个方法的接口的多重继承时,Java 类型系统可能有点模棱两可,尽管存在许多情况需要引用多个不相关的CssResource类型。考虑包含复选框的树的情况。

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

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

interface MyCss extends CssResource {
  String other();
}

interface Resources extends ClientBundle {
  @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;
}

组合“TreeCbCss”接口将是不够的,因为 TreeCss 接口和 CbCss 接口的使用者将从 widget 方法中接收相同的值。此外,如果没有使用某种类选择器前缀,在关联 CSS 文件中仅使用.widget也将是不够的。前缀是在CssResource类型上定义的(而不是在CssResource访问器方法上定义的),为了在导入给定范围的所有 CSS 文件中保持一致性。导入多个具有相同前缀或简单名称的类是一个编译时错误。

共享范围的情况可以用导入范围完全处理,但是这种形式有点冗长,并且不相关范围之间的关系不如有状态选择器使用常见。

示例:StackPanel 在 StackPanel 内部

这是一个目前在 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 {
  @Source("stackPanel.css")
  StackPanelInner inner();

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

文件stackPanel.css定义了任何给定 StackPanel 的基本结构

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

outer()方法可以继续使用基本stackPanel.css文件,因为StackPanelCss中定义的访问器方法被映射到默认(无前缀)命名空间。内部 StackPanel 的样式成员也可以使用,但在inner前缀中。以下是outer.css可能包含的内容

.widget {color: red;}

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

外部和遗留范围

在许多情况下,新开发的 CSS 需要与外部或遗留 CSS 结合使用。@external at-rule 可以用来抑制选择器混淆,同时仍然允许以编程方式访问选择器名称。

interface MyCssResource extends CssResource {
  String obfuscated();
  String legacySelectorA();
}

interface Resource extends ClientBundle {
  @Source("my.css")
  MyCssResource css();
}
@external legacySelectorA, legacySelectorB;
.obfuscated .legacySelectorA { .... }
.obfuscated .legacySelectorB { .... }

在上面的示例中,.obfuscated类选择器将被混淆,obfuscated()方法将返回替换的名称。这两个遗留选择器都不会被混淆,legacySelectorA()方法将返回未混淆的值。此外,由于legacySelectorB是在@external声明中显式定义的,因此不可访问的类名不会触发错误。

自动生成CssResource接口

GWT 分发版中包含一个实用程序,它将分析与CssResource兼容的 CSS 文件,并为访问文件中使用的类名创建一个相应的 Java 接口。

java -cp gwt-dev.jar:gwt-user.jar com.google.gwt.resources.css.InterfaceGenerator \
  -standalone -typeName some.package.MyCssResource -css input.css

生成的接口将被输出到System.out-standalone选项将向输出添加必要的packageimport语句,以便它可以作为构建过程的一部分使用。

CssResourceCookbook

本节包含示例,展示如何使用CssResource

浏览器特定 css

.foo {
  background: green;
}

@if user.agent ie6 {
  /* Rendering fix */
  .foo {
    position: relative;
  }
} @elif user.agent safari {
  .foo {
    \-webkit-border-radius: 4px;
  }
} @else {
  .foo {
    font-size: x-large;
  }
}

混淆的 CSS 类名

CssResource将在运行时使用方法名作为 CSS 类名进行混淆。

interface MyCss extends CssResource {
  String className();
}

interface MyResources extends ClientBundle {
  @Source("my.css")
  MyCss css();
}

当 CSS 编译时,所有带有.className选择器的实例都将被替换为混淆的符号。要使用混淆的名称

MyResources resources = GWT.create(MyResources.class);
Label l = new Label("Some text");
l.addStyleName(resources.css().className());

如果您的 css 文件中包含不是合法 Java 标识符的类名,您可以在访问器方法上使用@ClassName注释

interface MyCss extends CssResource {
  @ClassName("some-other-name")
  String someOtherName();
}

背景图像/精灵

CssResource重用ImageResource捆绑技术并将它们应用于 CSS 背景图像。这通常被称为“精灵”,CssResource中使用了一个特殊的@sprite规则。

interface MyResources extends ClientBundle {
  @Source("image.png")
  ImageResource image();

  @Source("my.css");
  CssResource css();
}

my.css中,精灵使用@sprite关键字定义,后跟任意 CSS 选择器,规则块必须包含gwt-image属性。gwt-image属性应该命名ImageResource访问器函数。

@sprite .myImage {
  gwt-image: 'image';
}

与给定选择器匹配的元素将显示命名图像,并将其高度和宽度自动设置为图像的高度和宽度。

平铺图像

如果ImageResource@ImageOptions注释修饰,则源图像可以沿 X 轴或 Y 轴平铺。这允许您使用 1 像素宽(或高)的图像来定义边框,同时仍然利用ImageResource提供的图像捆绑优化。

interface MyResources extends ClientBundle {
  @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
  @Source("image.png")
  ImageResource image();
}

@sprite的选择器匹配的元素将仅设置其高度或宽度,具体取决于图像要重复的方向。

9 盒子

为了使 9 盒子的内容区域具有正确的尺寸,必须考虑边框图像的高度和宽度。与其在 CSS 文件中硬编码图像宽度,不如使用value() CSS 函数从关联的ImageResource中插入高度或宽度。

public interface Resources extends ClientBundle {
    Resources INSTANCE = GWT.create(Resources.class);

    @Source("bt.png")
    @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
    ImageResource bottomBorder();

    @Source("btl.png")
    ImageResource bottomLeftBorder();

    @Source("btr.png")
    ImageResource bottomRightBorder();

    @Source("StyleInjectorDemo.css")
    CssResource css();

    @Source("lr.png")
    @ImageOptions(repeatStyle = RepeatStyle.Vertical)
    ImageResource leftBorder();

    @Source("rl.png")
    @ImageOptions(repeatStyle = RepeatStyle.Vertical)
    ImageResource rightBorder();

    @Source("tb.png")
    @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
    ImageResource topBorder();

    @Source("tbl.png")
    ImageResource topLeftBorder();

    @Source("tbr.png")
    ImageResource topRightBorder();
  }
.contentArea {
  padding: value('topBorder.getHeight', 'px') value('rightBorder.getWidth', 'px')
      value('bottomBorder.getHeight', 'px') value('leftBorder.getWidth', 'px');
}

@sprite .contentAreaTopLeftBorder {
  gwt-image: 'topLeftBorder';
  position: absolute;
  top:0;
  left: 0;
}

@sprite .contentAreaTopBorder {
  gwt-image: 'topBorder';
  position: absolute;
  top: 0;
  left: value('topLeftBorder.getWidth', 'px');
  right: value('topRightBorder.getWidth', 'px');
}

@sprite .contentAreaTopRightBorder {
  gwt-image: 'topRightBorder';
  position: absolute;
  top:0;
  right: 0;
}

@sprite .contentAreaBottomLeftBorder {
  gwt-image: 'bottomLeftBorder';
  position: absolute;
  bottom: 0;
  left: 0;
}

@sprite .contentAreaBottomBorder {
  gwt-image: 'bottomBorder';
  position: absolute;
  bottom: 0;
  left: value('bottomLeftBorder.getWidth', 'px');
  right: value('bottomRightBorder.getWidth', 'px');
}

@sprite .contentAreaBottomRightBorder {
  gwt-image: 'bottomRightBorder';
  position: absolute;
  bottom: 0;
  right: 0;
}

@sprite .contentAreaLeftBorder {
  gwt-image: 'leftBorder';
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
}

@sprite .contentAreaRightBorder {
  gwt-image: 'rightBorder';
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
}
<div class="contentArea">

<div class="contentAreaTopLeftBorder"></div>
<div class="contentAreaTopBorder"></div>
<div class="contentAreaTopRightBorder"></div>
<div class="contentAreaBottomLeftBorder"></div>
<div class="contentAreaBottomBorder"></div>

<div class="contentAreaBottomRightBorder"></div>
<div class="contentAreaLeftBorder"></div>
<div class="contentAreaRightBorder"></div>
</div>