5.2 Sass 和 Asset Pipeline

在新版 Rails 中,最大的变化是增加了 Asset Pipeline,这个功能明显提升了 CSS、JavaScript 和图片等静态资源文件的生成效率,而且还能降低管理成本。本节我们先大致介绍一下 Asset Pipeline,然后说明如何使用强大的 CSS 编写工具 Sass。

5.2.1 Asset Pipeline

Asset Pipeline 对 Rails 做了很多改动,但对 Rails 开发者来说只有三个特性需要了解:静态资源文件夹,清单文件,以及预处理器引擎。[11]下面一一介绍。

静态资源文件夹

在 Rails 3.0 及之前的版本,静态文件放在 public/ 文件夹中,并且按照下面的方式组织:

  • public/stylesheets

  • public/javascripts

  • public/images

这些文件夹中的文件通过请求 http://example.com/stylesheets 等地址直接发送给浏览器。(Rails 3.0 之后的版本也会这么做。)

在最新版 Rails 中,静态文件可以放在三个标准文件夹中,而且各有各的用途:

  • app/assets:当前应用的资源文件;

  • lib/assets:开发团队自己开发的代码库使用的资源文件;

  • vendor/assets:第三方代码库使用的资源文件;

你可能猜到了,这几个文件夹中都有针对不同资源类型的子文件夹,例如:

$ ls app/assets/
http://railstutorial-china.org/book/images/ javascripts/ stylesheets/

现在我们知道 5.1.2 节custom.css.scss 存放位置的用意了:因为 custom.css.scss 只在应用中使用,所以把它存放在 app/assets/stylesheets 文件夹中。

清单文件

把资源文件放在适当的文件夹中之后,要通过清单文件告诉 Rails 怎么把它们合并成一个文件(通过 Sprockets gem 实现,而且只能合并 CSS 和 JavaScript 文件,不会合并图片)。举个例子,我们来看一下应用默认的样式表清单文件,如代码清单 5.14 所示。

代码清单 5.14:应用的样式表清单文件

app/assets/stylesheets/application.css

/*
 * This is a manifest file that'll be compiled into application.css, which
 * will include all the files listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets,
 * vendor/assets/stylesheets, or vendor/assets/stylesheets of plugins, if any,
 * can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear
 * at the bottom of the compiled file so the styles you add here take
 * precedence over styles defined in any styles defined in the other CSS/SCSS
 * files in this directory. It is generally better to create a new file per
 * style scope.
 *
 *= require_tree .
 *= require_self
 */

这里关键的代码是几行 CSS 注释,Sprockets 通过这些注释引入相应的文件:

/*
 .
 .
 .
 *= require_tree .
 *= require_self
*/

其中

*= require_tree .

会把 app/assets/stylesheets 文件夹中的所有 CSS 文件(包含子文件夹中的文件)都引入应用的 CSS 。

下面这行:

*= require_self

会把 application.css 这个文件中的 CSS 也加载进来。

Rails 提供的默认清单文件可以满足我们的需求,所以本书不会对其做任何修改。Rails 指南中有一篇专门介绍 Asset Pipeline 的文章,说得更详细。

预处理器引擎

准备好资源文件后,Rails 会使用一些预处理器引擎来处理它们,并通过清单文件将其合并,然后发送给浏览器。我们通过扩展名告诉 Rails 使用哪个预处理器。三个最常用的扩展名是:Sass 文件的 .scss,CoffeeScript 文件的 .coffee,ERb 文件的 .erb。我们在 3.4.3 节介绍过 ERb,5.2.2 节会介绍 Sass。本书不会使用 CoffeeScript,这是一个很小巧的语言,可以编译成 JavaScript。(RailsCast 中关于 CoffeeScript 的视频是很好的入门教程。)

预处理器引擎可以连接在一起使用,因此

foobar.js.coffee

只会使用 CoffeeScript 处理器,而

foobar.js.erb.coffee

会使用 CoffeeScript 和 ERb 处理器(按照扩展名的顺序从右向左处理,所以 CoffeeScript 处理器先执行)。

在生产环境中的效率问题

Asset Pipeline 带来的好处之一是,能自动优化资源文件,在生产环境中使用效果极佳。CSS 和 JavaScript 的传统组织方式是,把不同功能的代码放在不同的文件中,而且排版良好(有很多缩进)。这么做对编程人员很友好,但在生产环境中使用却效率低下——加载大量的文件会明显增加页面的加载时间,这是影响用户体验最主要的因素之一。使用 Asset Pipeline,生产环境中应用所有的样式都会集中到一个 CSS 文件中(application.css),所有 JavaScript 代码都会集中到一个 JavaScript 文件中(application.js),而且还会压缩这些文件,删除不必要的空格,减小文件大小。这样我们就最好地平衡了两方面的需求,开发方便,线上高效。

5.2.2 句法强大的样式表

Sass 是一种编写 CSS 的语言,从多方面增强了 CSS 的功能。本节我们要介绍两个最主要的功能:嵌套和变量。(还有一个功能是“混入”,7.1.1 节再介绍。)

5.1.2 节说过,Sass 支持一种名为 SCSS 的格式(扩展名为 .scss),这是 CSS 句法的一个扩展集。SCSS 只为 CSS 添加了一些功能,而没有定义全新的句法。[12]也就是说,所有有效的 CSS 文件都是有效的 SCSS 文件,这对已经定义了样式的项目来说是件好事。在我们的应用中,因为要使用 Bootstrap,所以从一开始就使用了 SCSS。Rails 的 Asset Pipeline 会自动使用 Sass 预处理器处理扩展名为 .scss 的文件,所以 custom.css.scss 文件会首先经由 Sass 预处理器处理,然后引入应用的样式表中,再发送给浏览器。

