背景

公司的产品线涵盖多个产品,这些产品中会有一些相同的功能,如登录,认证等,为了保持这些功能在各个产品中的一致性,我们在各个产品中维护一份相同的代码。这带来了很大的不便:当出现新的需求时,不得不同时在多个产品中更改代码,使它们保持一致。为了解决这个问题,我们可以将这些公共部分抽取出来放在一个单独的子项目中,其他项目只是引用该子项目,当出现新的需求时,我们只要改变该子项目即可。

在这个思路的基础上,有两个问题需要解决:

  • 以何种方式维护子项目

  • 如何维护产品对子项目的引用

我们先从一个正常的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: 源代码目录

  • 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。

bower管理引用

模块版本发布应遵循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会有更好的组件化方式实现。

angular 1.x多项目共享子项目实践之路的更多相关文章

  1. H5混合开发app如何升级的方法

    本篇文章主要介绍了H5混合开发app如何升级的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. ios – 比较两个版本号

    如何比较两个版本号字符串?例如:3.1.1和3.1.2.5.4现在我需要找出3.1.2.5.4是否高于3.1.1但我不知道如何做到这一点.有谁能够帮我?

  3. iOS App版本编号

    iTunesConnect开发人员指南PDF(第47页),关于iOS应用程序的版本号,请参阅以下内容:Usetypicalsoftwareversioningconventions(suchas“1.0″or“1.0.1”or“1.1”)没有关于应用版本号的确切格式的声明.我们可以使用版本号,例如“1.0.0.1”或“1.0.0.0.1”吗?解决方法我的建议是坚持使用1.x.x格式作为应用商店用途

  4. swift获取屏幕的宽高

    之前写了一篇关于获取iphone屏幕宽高的方法,算是能解决ios7下的一个小bug,是用OC写的,文章地址:http://blog.csdn.net/wingsofpiano/article/details/45726729这次用swift语言试着写了一个相同的方法,同样,粘贴到viewcontroller就能用使用示例:如果有什么错误的地方还望各位前辈能指出来

  5. swift中获取判断设备版本的方法

    1.直接获取,按需要加if判断即可2.获取每一位的版本号,利用switch进行判断3.获取版本号后,定义一个返回比较结果的对象进行比较

  6. Swift获取APP版本号,提示用户升级

    获取当前版本号获取历史的版本号??

  7. swift 获取app版本号及 ios 版本号

    =infoDictionary!["CFBundledisplayName"]letmajorVersion:AnyObject?=infoDictionary!["CFBundleShortVersionString"]letminorVersion:AnyObject?=infoDictionary!

  8. Swift开发:Info.plist文件的常见配置

    如果使用文本编辑器打开这个文件,会发现这是一个XML格式的文本文件,使用Xcode的OpenAs->SourceCode或者PropertyList可以进行编辑,本文会重点介绍一些在iOS开发中常见的的Info.plist的配置项。Info.plist配置项说明1、设置启动图标2、设置启动闪屏图片3、设置版本号相关设置Bundle的版本号。

  9. swift – 不能调用非函数类型’CIImage?’的值?

    用Xcode游乐场测试7.1.1.和Swift2.1这是因为,一旦进入UIImage,CIImage一词被视为UIImage的CIImage财产,由于隐含的自我作为消息收件人–换句话说,Swift将你的CIImage变成self.CIImage,从那里下降.您可以通过Swift使用模块命名空间来消除歧义:编辑在Swift3中,这个问题会消失,因为所有的属性将以小写字母开始.该属性将被命名为ciImage,并且不会与CIImage类混淆.

  10. 单元测试 – UIApplication.sharedApplication().委托作为AppDelegate在swift单元测试中使用它导致EXC_BAD_ACCESS

    我正在尝试在swift中使用单元测试来测试一些真正的应用程序行为.当我尝试从我的测试函数将deUIApplicationDelegate投射到我的AppDelegate时,我得到了EXC_BAD_ACCESS异常.测试代码下方:AppDelegate类设置为public,因此从访问级别来看不是问题.在同一个测试目标中使用objective-c可行.简单说明下面:debuger说someDelega

