公司的產品線涵蓋多個產品,這些產品中會有一些相同的功能,如登陸,認證等,爲了保持這些功能在各個產品中的一致性,咱們在各個產品中維護一份相同的代碼。這帶來了很大的不便:當出現新的需求時,不得不一樣時在多個產品中更改代碼,使它們保持一致。爲了解決這個問題,咱們能夠將這些公共部分抽取出來放在一個單獨的子項目中,其餘項目只是引用該子項目,當出現新的需求時,咱們只要改變該子項目便可。javascript
在這個思路的基礎上,有兩個問題須要解決:css
以何種方式維護子項目html
如何維護產品對子項目的引用前端
咱們先從一個正常的angular項目提及。例如,若是你想在你的項目中引入Angular UI Bootstrap組件,一般你會怎麼作?java
在bower.json中添加所須要的dependencies:git
{ "name": "your project", "version": "0.0.1", "dependencies": { "angularJS": "1.4.x", "angular-animate": "1.4.x", .... } }
使用bower install命令,安裝dependencies,並引用:angularjs
<script src="../bower_components/angular.js" type="text/javascript"></script>
在你的項目中聲明dependencies:github
angular.module('myModule', ['ui.bootstrap']);
經過以上的例子咱們能夠獲得必定的啓示:json
子項目最終應該以angular module的形式出現gulp
使用bower去維護對包的引用
可配置,當使用該模塊時能夠對該模塊傳遞參數
可構建,將分離的多個文件構建成一個文件
可測試,保證模塊的魯棒性
可發佈,供其餘項目引用
包含完整的事例代碼,供其餘人蔘考
咱們在代碼層面上能夠經過provider來實現。例如:
angular.module('myModule') .provider('myProvider', function() { var name = null; // setName can be called duaring module init this.setName = function (newName) { name = newName; }; return { handleName : function() { // do something with name } }; })
在另外一個module中引入該module時,咱們能夠改變該module中name的值:
angular.module('anotherModule', ['myModule']) .config(function(myProviderProvider){ myProviderProvider.setName('name'); });
provider是模塊之間交流的橋樑,它可使模塊達到可配置。
在angular中,一個模塊的本質就是一個命名空間,在該命名空間中咱們能夠增長provider, directive, factory, constant等,它實際上就是一個功能的集合。聲明形式一般以下:
angular.module('myModule', [dependencies]) .directive('myDirective', ...) .factory('myFactory', ...); ...
directive是angular中最重要的概念,它是angular 1.x中實現組件化的基礎。factory一般是爲了完成一些輔助功能,如與後端進行數據交互或提供一些util方法等。可是爲了代碼的可維護性,一般它們會將它們放在一個單獨的文件中, 如:
file1.js爲模塊的聲明文件:
angular.module('myModule', [dependencies]);
file2.js爲一個directive聲明文件:
angular.module('myModule') .directive('mydirective', function() { ... });
file2.js爲一個factory的聲明文件:
angular.module('myModule') .factory('myfactory', function() { ... });
可是在發佈版本中,咱們但願全部的這些文件合併在同一個js文件中,這就是構建的過程。咱們可使用gulp構建工具實現該目的。directive中有時會包含模板html文件,咱們將html文件經過angular的$templateCache服務也打包進js中。
一般前端的測試分爲兩種: 單元測試和集成測試(又叫作E2E測試)。單元測試的目的是爲了測試一個接口或者功能是否能獲得預期的結果,測試對象一般爲一個函數,可是前端最大的問題就是瀏覽器的兼容問題,可能在一個瀏覽器中能跑的代碼在另外一個瀏覽器中出現錯誤,因此咱們須要在多個瀏覽器中去進行測試,咱們可使用gulp搭配測試框架karma去簡單的完成在多瀏覽器下的單元測試。集成測試是站在用戶的角度上去執行各類操做,看產品是否穩定等。因爲該模塊中只是出現一些簡單的UI組件,並不是一個完整的產品,因此並無作相關的集成測試。
這一部分會在下一章bower管理對子項目引用中詳細說明。
因爲模塊會在多個項目中被不一樣的人使用,對於這些人最快熟悉該模塊的方法就是經過一些demo去了解,因此每一個模塊中應該包含必定的事例代碼供模塊的使用者參考。
目錄結構以下:
javascript-modules - module1 - lib - myproject.module.js - component - mydirective.directive.js - template.html - templateStyle.scss - myfactory.factory.js - myprovider.provider.js - ....spec.js ... - release - myproject.bundle.js - myproject.bundle.css - example - example1 - example2 - gulp - task1.js - task2.js ... - gulpfile.js - karma.js - package.json
說明:
lib: 源代碼目錄
release: 發佈版本目錄(只包括js和css文件)
example: 事例代碼目錄,具體的事例代碼目錄下包含index.html文件,在該html文件中引入release版本的js和css,而後啓動http-server命令打開本地服務器進行測試
gulp: gulp task目錄
gulpfile.js: 用於執行gulp目錄中的各類task
karma.js: karma配置文件
package.json: 配置文件
gulp中應包含如下task:
build: 合併全部的html文件到$templateCache中,合併全部的js文件,將scss等編譯成css
test: 執行lib下的全部測試文件
release: 將打包後的最終代碼上傳到內部包管理服務器等
PS: 因爲歷史遺留問題,angular中component和directive之間的界限模糊不清。指令應只封裝DOM操做,而組件表明一個自給自足的獨立單元 - 有本身的視圖和數據邏輯。在angular1.5中增長了component的概念,咱們應該更加清晰的區別component和directive,在使用時directive只應該執行封裝DOM的操做,而不該該去創造DOM節點,也就是說directive中的restrict應設置爲A。
模塊版本發佈應遵循semver(語義化版本)原則。
版本格式爲:主版本號.次版本號.修訂號(MAJOR.MINOR.PATCH)。版本號遞增規則以下:
主版本號:當你作了不兼容的API修改,
次版本號:當你作了向下兼容的功能性新增,
修訂號:當你作了向下兼容的問題修正。
版本號的管理應該包含在gulp release任務中。
一般咱們但願咱們開發出的模塊只是對企業內部可見,對外部不可見。這就要求咱們不能使用日常的方式去使用bower進行包的發佈和依賴管理。在借鑑了後端的一些包管理思路後,咱們將該包發佈在企業內部的私有包管理服務器上,而後在bower.json中經過如下方式來引入包:
{ "module":"address/module-version.zip" }
每次當有新的版本的包發佈時,咱們只須要在bower.json中改變version號,而後使用bower install從新安裝新版本的包便可達到更新包的目的。
本文總結了angular 1.x多項目共享子項目工程化方面的一些實踐,並不涉及到複雜的代碼,主要涉及到angular module的概念,使用bower進行包管理,使用gulp做爲自動化工具等工程化的知識。上述方法也存在必定的問題,每次版本更新時,都要在引用它的各個項目中更新版本號並使用bower install從新安裝該模塊。一種更好的思路是使用git submodule/subtree,因爲並無在這方面的實踐經驗,因此再也不贅述。本文主要是針對angular1.x版本的實踐。因爲當前angular2已經發布,它提供了強大的組件功能,因此針對angular2會有更好的組件化方式實現。