公司的产品线涵盖多个产品,这些产品中会有一些相同的功能,如登陆,认证等,为了保持这些功能在各个产品中的一致性,咱们在各个产品中维护一份相同的代码。这带来了很大的不便:当出现新的需求时,不得不一样时在多个产品中更改代码,使它们保持一致。为了解决这个问题,咱们能够将这些公共部分抽取出来放在一个单独的子项目中,其余项目只是引用该子项目,当出现新的需求时,咱们只要改变该子项目便可。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会有更好的组件化方式实现。