随机推荐

  1. Angular2 innerHtml删除样式

    我正在使用innerHtml并在我的cms中设置html,响应似乎没问题,如果我这样打印:{{poi.content}}它给了我正确的内容:``但是当我使用[innerHtml]=“poi.content”时,它会给我这个html:当我使用[innerHtml]时,有谁知道为什么它会剥离我的样式Angular2清理动态添加的HTML,样式,……

  2. 为Angular根组件/模块指定@Input()参数

    我有3个根组件,由根AppModule引导.你如何为其中一个组件指定@input()参数?也不由AppModalComponent获取:它是未定义的.据我所知,你不能将@input()传递给bootstraped组件.但您可以使用其他方法来做到这一点–将值作为属性传递.index.html:app.component.ts:

  3. angular-ui-bootstrap – 如何为angular ui-bootstrap tabs指令指定href参数

    我正在使用角度ui-bootstrap库,但我不知道如何为每个选项卡指定自定义href.在角度ui-bootstrap文档中,指定了一个可选参数select(),但我不知道如何使用它来自定义每个选项卡的链接另一种重新定义问题的方法是如何使用带有角度ui-bootstrap选项卡的路由我希望现在还不算太晚,但我今天遇到了同样的问题.你可以通过以下方式实现:1)在控制器中定义选项卡href:2)声明一个函数来改变控制器中的散列:3)使用以下标记:我不确定这是否是最好的方法,我很乐意听取别人的意见.

  4. 离子框架 – 标签内部的ng-click不起作用

    >为什么标签标签内的按钮不起作用?>但是标签外的按钮(登陆)工作正常,为什么?>请帮我解决这个问题.我需要在点击时做出回复按钮workingdemo解决方案就是不要为物品使用标签.而只是使用divHTML

  5. Angular 2:将值传递给路由数据解析

    我正在尝试编写一个DataResolver服务,允许Angular2路由器在初始化组件之前预加载数据.解析器需要调用不同的API端点来获取适合于正在加载的路由的数据.我正在构建一个通用解析器,而不是为我的许多组件中的每个组件设置一个解析器.因此,我想在路由定义中传递指向正确端点的自定义输入.例如,考虑以下路线:app.routes.ts在第一个实例中,解析器需要调用/path/to/resourc

  6. angularjs – 解释ngModel管道,解析器,格式化程序,viewChangeListeners和$watchers的顺序

    换句话说:如果在模型更新之前触发了“ng-change”,我可以理解,但是我很难理解在更新模型之后以及在完成填充更改之前触发函数绑定属性.如果您读到这里:祝贺并感谢您的耐心等待!

  7. 角度5模板形式检测形式有效性状态的变化

    为了拥有一个可以监听其包含的表单的有效性状态的变化的组件并执行某些组件的方法,是reactiveforms的方法吗?

  8. Angular 2 CSV文件下载

    我在springboot应用程序中有我的后端,从那里我返回一个.csv文件WheniamhittingtheURLinbrowsercsvfileisgettingdownloaded.现在我试图从我的角度2应用程序中点击此URL,代码是这样的:零件:服务:我正在下载文件,但它像ActuallyitshouldbeBook.csv请指导我缺少的东西.有一种解决方法,但您需要创建一个页面上的元

  9. angularjs – Angular UI-Grid:过滤后如何获取总项数

    提前致谢:)你应该避免使用jQuery并与API进行交互.首先需要在网格创建事件中保存对API的引用.您应该已经知道总行数.您可以使用以下命令获取可见/已过滤行数:要么您可以使用以下命令获取所选行的数量:

  10. angularjs – 迁移gulp进程以包含typescript

    或者我应该使用tsc作为我的主要构建工具,让它解决依赖关系,创建映射文件并制作捆绑包?

返回
顶部