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: ['src/**/*.js'], dest: 'dist/<%= pkg.name %>.js' }
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: ['src/common/*.js'], dest: 'dist/common.js' }, all: { src: ['src/**/*.js'], dest: 'dist/all.js' } }
對于包含了多個目標的配置來說,可以通過 options 屬性來配置不同目標的默認屬性值。在目標中也可以通過 options 屬性來覆寫默認值。
任務創建與執行
在 對插件進行配置之后,需要在 Gruntfile 中創建相關的任務。這些任務由 Grunt 負責執行。在加載了 Grunt 插件之后,該插件提供的任務可以被執行。也可以通過 grunt.registerTask 方法來定義新的任務,或是為已有的任務創建別名。在定義一個任務時,需要提供任務的名稱和所執行的方法。任務的描述是可選的。代碼清單4中給出了一個簡單的任務。當通過“grunt sample”運行該任務時,會在控制臺輸出相應的提示信息。
清單 4. 簡單的 Grunt 任務
grunt.registerTask('sample', 'My sample task', function() { grunt.log.writeln('This is a sample task.'); });
在定義任務時可以聲明任務運行時所需的參數,在通過 grunt 運行任務時可以指定這些參數的值。代碼清單5給 出了一個包含參數的任務的示例。任務 profile 在運行時需要提供 2 個參數 name 和 email。在通過 grunt 運行時,使用“grunt profile:alex:alex@example.org”可以把參數值“alex”和“alex@example.org”分別傳遞給參數 name 和 email。不同的參數之間通過“:”分隔。
清單 5. 包含參數的 Grunt 任務
grunt.registerTask('profile', 'Print user profile', function(name, email) { grunt.log.writeln('Name -> ' + name + '; Email -> ' + 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('asynctask', function() { var done = this.async(); setTimeout(function() { grunt.log.writeln('Done!'); 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('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-concat');
Bower
在 Web 應用開發中,一般都會使用很多第三方 JavaScript 庫,比如 jQuery 和 推ter 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 庫
<script src="/bower_components/jquery/jquery.min.js"></script>
與逐一安裝所需的庫相比,更好的方式是在 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 和 推ter Bootstrap 等。只需要一個簡單的命令,就可以生成一個能夠直接運行的 Web 應用。后續的開發可以基于生成的應用骨架來進行。這在很大程度上簡化了應用的開發工作,尤其是某些原型應用。
在生成的 Web 應用中包含了一些常用的 Grunt 任務。這些任務可以幫助快速的進行開發。這些任務包括:
-
grunt server:啟動支持 Live Reload 技術的服務器。當本地的文件有修改時,所打開的頁面會自動刷新來反映最新的改動。這免去了每次手動刷新的麻煩,使得開發過程變得更加方便快捷。
-
grunt test:運行基于 Mocha 的自動化測試。
-
grunt build:構建整個 Web 應用。其中所執行的任務包括 JavaScript 和 CSS 文件的合并、壓縮和混淆等操作,以及添加版本號等。
Yeoman
Yeoman 的重要之處在于把各種不同的工具整合起來,形成一套完整的前端開發的工作流程。使用 Yeoman 之后的開發流程可以分成如下幾個基本的步驟。
在 開發的最初階段需要確定前端的技術選型。這包括選擇要使用的前端框架。在絕大部分的 Web 應用開發中,都需要第三方庫的支持。有的應用可能只使用 jQuery,有的應用會增加 推ter 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 應用中,有助于創建高質量和可維護的應用。