Yeoman:Web 应用开发流程与工具

随着 Web 2.0 和 HTML 5 的流行,现在的 Web 应用所能提供的功能和交互能力比之前传统的 Web 应用要强大很多。应用的很多实现逻辑被转移到了浏览器端来实现。浏览器不再只提供单一的数据接收和展现功能,而是提供更多的用户交互能力。浏览器端所包含 的 HTML、CSS 和 JavaScript 代码也变得更加复杂。对于日益复杂的前端代码,需要有更好的流程和工具来管理开发的各个方面,包括初始的代码结构、开发流程和自动化测试等。Yeoman 是一个新兴的工具。它结合了 Yo、Grunt 和 Bower 等工具,组成了一个完整的工具集合,提供各种 Web 应用开发中所需的实用功能。

Yeoman 的最大优势在于它整合了各种流行的实用工具,提供了一站式的解决方案,使得 Web 应用开发中的很多方面变得简单。Yeoman 使得开发人员可以专注于应用本身的实现,而不用在搭建应用的基础结构、进行任务构建和其他辅助任务上花费过多的时间和精力。Yeoman 同时也把一些好的最佳实践自动地引入到项目的开发中。比如当需要在应用中使用第三方的 JavaScript 库时,一般的做法是直接到库的网站上进行下载。而 Yeoman 中基于 Bower 进行依赖管理的做法则是更好的实践方式。

Yeoman 的功能由其所包含的工具来实现。下面分别介绍 Yeoman 中包含的 Yo、Grunt 和 Bower 等工具。

Grunt

Grunt 是一个 JavaScript 任务执行工具,其核心理念是自动化。在 Web 应用开发过程中,会有很多不同的任务需要执行。这些任务与 Web 应用开发中的不同类型的组件和所处的阶段相关。比如对 JavaScript 来说,在开发阶段会需要使用 JSLint 和 JSHint 这样的工具来检查 JavaScript 代码的质量;在构建阶段,从前端性能的角度出发,会需要把多个 JavaScript 文件在合并之后进行压缩。对于 CSS 文件也有类似的任务需要执行。其他的任务还包括压缩图片、合并压缩和混淆 JavaScript 代码以及运行自动化单元测试用例等。所有这些任务都需要进行相应的配置,并通过对应的方式来运行。不同任务的运行方式并不相同,取决于任务本身使用的技 术。比如一些与 JavaScript 相关的任务,如 JSLint 和 JSHint,通过 JavaScript 引擎来运行。对于一般的基于 Java 平台的 Web 应用,如果需要执行 JSLint 任务,需要使用 Rhino 这样的引擎来执行 JavaScript 代码,同时与 Apache Ant、Maven 或 Gradle 这样的构建工具进行集成。这种方式的问题在于不同的任务的配置方式都不相同,并且需要与已有的构建系统进行集成。开发人员需要查询很多的文档才能知道如何 配置并使用这些任务。

Grunt 基于流行的 NodeJS 平台来运行。所有的任务执行都基于统一的平台。Grunt 的优势在于集成了非常多的任务插件。这些插件有些是 Grunt 团队开发的,更多的是由社区贡献的。这些插件使用 NodeJS 标准的模块机制来分发,只需要使用 npm 就可以进行管理。Web 应用只需要通过一个文件来声明所要执行的任务并进行相应的配置,Grunt 会负责任务的运行。通过这种方式,所有任务的配置都在一个文件中管理。

Grunt 的安装过程很简单。只需要运行“npm install -g grunt-cli”命令就可以安装。在安装 Yeoman 时,Grunt 就已经作为一部分被自动安装了。对于一个应用来说,使用 Grunt 需要两个文件。一个是 npm 使用的 package.json。该文件中包含了应用的相关元数据。在该文件中需要通过 devDependencies 来声明对 Grunt 及其他插件的依赖。另外一个文件是 Gruntfile,可以是一个 JavaScript 或 CoffeeScript 文件。该文件的作用是配置应用中所需要执行的任务。在 package.json 文件中声明依赖并安装 Grunt 插件之后,就可以在 Gruntfile 中配置并加载这些任务。通过 grunt 命令可以运行这些任务。不同任务的配置方式相对类似,只是所提供的配置项并不相同。

任务配置

Gruntfile 中的相关配置都包含在一个 JavaScript 方法中。在这个方法中,通过 grunt.initConfig 方法可以对使用的插件进行配置。由于在 Gruntfile 文件中进行配置时,通常会需要使用 package.json 文件中的某些值,一般的做法是把 package.json 的内容读入到某个属性中,方便在代码的其他部分中使用。代码清单1给出了 Gruntfile 的基本结构。调用 initConfig 方法的参数对象中的 pkg 属性表示的是 package.json 的内容。

