剑客
关注科技互联网

angular 1.x 多项目共享子项目实践之路

背景

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

angular 1.x 多项目共享子项目实践之路

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

  • 以何种方式维护子项目

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

我们先从一个正常的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会有更好的组件化方式实现。

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址