嵌套

样式表中经常会定义嵌套元素的样式,例如,在代码清单 5.5 中,定义了 .center.center h1 两个样式:

.center {
  text-align: center;
}

.center h1 {
  margin-bottom: 10px;
}

使用 Sass 可将其改写成

.center {
  text-align: center;
 h1 {    margin-bottom: 10px;
  }
}

内层的 h1 会自动放入 .center 上下文中。

嵌套还有一种形式,句法稍有不同。在代码清单 5.7 中,有如下的代码

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: #fff;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
}

#logo:hover {
  color: #fff;
  text-decoration: none;
}

其中 LOGO 的 ID #logo 出现了两次,一次单独出现,另一次和 hover 伪类一起出现(鼠标悬停其上时的样式)。如果要嵌套第二组规则,我们要引用父级元素 #logo,在 SCSS 中,使用 & 符号实现:

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: #fff;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
 &:hover {    color: #fff;
    text-decoration: none;
  }
}

把 SCSS 转换成 CSS 时,Sass 会把 &:hover 编译成 #logo:hover

这两种嵌套方式都可以用在代码清单 5.13 中的底部样式上,改写成:

footer {
  margin-top: 45px;
  padding-top: 5px;
  border-top: 1px solid #eaeaea;
  color: #777;
  a {
    color: #555;
    &:hover {
      color: #222;
    }
  }
  small {
    float: left;
  }
  ul {
    float: right;
    list-style: none;
    li {
      float: left;
      margin-left: 15px;
    }
  }
}

自己动手改写代码清单 5.13 是个不错的练习,改完后应该验证一下 CSS 是否还能正常使用。

变量

Sass 允许我们自定义变量来避免重复,这样也可以写出更具表现力的代码。例如,代码清单 5.6代码清单 5.13 中都重复使用了同一个颜色代码:

h2 {
  .
  .
  .
 color: #777; }
.
.
.
footer {
  .
  .
  .
 color: #777; }

上面代码中的 #777 是淡灰色,我们可以把它定义成一个变量:

$light-gray: #777;

然后我们可以这样写 SCSS:

$light-gray: #777;
.
.
.
h2 {
  .
  .
  .
  color: $light-gray;
}
.
.
.
footer {
  .
  .
  .
  color: $light-gray;
}

因为像 $light-gray 这样的变量名比 #777 意思更明确,所以把不重复使用的值定义成变量往往也是很有用的。其实,Bootstrap 框架定义了很多颜色变量,Bootstrap 文档中有这些变量的 Less 形式。这个页面中的变量使用 Less 句法,而不是 Sass,不过 bootstrap-sass gem 为我们提供了对应的 Sass 形式。二者之间的对应关系也不难猜测,Less 使用 @ 符号定义变量,而 Sass 使用 $ 符号。在 Bootstrap 文档中我们看到已经为淡灰色定义了变量:

@gray-light: #777;

也就是说,在 bootstrap-sass gem 中有一个对应的 SCSS 变量 $gray-light。我们可以用它换掉自己定义的 $light-gray 变量:

h2 {
  .
  .
  .
  color: $gray-light;
}
.
.
.
footer {
  .
  .
  .
  color: $gray-light;
}

使用 Sass 提供的嵌套和变量功能改写应用的整个样式表后得到的代码如代码清单 5.15 所示。这段代码使用了 Sass 变量(参照 Bootstrap Less 变量页面)和内置的颜色名称(例如,white 代表 #fff)。特别注意一下 footer 标签样式的明显改进。

代码清单 5.15:使用嵌套和变量改写后的 SCSS 文件

app/assets/stylesheets/custom.css.scss

@import "bootstrap-sprockets";
@import "bootstrap";

/* mixins, variables, etc. */

$gray-medium-light: #eaeaea;

/* universal */

body {
  padding-top: 60px;
}

section {
  overflow: auto;
}

textarea {
  resize: vertical;
}

.center {
  text-align: center;
  h1 {
    margin-bottom: 10px;
  }
}

/* typography */

h1, h2, h3, h4, h5, h6 {
  line-height: 1;
}

h1 {
  font-size: 3em;
  letter-spacing: -2px;
  margin-bottom: 30px;
  text-align: center;
}

h2 {
  font-size: 1.2em;
  letter-spacing: -1px;
  margin-bottom: 30px;
  text-align: center;
  font-weight: normal;
  color: $gray-light;
}

p {
  font-size: 1.1em;
  line-height: 1.7em;
}

/* header */

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: white;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
  &:hover {
    color: white;
    text-decoration: none;
  }
}

/* footer */

footer {
  margin-top: 45px;
  padding-top: 5px;
  border-top: 1px solid $gray-medium-light;
  color: $gray-light;
  a {
    color: $gray;
    &:hover {
      color: $gray-darker;
    }
  }
  small {
    float: left;
  }
  ul {
    float: right;
    list-style: none;
    li {
      float: left;
      margin-left: 15px;
    }
  }
}

Sass 提供了很多简化样式表的功能,代码清单 5.15 只用到了最主要的功能,这是个好的开始。更多功能请查看 Sass 的网站