隨著 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 的基本結構
1 2 3 4 5 |
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 的配置
1 2 3 4 |
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 的多目標配置
1 2 3 4 5 6 7 8 9 10 |
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 任務
1 2 3 |
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 任務
1 2 3 |
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. 使用新增別名的方式建立的任務
1 |
grunt.registerTask('default', ['jshint', 'qunit', 'concat', 'uglify']); |
大部分任務是同步執行的,也可以用非同步的方式來執行。如果任務中的某部分需要比較長的時間完成,可以通過非同步的方式來完成。程式碼清單7給出了一個非同步執行的任務的示例。通過呼叫 async 方法可以把當前任務的執行變成非同步的。呼叫 async 方法的返回值是一個 JavaScript 方法。當任務執行完成之後,呼叫該 JavaScript 方法來通知 grunt。
清單 7. 非同步執行的任務
1 2 3 4 5 6 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. 外掛載入示例
1 2 |
grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-concat'); |
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 庫
1 |
<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 檔案示例
1 2 3 4 5 6 7 8 9 10 11 |
{ "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 檔案
1 2 3 |
{ "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 應用中,有助於建立高質量和可維護的應用。