随着 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 的基本結構
在配置對象中可以包含任意的屬性值。不過重要的是執行不同任務的插件所對應的配置項。每個插件的配置項在配置對象中的屬性名稱與插件的名稱相對應。比如 grunt-contrib-concat 插件所對應的配置項屬性名稱為 concat,如代碼清單2所 示。插件 grunt-contrib-concat 的作用是把多個 javascript 檔案拼接在一起組成單個檔案。在該插件的配置項中,src 和 dest 屬性分别表示要拼接的 javascript 檔案和生成的目标檔案的名稱。其中 src 屬性的值使用通配符指定了一系列檔案,dest 屬性的值中通過 pkg.name 引用了 package.json 檔案中定義的屬性 name 的值。“<%= %>”是 grunt 提供的字元串模闆的文法格式,用來根據變量值生成字元串。
清單 2. 插件 grunt-contrib-concat 的配置
grunt 的模闆使用“<% %>”來進行表達式的分隔,同時也支援表達式的嵌套。在解析模闆中包含的内容時,整個配置對象被作為解析時的上下文。也就是說配置對象中包含的屬性 都可以直接在模闆中引用。除此之外,grunt 對象及其包含的方法也可以在模闆中使用。模闆有兩種形式:第一種是“<%= %>”,用來引用配置對象中的屬性值;第二種是“<% %>”,用來執行任意的 javascript 代碼,通常用來控制代碼執行流程。
有的插件允許同時定義多個不同的配置,稱為不同的“目标(target)”。這是因為某些任務在不同的條件下所使用的配置并不相同。對于這些不同的目标,可以在配置對象中添加相應名稱的屬性來表示。代碼清單3給 出了 grunt-contrib-concat 插件的另一種配置方式。代碼中定義了 common 和 all 兩個不同的目标。每個目标的配置并不相同。在運作任務時,通過“grunt concat:common”和“grunt concat:all”來運作不同的目标。如果沒有指定具體的目标,而是通過“grunt concat”來直接運作,則會依次執行所有的目标。
清單 3. 插件 grunt-contrib-concat 的多目标配置
對于包含了多個目标的配置來說,可以通過 options 屬性來配置不同目标的預設屬性值。在目标中也可以通過 options 屬性來覆寫預設值。
任務建立與執行
在 對插件進行配置之後,需要在 gruntfile 中建立相關的任務。這些任務由 grunt 負責執行。在加載了 grunt 插件之後,該插件提供的任務可以被執行。也可以通過 grunt.registertask 方法來定義新的任務,或是為已有的任務建立别名。在定義一個任務時,需要提供任務的名稱和所執行的方法。任務的描述是可選的。代碼清單4中給出了一個簡單的任務。當通過“grunt sample”運作該任務時,會在控制台輸出相應的提示資訊。
清單 4. 簡單的 grunt 任務
在定義任務時可以聲明任務運作時所需的參數,在通過 grunt 運作任務時可以指定這些參數的值。代碼清單5給 出了一個包含參數的任務的示例。任務 profile 在運作時需要提供 2 個參數 name 和 email。在通過 grunt 運作時,使用“grunt profile:alex:[email protected]”可以把參數值“alex”和“[email protected]”分别傳遞給參數 name 和 email。不同的參數之間通過“:”分隔。
清單 5. 包含參數的 grunt 任務
如果要定義的任務類似 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 指令時使用了“–force”參數。在任務代碼中可以通過 grunt.task.requires 方法來聲明對其他任務的依賴。如果所依賴的任務沒有成功執行,目前任務也不會被執行。當任務對應的 javascript 方法在執行時傳回 false 時,該任務被認為執行失敗。對于異步執行的任務,隻需要在調用 async 傳回的回調方法時傳入 false 參數即可。比如在代碼清單7中,可以使用“done(false);”來聲明異步任務執行失敗。
為了能夠在調用 grunt 時使用插件提供的任務,需要使用 grunt.loadnpmtasks 方法來加載插件。代碼清單8給出了加載 grunt-contrib-watch 和 grunt-contrib-concat 插件的示例。
清單 8. 插件加載示例
在 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 檔案示例
bower 本身的配置可以通過.bowerrc 檔案來完成。該檔案以 json 格式來進行配置。代碼清單11給出了.bowerrc 檔案的示例。該示例中通過 directory 定義了 bower 下載下傳庫的目錄。
清單 11. 配置 bower 的.bowerrc 檔案
當 打算開始開發一個 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