背景
公司的产品线涵盖多个产品,这些产品中会有一些相同的功能,如登录,认证等,为了保持这些功能在各个产品中的一致性,我们在各个产品中维护一份相同的代码。这带来了很大的不便:当出现新的需求时,不得不同时在多个产品中更改代码,使它们保持一致。为了解决这个问题,我们可以将这些公共部分抽取出来放在一个单独的子项目中,其他项目只是引用该子项目,当出现新的需求时,我们只要改变该子项目即可。
在这个思路的基础上,有两个问题需要解决:
以何种方式维护子项目
如何维护产品对子项目的引用
我们先从一个正常的angular项目说起。例如,如果你想在你的项目中引入Angular UI Bootstrap组件,通常你会怎么做?
-
在bower.json中添加所需要的dependencies:
{ "name": "your project","version": "0.0.1","dependencies": { "angularJS": "1.4.x","angular-animate": "1.4.x",.... } }
-
使用bower install命令,安装dependencies,并引用:
<script src="../bower_components/angular.js" type="text/javascript"></script>
-
在你的项目中声明dependencies:
angular.module('myModule',['ui.bootstrap']);
通过以上的例子我们可以得到一定的启示:
子项目最终应该以angular module的形式出现
使用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: 源代码目录
example: 事例代码目录,具体的事例代码目录下包含index.html文件,在该html文件中引入release版本的js和css,然后启动http-server命令打开本地服务器进行测试
gulp: gulp task目录
gulpfile.js: 用于执行gulp目录中的各种task
karma.js: karma配置文件
package.json: 配置文件
gulp中应包含以下task:
test: 执行lib下的所有测试文件
PS: 由于历史遗留问题,angular中component和directive之间的界限模糊不清。指令应只封装DOM操作,而组件代表一个自给自足的独立单元 - 有自己的视图和数据逻辑。在angular1.5中增加了component的概念,我们应该更加清晰的区别component和directive,在使用时directive只应该执行封装DOM的操作,而不应该去创造DOM节点,也就是说directive中的restrict应设置为A。
bower管理引用
模块版本发布应遵循semver(语义化版本)原则。
版本格式为:主版本号.次版本号.修订号(MAJOR.MINOR.PATCH)。版本号递增规则如下:
通常我们希望我们开发出的模块只是对企业内部可见,对外部不可见。这就要求我们不能使用平常的方式去使用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会有更好的组件化方式实现。
原文链接:/angularjs/148664.html