清单 1. Gruntfile 的基本结构

module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json')
  });
};

在配置对象中可以包含任意的属性值。不过重要的是执行不同任务的插件所对应的配置项。每个插件的配置项在配置对象中的属性名称与插件的名称相对应。比如 grunt-contrib-concat 插件所对应的配置项属性名称为 concat,如代码清单2所 示。插件 grunt-contrib-concat 的作用是把多个 JavaScript 文件拼接在一起组成单个文件。在该插件的配置项中,src 和 dest 属性分别表示要拼接的 JavaScript 文件和生成的目标文件的名称。其中 src 属性的值使用通配符指定了一系列文件,dest 属性的值中通过 pkg.name 引用了 package.json 文件中定义的属性 name 的值。“<%= %>”是 Grunt 提供的字符串模板的语法格式,用来根据变量值生成字符串。

清单 2. 插件 grunt-contrib-concat 的配置

concat: {
  src: [&#039;src/**/*.js&#039;],
  dest: &#039;dist/<%= pkg.name %>.js&#039;
}

Grunt 的模板使用“<% %>”来进行表达式的分隔,同时也支持表达式的嵌套。在解析模板中包含的内容时,整个配置对象被作为解析时的上下文。也就是说配置对象中包含的属性 都可以直接在模板中引用。除此之外,grunt 对象及其包含的方法也可以在模板中使用。模板有两种形式:第一种是“<%= %>”,用来引用配置对象中的属性值;第二种是“<% %>”,用来执行任意的 JavaScript 代码,通常用来控制代码执行流程。

有的插件允许同时定义多个不同的配置,称为不同的“目标(target)”。这是因为某些任务在不同的条件下所使用的配置并不相同。对于这些不同的目标,可以在配置对象中添加相应名称的属性来表示。代码清单3给 出了 grunt-contrib-concat 插件的另一种配置方式。代码中定义了 common 和 all 两个不同的目标。每个目标的配置并不相同。在运行任务时,通过“grunt concat:common”和“grunt concat:all”来运行不同的目标。如果没有指定具体的目标,而是通过“grunt concat”来直接运行,则会依次执行所有的目标。

清单 3. 插件 grunt-contrib-concat 的多目标配置

concat: {
  common: {
    src: [&#039;src/common/*.js&#039;],
    dest: &#039;dist/common.js&#039;
  },
  all: {
    src: [&#039;src/**/*.js&#039;],
    dest: &#039;dist/all.js&#039;
  }
}

对于包含了多个目标的配置来说,可以通过 options 属性来配置不同目标的默认属性值。在目标中也可以通过 options 属性来覆写默认值。

任务创建与执行

在 对插件进行配置之后,需要在 Gruntfile 中创建相关的任务。这些任务由 Grunt 负责执行。在加载了 Grunt 插件之后,该插件提供的任务可以被执行。也可以通过 grunt.registerTask 方法来定义新的任务,或是为已有的任务创建别名。在定义一个任务时,需要提供任务的名称和所执行的方法。任务的描述是可选的。代码清单4中给出了一个简单的任务。当通过“grunt sample”运行该任务时,会在控制台输出相应的提示信息。

清单 4. 简单的 Grunt 任务

grunt.registerTask(&#039;sample&#039;, &#039;My sample task&#039;, function() {
  grunt.log.writeln(&#039;This is a sample task.&#039;);
});

在定义任务时可以声明任务运行时所需的参数,在通过 grunt 运行任务时可以指定这些参数的值。代码清单5给 出了一个包含参数的任务的示例。任务 profile 在运行时需要提供 2 个参数 name 和 email。在通过 grunt 运行时,使用“grunt profile:alex:alex@example.org”可以把参数值“alex”和“alex@example.org”分别传递给参数 name 和 email。不同的参数之间通过“:”分隔。

清单 5. 包含参数的 Grunt 任务

grunt.registerTask(&#039;profile&#039;, &#039;Print user profile&#039;, function(name, email) {
  grunt.log.writeln(&#039;Name -> &#039; + name + &#039;; Email -> &#039; + email);
});

如果要定义的任务类似 grunt-contrib-concat 插件可以支持多个不同的目标,只需要使用 grunt.registerMultiTask 方法来进行定义即可。

除了定义新的任务之外,还可以通过为已有的任务添加别名的方式来创建新的任务。代码清单6给出了一个示例。名为 default 的任务在执行时,会依次执行 jshint、qunit、concat 和 uglify 等任务。当运行 grunt 命令时,如果没有指定任务名称,会尝试运行名为 default 的任务。

清单 6. 使用添加别名的方式创建的任务

grunt.registerTask('default', ['jshint', 'qunit', 'concat', 'uglify']);
大部分任务是同步执行的,也可以用异步的方式来执行。如果任务中的某部分需要比较长的时间完成,可以通过异步的方式来完成。代码清单7给出了一个异步执行的任务的示例。通过调用 async 方法可以把当前任务的执行变成异步的。调用 async 方法的返回值是一个 JavaScript 方法。当任务执行完成之后,调用该 JavaScript 方法来通知 grunt。

清单 7. 异步执行的任务

grunt.registerTask(&#039;asynctask&#039;, function() {
  var done = this.async();
  setTimeout(function() {
    grunt.log.writeln(&#039;Done!&#039;);
    done();
  }, 1000);
});

一个任务可以依赖其他任务的成功执行。当某个任务执行失败之后,剩下的其他任务不会被执行,除非在执行 grunt 命令时使用了“–force”参数。在任务代码中可以通过 grunt.task.requires 方法来声明对其他任务的依赖。如果所依赖的任务没有成功执行,当前任务也不会被执行。当任务对应的 JavaScript 方法在执行时返回 false 时,该任务被认为执行失败。对于异步执行的任务,只需要在调用 async 返回的回调方法时传入 false 参数即可。比如在代码清单7中,可以使用“done(false);”来声明异步任务执行失败。

为了能够在调用 grunt 时使用插件提供的任务,需要使用 grunt.loadNpmTasks 方法来加载插件。代码清单8给出了加载 grunt-contrib-watch 和 grunt-contrib-concat 插件的示例。

清单 8. 插件加载示例

grunt.loadNpmTasks(&#039;grunt-contrib-watch&#039;);
grunt.loadNpmTasks(&#039;grunt-contrib-concat&#039;);
Bower

在 Web 应用开发中,一般都会使用很多第三方 JavaScript 库,比如 jQuery 和 Twitter BootStrap 这样的常见库。传统的做法是从这些库的网站上直接下载所需版本的 JavaScript 库文件,放到 Web 应用的某个目录中,然后在 HTML 页面中引用。这种做法的问题在于引入了很多额外的工作量,包括查找所需的 JavaScript 库文件、下载和管理等。一些 JavaScript 库有很多个版本,也依赖于其他 JavaScript 库。对于给定版本的某个 JavaScript 库,需要找到它所依赖的兼容版本的其他 JavaScript 库。这可能是一个递归的过程,会花费很多的时间。Bower 是一个前端库管理工具,可以很好的解决在 Web 应用中引用第三方库时可能遇到的问题。Bower 所提供的功能类似于 Java 开发中会用到的 Apache Ivy、Apache Maven 或 Gradle 等工具。

Bower 也是基于 NodeJS 开发的。只需要使用“npm install -g bower”命令即可安装。Yeoman 在安装时也包含了 Bower。在安装完 Bower 之后,就可以在命令行使用 bower 命令来管理所需的库。通过“bower help”可以查看 Bower 命令行所支持的操作。一般的做法是首先通过“bower search”命令来搜索需要使用的库。如“bower search jquery”可以用来搜索名称中包含 jquery 的库。当找到所需的库之后,可以通过“bower install”命令来安装。如“bower install jquery-ui”可以用来安装 jQuery UI 库。在安装时可以指定库的版本,如“bower install jquery-ui#1.9.2”可以安装 jQuery UI 的 1.9.2 版本。在使用名称来安装库时,要求该库已经注册到 Bower。Bower 也支持从远程或本地 git 仓库和本地文件中安装库。Bower 会把下载的库文件放在 bower_components 目录中。当库有更新时,通过“bower update”命令来进行更新。当不需要一个库时,通过“bower uninstall”命令来删除。使用“bower list”命令可以列出来当前应用中已经安装的库的信息。

在通过 Bower 安装库之后,可以直接在 HTML 页面中引用,如代码清单 9所示。这要求 Bower 的下载目录是可以公开访问的。

清单 9. HTML 页面中引入 Bower 管理的 JavaScript 库


与逐一安装所需的库相比,更好的方式是在 bower.json 文件中定义所依赖的库,然后运行“bower install”命令来安装所有的这些库。bower.json 文件的作用类似于 NodeJS 中 package.json。可以直接创建该文件,也可以通过“bower init”命令来以交互式的方式创建。代码清单10给出了 bower.json 文件的示例。使用 dependencies 来声明所依赖的库及其版本。有了 bower.json 文件之后,只需要运行一次“bower install”命令就可以安装所需的全部库。

清单 10. bower.json 文件示例

{
  "name": "yeoman-sample",
  "version": "0.1.0",
  "dependencies": {
    "sass-bootstrap": "~3.0.0",
    "requirejs": "~2.1.8",
    "modernizr": "~2.6.2",
    "jquery": "~1.10.2"
  },
  "devDependencies": {}
}

Bower 本身的配置可以通过.bowerrc 文件来完成。该文件以 JSON 格式来进行配置。代码清单11给出了.bowerrc 文件的示例。该示例中通过 directory 定义了 Bower 下载库的目录。

清单 11. 配置 Bower 的.bowerrc 文件

{
    "directory": "app/bower_components"
}
Yo

当 打算开始开发一个 Web 应用时,初始的目录结构和基础文件很重要,因为这些是应用开发的基础。有些开发人员选择从零开始进行,或是复制已有的应用。更好的选择是基于已有的模板。 很多 Web 应用程序使用 HTML5 Boilerplate 这样的模板来生成初始的代码结构。这样做的好处是可以直接复用已有的最佳实践,避免很多潜在的问题,为以后的开发打下一个良好的基础。Yo 是一个 Web 应用的架构(scaffolding)工具。它提供了非常多的模板,用来生成不同类型的 Web 应用。这些模板称为生成器(generator)。社区也贡献了非常多的生成器,适应于各种不同的场景。通过 Yo 生成的应用使用 Grunt 来进行构建,使用 Bower 进行依赖管理。

以基本的 Web 应用生成器为例,只需要使用“yo webapp”命令就可以生成一个基本的 Web 应用的骨架。运行该命令之后,会有一些提示信息来对生成的应用进行基本的配置,可以选择需要包含的功能。默认生成的 Web 应用中包含了 HTML5 Boilerplate、jQuery、Modernizr 和 Twitter Bootstrap 等。只需要一个简单的命令,就可以生成一个能够直接运行的 Web 应用。后续的开发可以基于生成的应用骨架来进行。这在很大程度上简化了应用的开发工作,尤其是某些原型应用。

在生成的 Web 应用中包含了一些常用的 Grunt 任务。这些任务可以帮助快速的进行开发。这些任务包括:

  • grunt server:启动支持 Live Reload 技术的服务器。当本地的文件有修改时,所打开的页面会自动刷新来反映最新的改动。这免去了每次手动刷新的麻烦,使得开发过程变得更加方便快捷。
  • grunt test:运行基于 Mocha 的自动化测试。
  • grunt build:构建整个 Web 应用。其中所执行的任务包括 JavaScript 和 CSS 文件的合并、压缩和混淆等操作,以及添加版本号等。

Yeoman

Yeoman 的重要之处在于把各种不同的工具整合起来,形成一套完整的前端开发的工作流程。使用 Yeoman 之后的开发流程可以分成如下几个基本的步骤。

在 开发的最初阶段需要确定前端的技术选型。这包括选择要使用的前端框架。在绝大部分的 Web 应用开发中,都需要第三方库的支持。有的应用可能只使用 jQuery,有的应用会增加 Twitter Bootstrap 库,而有的应用则会使用 AngularJS。在确定了技术选型之后,就可以在 Yeoman 中查找相应的生成器插件。一般来说,基于常见库的生成器都可以在社区中找到。比如使用 AngularJS、Backbone、Ember 和 Knockout 等框架的生成器。

所有的生成器都使用 npm 来进行安装。生成器的名称都使用“generator-”作为前缀,如“generator-angular”、“generator- backbone”和“generator-ember”等。安装完成之后,通过 yo 命令就可以生成应用的骨架代码,如“yo angular”用来生成基于 AngularJS 的应用骨架。

生成的应用骨架中包含了一个可以运行的基本应用。只需要通过“grunt server”命令来启动服务器就可以查看。应用的骨架搭建完成之后,把生成的代码保存到源代码仓库中。团队可以在这个基础上进行开发。开发中的一些常用 任务可以通过 Yeoman 来简化。当需要引入第三方库时,通过 Bower 来搜索并添加。

小结

面 对复杂的 Web 应用的开发,良好的流程和工具支持是必不可少的,可以让日常的开发工作更加顺畅。Yeoman 作为一个流行的工具集,在整合了 Yo、Grunt 和 Bower 等工具的基础上,定义了一个更加完备和清晰的工作流程。通过把一些最佳实践引入到 Web 应用中,有助于创建高质量和可维护的应用。

原文出处: IBM DeveloperWorks

文章转载自 开源中国社区 [http://www.oschina.net]

上一篇:ubuntu 安装软件


下一篇:【开箱即用的模板使用系列教程③】用这个模板快速统筹企业车辆安排