如何开发公司组件库(下)

上一篇中介绍了在开发公司的组件库以前须要的准备工做,主要包括:需求评审、调研、组件设计和提出 issue。 本篇主要介绍在具体的开发环节须要注意哪些事项。html

建立开发环境

组件开发的时候须要不断测试当前已开发的功能,这里建议单独配置一个私人 demo 项目用于本地开发。建立组件对应的初始库(详见Angular 的建立库),执行ng build [库名称]生成本地的组件库。配置完成后,使用npm link连接刚才打包好的组件库和 demo 项目,而后在 demo 项目的angular.json中添加preserveSymlinks: true(以下),完成建立开发环境。这是为了防止本地库的引用依赖错误。node

{
    "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
    "version": 1,
    "newProjectRoot": "projects",
    "projects": {
        "my-project": {
            "root": "",
            "sourceRoot": "src",
            "projectType": "application",
            "architect": {
                "build": {
                    "builder": "@angular-devkit/build-angular:browser",
                    "options": {
                       "outputPath": "target",
                       "index": "src/index.html",
                       "main": "src/main.ts",
                       "tsConfig": "src/tsconfig.app.json",
                       "polyfills": "src/showcase/polyfills.ts",
                       "preserveSymlinks": true,
...
复制代码

肯定组件的开发方式

组件开发分为自行开发组件和基于第三方组件的二次封装,这两种在开发时略有不一样。前者一般是从自身的业务抽离而来好比标注组件,后者一般会有比较成熟的开源方案可供使用好比表格、图谱等。一般自行开发组件的成本会比较高,在业务相关性低,交互逻辑复杂,有比较成熟的开源方案的状况下,最好基于第三方组件作二次封装。react

中介者模式

二次封装一般有两种方式,能够用设计模式来表示:装饰者模式与中介者模式。git

装饰者模式是指在不改变原类及不使用继承的状况下,动态的扩展一个对象的功能。一般来讲就是用装饰器来实现一些原第三方组件作不到的功能。这种模式并不经常使用,这里很少介绍。github

咱们一般使用的是中介者模式。中介者模式是指用一个中介对象来封装一系列的对象交互,中介者使各对象不须要显式地相互引用,从而使其耦合松散,并且能够独立地改变它们之间的交互。 好比下面的例子中,TableComponent充当了一个中介者,将输入的data属性转化为第三方组件st能够接受的数据类型,所以能够不加修改的直接引用原来的组件。typescript

// table.component.ts
export class TableComponent implements OnChanges {
  @Input()
  data: Data[];
  _data: STData[];

  get _data(): STData[] {
    return this.data.map((item, index) => {
      if (this.selectedRows && this.selectedRows.includes(index)) {
        return {
          checked: true,
          ...item
        };
      }
      return item;
    });
  }
}
复制代码
<st [data]="_data" ...></st>
复制代码

是否应该使用组件继承

基于第三方组件的开发除了上述两种模式,其实还能够用继承来实现。Angular 的组件是使用@Component装饰器来实现的,所以组件的继承等同于 TypeScript 中带有类装饰器的类的继承。如下是一个组件继承的简单例子。像DerivedComponent同样,派生类若是拥有本身的@Component,其实至关于被两个@Component装饰器修饰过构造函数。根据 TypeScript 的装饰器组合原则,最外层的@Component被调用后对构造函数的修改会覆盖里层的@Component,最终生效的只是派生类的@Component中传入的参数。所以,若是须要改变@Component中的参数,则须要传入组件所需的所有参数。特别是providers中组件级的依赖,也是须要传入的,但第三方组件每每不会导出它的组件级依赖,就会给开发形成困难。而若是直接继承基类,像DirectDerivedComponent,该组件会继承@Component中全部的元参数和 BaseComponent 提供的类型与构造函数。可是由于不能自定义组件的元参数,致使这种使用方式受限不少。npm

在 TypeScript 里,当多个装饰器应用在一个声明上时会进行以下步骤的操做:json

  1. 由上至下依次对装饰器表达式求值。
  2. 求值的结果会被看成函数,由下至上依次调用。
// BaseComponent 基类组件
@Component({
  selector: 'app-base',
  templateUrl: './base.component.html',
  styleUrls: ['./base.component.less'],
  providers: [BaseService]
})
export class BaseComponent {
  construct(baseService: BaseService) {}
}

// DerivedComponent 派生类组件
@Component({
  selector: 'app-derived',
  templateUrl: './derived.component.html',
  styleUrls: ['./derived.component.less'],
  providers: [BaseService] // 装饰器中的元参数会覆盖基类组件,所以须要从新提供 providers
})
export class DerivedComponent extends BaseComponent {}

// DirectDerivedComponent 直接继承的派生类组件
export class DirectDerivedComponent extends BaseComponent {}
复制代码

以上分析说明,在 Angular 中使用继承的方式开发组件是一件受限不少且复杂的事情。并且 Angular 官方也没有提供关于组件继承相关的文档。只能从源码分析猜想组件继承的机制。所以,全部这些其实只是想说明一件事:尽可能避免使用组件继承。若是你足够细心,会发如今 Angular 在DI 实战一节有此说明。(不只 Angular, React 官方也强调避免使用组件继承。)固然也不是彻底不能使用,当组件遵循第三方组件的大多数交互逻辑只是在视图层有所区别时,才建议考虑使用继承的方式实现。segmentfault

想了解更多关于组件继承,这篇文章可能会有帮助。设计模式

引入第三方组件

若是你肯定引入第三方组件,此时很容易忽略的一个问题就是第三方组件的 npm 包依赖问题。请选择第三方组件一个合适版本引入,确保兼容公司组件库的核心依赖库。

另外值得说明的是,在开发库的过程当中,请在组件库根目录的 package.json 中的 devDependencies 中安装全部的同级依赖。而在目标库的 package.json 中使用 peerDependencies 说明同级依赖。关于为什么这样作的缘由能够参考 Angular 文档开发 Angualr 库与 ng-packagr 的文档dependencies

实现一个最小 demo

在一切准备就绪以后,先不要马上投入组件具体功能的实现中。我建议第一步实现一个最小的 demo,实现最基本最重要的功能,确保咱们的思路是可行的,至关于 POC。好比我在开发 table 组件的时候,初版的设计是这样的。table 列是经过 jsx 语法来实现自定义的,列的 API 设计也依赖于此。若是你以前用 React 的话会以为这看起来是很是天然的设计。

@Component({
  selector: 'table-demo',
  template: ` <bx-table [data]="data" [columns]="columns" ></bx-table> `
})
export class TableDemo {
  // 表格行配置
  columns = [
    {
      title: '姓名',
      dataIndex: 'name',
      key: 'name'
    },
    {
      title: 'Action',
      key: 'action',
      template: (value, record, index) =>
        `<a>Action 一 {{ record.name }}</a><nz-divider nzType="vertical"></nz-divider><a>Delete</a>`
    }
  ];
}
复制代码

然而 Angular 是不支持这种语法的,这是 React 的特性。致使我只能另寻他法。最终选择仿照 delon 实现自定义模板,而后从新改写 API。因此这个环节能够防止咱们犯那些很严重的错误。每每咱们第一次设计的 API 有诸多不合理之处,这是一个很好的机会进行调整,同时成功的 demo 也让咱们有信心继续开发下去。

继续开发,逐步实现 issue 中的功能

接下来就须要咱们慢慢逐步地实现 issue 中所列的功能了,在这期间可能会遇到不少意想不到的困难致使开发须要延期,也可能会须要调整不少次 API 以及增删一些功能。咱们是组件的设计者也是开发者同时也是使用者,咱们应该对它有本身的想法,开发时不断和你们沟通,能够据理力争也能够虚怀若谷,对它负责到底。这会是一个比较漫长的过程。咱们会像西西弗斯同样,不断踩坑爬坑,踩坑爬坑,踩坑爬坑。由于造轮子会比用轮子复杂许多,可能常常会碰到咱们知识的边界,会比较痛苦。可是当你最痛苦的时候,就是你进步最快的时候(也是头发掉的最快的时候)。

tsconfig.json 中的路径映射

在开发 angular 库的过程当中,必然要进行打包(ng build)。因为 angular 默认的打包工具(@angular-devkit/build-ng-packagr)来自于ng-packagr,这里把我在使用ng-packagr时遇到的一个比较常见的坑和你们分享一下。

咱们常常须要用别名指代某个位置的文件,好比须要用@component/*指代node_modules/component/dist/*。像这样的路径映射 typescript 是经过 tsconfig.json 的compilerOptionsbaseUrlpaths来支持的。在 Angular 项目中会使用 angular.json 中指定的 tsconfig 文件,而 VS Code 编辑器中会使用根目录下的 tsconfig.json 文件。所以有时会出现编辑器不能正确解析路径映射的状况,可是 angular 是能够正常构建的。(来源见这里

在开发库的时候,通常来讲路径映射应该在主目录下的 tsconfig.json中配置,但有时候须要在库级别进行路径映射,也就是在 tsconfig.lib.json进行配置。这里值得注意的是,paths指向的路径必须是已编译的文件所在路径,并且除了目标自己,也须要对目标的子文件进行映射。例如同处于projects目录下的lib-alib-blib-b引入了lib-a,此时假如咱们必需要在lib-b里进行路径映射,那么lib-b/tsconfig.lib.json须要配置大体以下。(关于此问题的讨论详见这里

{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "lib-a": ["../../dist/lib-a"],
      "lib-a/*": ["../../dist/lib-a/*"]
    }
  }
}
复制代码

开发终于结束了

经历一段时间辛苦的开发以后,组件终于能够投入使用了。在组件发布以前,请检查如下工做是否完成。

  • 对比 issue 中的设计,查看是否完成全部的功能以及 API 是否有变更。若是有和 issue 设计不一致的地方,向你们说明缘由。固然,最好是在开发的时候就积极沟通,和同事达成一致。
  • 添加组件的 README,进行 API 说明。若是有必要的话,尽可能补充使用范例。
  • 编辑.gitignore使其再也不忽略相应的编译文件,提交编译文件。

接下来只须要按照流程进行发布便可。

  1. 发起 MR(若是在开发前已经发起了 WIP 状态的 MR,如今须要移除 WIP 标志)
  2. Code Review
  3. 合并入主分支
  4. 决定是否发布新版本
    • 切出 release 分支,更新版本号
    • 使用 cheers 发布 beta 版本(默认会打上 tag)
    • 使用 demo 库测试 beta 版本,测试无误后合并入 master 分支,移除 release 分支

工做流介绍

咱们的项目通常采用 gitflow 做为 git 工做流。它拥有两个长期分支 develop 与 master 和一些临时分支 feature分支、release 分支、hotfix 分支等。平常的开发在临时分支上进行,开发完毕后合并入 develop 分支,更新版本的时候经过 release 分支分别合并入 develop 分支和 master 分支。下图是 gitflow 的工做流图。(了解更多移步这里)

gitflow

不过组件库项目开发通常采用github flow。github flow 与 gitflow 不一样的是移除了 develop 分支,只需维护一个长期分支 master(实际上它更相似于 github flow,见下图)。所以组件功能的更新不须要通过合入 develop 分支与切换 release 分支等操做,直接合入主分支便可。等到变更积累到必定量,会在主分支切出 release 分支,修改版本号而后打上 tag 进行发布。如此设计缘由在于 gitflow 的工做流使用 develop (或 release 分支)来进行测试环境的更新,master 来进行生产环境的更新,适合于业务开发。可是组件库不依赖于测试环境,所以也就无需 develop 分支。常见的开源项目通常都采用 github flow 的形式管理工做流。

github flow

至此全部的工做就都结束了,停下来歇一歇,总结一下开发过程当中的收获,和同事们分享你的成长吧!

相关连接: 如何开发公司组件库(上)

相关文章
相关标签/搜索