如何用DevUI搭建自己的Angular组件库

2022-04-15 0 964
目录
  • 前言
  • 创建组件库
  • 关键配置修改
    • 目录布局调整
    • 库构建关键配置
  • 开发一个Alert组件
    • 组件功能介绍
    • 组件结构分解
    • 定义输入输出
    • 定义布局
  • 测试Alert组件
    • 开发态引用组件
    • 编写单元测试
  • 发布组件

    前言

    作为前端开发者,随着公司业务的不断发展和增长,业务对组件功能、交互的诉求会越来越多,不同产品或者团队之间公用的组件也会越来越多,这时候就需要有一套用于支撑内部使用的组件库,也可以是基于已有组件扩展或者封装一些原生三方库。本文会手把手教你搭建自己的Angular组件库。

    创建组件库

    我们首先创建一个Angular项目,用来管理组件的展示和发布,用以下命令生成一个新的项目

    ng new <my-project>

    项目初始化完成后,进入到项目下运行以下cli命令初始化lib目录和配置, 生成一个组件库骨架

    ng generate library <my-lib> --prefix <my-prefix>

    my-lib为自己指定的library名称,比如devui,my-prefix为组件和指令前缀,比如d-xxx,默认生成的目录结构如下

    如何用DevUI搭建自己的Angular组件库

    angular.json配置文件中也可以看到projects下面多出了一段项目类型为library的配置

    "my-lib": {
      "projectType": "library",
      "root": "projects/my-lib",
      "sourceRoot": "projects/my-lib/src",
      "prefix": "dev",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-ng-packagr:build",
          "options": {
              "tsConfig": "projects/my-lib/tsconfig.lib.json",
              "project": "projects/my-lib/ng-package.json"
          },
      "configurations": {
        "production": {
          "tsConfig": "projects/my-lib/tsconfig.lib.prod.json"
        }
      }
    },
    ...

    关键配置修改

    目录布局调整

    从目录结构可以看出默认生成的目录结构比较深,参考material design,我们对目录结构进行自定义修改如下:

    如何用DevUI搭建自己的Angular组件库

    修改说明:

    • 删除了my-lib目录下的src目录,把src目录下的test.ts拷贝出来,组件库测试文件入口
    • 把组件平铺到my-lib目录下,并在my-lib目录下新增my-lib.module.ts(用于管理组件的导入、导出)和index.ts(导出my-lib.module.ts,简化导入)
    • 修改angular.json中my-lib下面的sourceRoot路径,指向my-lib即可

    修改如下:

    // my-lib.module.ts
    
    
    import { NgModule } from '@angular/core';
    import { CommonModule } from '@angular/common';
    import { AlertModule } from 'my-lib/alert'; // 此处按照按需引入方式导入,my-lib对应我们的发布库名
    
    
    @NgModule({
      imports: [ CommonModule ],
      exports: [AlertModule],
      providers: [],
    })
    export class MyLibModule {}
    
    
    // index.ts
    export * from './my-lib.module';
    
    
    //angular.json
    "projectType": "library",
    "root": "projects/my-lib",
    "sourceRoot": "projects/my-lib", // 这里路径指向我们新的目录
    "prefix": "devui"

    库构建关键配置

    ng-package.json配置文件,angular library构建时依赖的配置文件

    {
      "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
      "dest": "../../publish",
      "lib": {
        "entryFile": "./index.ts"
      },
      "whitelistedNonPeerDependencies": ["lodash-es"]
    }

    关键配置说明:

    • dest,lib构建输出路径,这里我们修改为publish目录,和项目构建dist目录区分开
    • lib/entryFile,指定库构建入口文件,此处指向我们上文的index.ts

    whitelistedNonPeerDependencies(可选),如果组件库依赖了第三方库,比如lodash,需要在此处配置白名单,因为ng-packagr构建时为了避免第三方依赖库可能存在多版本冲突的风险,会检查package.json的dependencies依赖配置,如果不配置白名单,存在dependencies配置时就会构建失败。

    package.json配置,建议尽量使用peerDependcies,如果业务也配置了相关依赖项的话

    {
      "name": "my-lib",
      "version": "0.0.1",
      "peerDependencies": {
        "@angular/common": "^9.1.6",
        "@angular/core": "^9.1.6",
        "tslib": "^1.10.0"
      }
    }

    详细完整的配置,可以参考angular官方文档 https://github.com/ng-packagr/ng-packagr/blob/master/docs/DESIGN.md

    开发一个Alert组件

    组件功能介绍

    我们参考DevUI组件库的alert组件开发一个组件,用来测试我们的组件库,alert组件主要是根据用户传入的类型呈现不同的颜色和图标,用于向用户显示不同的警告信息。视觉显示如下

    如何用DevUI搭建自己的Angular组件库

    组件结构分解

    首先,我们看一下alert组件目录包含哪些文件

    如何用DevUI搭建自己的Angular组件库

    目录结构说明:

    • 组件是一个完整的module(和普通业务模块一样),并包含了一个单元测试文件
    • 组件目录下有一个package.json,用于支持二级入口(单个组件支持按需引入)
    • public-api.ts用于导出module、组件、service等,是对外暴露的入口,index.ts会导出public-api,方便其它模块

    关键内容如下:

    // package.json
    {
      "ngPackage": {
        "lib": {
          "entryFile": "public-api.ts"
        }
      }
    }
    
    
    //public-api.ts
    /*
    * Public API Surface of Alert
    */
    export * from './alert.component';
    export * from './alert.module';

    定义输入输出

    接下来我们就开始实现组件,首先我们定义一下组件的输入输出,alert内容我们采用投影的方式传入,Input参数支持指定alert类型、是否显示图标、alert是否可关闭,Output返回关闭回调,用于使用者处理关闭后的逻辑

    import { Component, Input } from '@angular/core';
    // 定义alert有哪些可选类型
    export type AlertType = 'success' | 'danger' | 'warning' | 'info';
    
    
    @Component({
      selector: 'dev-alert',
      templateUrl: './alert.component.html',
      styleUrls: ['./alert.component.scss'],
    })
    export class AlertComponent {
      // Alert 类型
      @Input() type: AlertType = 'info';
      // 是否显示图标,用于支持用户自定义图标
      @Input() showIcon = true;
      // 是否可关闭
      @Input() closeable = false;
      // 关闭回调
      @Output() closeEvent: EventEmitter<boolean> = new EventEmitter<boolean>();
      hide = false;
      constructor() {}
    
    
      close(){
        this.closeEvent.emit(true);
        this.hide = true;
      }
    }

    定义布局

    根据api定义和视觉显示我们来实现页面布局结构,布局包含一个关闭按钮、图标占位和内容投影 ,组件关闭时,我们采用清空dom的方式处理。

    <div class="dev-alert {{ type }} " *ngIf="!hide">
      <button type="button" class="dev-close" (click)="close()" *ngIf="closeable"></button>
      <span class="dev-alert-icon icon-{{ type }}" *ngIf="showIcon"></span>
      <ng-content></ng-content>
    </div>

    到这里,我们组件的页面布局和组件逻辑已经封装完成,根据视觉显示再加上对应的样式处理就开发完成了。

    测试Alert组件

    开发态引用组件

    组件开发过程中,我们需要能够实时调试逻辑和调整UI展示,打开根目录下的tsconfig.json,修改一下paths路径映射,方便我们在开发态就可以本地调试我们的组件,这里直接把my-lib指向了组件源码,当然也可以通过ng build my-lib –watch来使用默认的配置, 指向构建好的预发布文件,此时这里就要配置成我们修改过的目录public/my-lib/*

    "paths": {
      "my-lib": [
        "projects/my-lib/index.ts"
      ],
      "my-lib/*": [
        "projects/my-lib/*"
      ],
    }

    配置完成后,就可以在应用中按照npm的方式使用我们正在开发的库了,我们在app.module.ts中先导入我们的正在开发的组件,这里可以从my-lib.module导入全部组件,或者直接导入我们的AlertModule(前面已经配置支持二级入口)

    import { AlertModule } from 'my-lib/alert';
    // import { MyLibModule } from 'my-lib';
    
    
    @NgModule({
      declarations: [
        AppComponent
      ],
      imports: [
        BrowserModule,
        AppRoutingModule,
        // MyLibModule
        AlertModule
      ],
      providers: [],
      bootstrap: [AppComponent]
    })
    export class AppModule { }

    此时在app.component.html页面中就可以直接使用我们正在开发的alert组件了

    <section>
      <dev-alert>我是一个默认类型的alert</dev-alert>
    </section>

    打开页面,就可以看到当前开发的效果,这时候我们就可以根据页面表现来调整样式和交互逻辑,此处就不继续展示了

    如何用DevUI搭建自己的Angular组件库

    编写单元测试

    前面提到我们有一个单元测试文件,组件开发为了保证代码的质量和后续重构组件的稳定性,在开发组件的时候,有条件的建议加上单元测试。

    由于我们调整了目录结构,我们先修改一下相关配置

    // angular.json
    "my-lib": {
      ...
      "test": {
        "builder": "@angular-devkit/build-angular:karma",
        "options": {
          "main": "projects/my-lib/test.ts", // 这里指向调整后的文件路径
          "tsConfig": "projects/my-lib/tsconfig.spec.json",
          "karmaConfig": "projects/my-lib/karma.conf.js"
        }
      },
    }
    
    
    //my-lib 目录下的tsconfig.spec.json  
    
    
    "files": [
      "test.ts" // 指向当前目录下的测试入口文件
    ]

    下面是一个简单的测试参考,只简单测试了type类型是否正确,直接测试文件中定义了要测试的组件,场景较多的时候建议提供demo,直接使用demo进行不同场景的测试。

    import { async, ComponentFixture, TestBed } from '@angular/core/testing';
    
    
    import { Component } from '@angular/core';
    import { AlertModule } from './alert.module';
    import { AlertComponent } from './alert.component';
    import { By } from '@angular/platform-browser';
    
    
    @Component({
      template: `
        <dev-alert [type]="type" [showIcon]= "showIcon"[closeable]="closeable"    (closeEvent)="handleClose($event)">
        <span>我是一个Alert组件</span>
        </dev-alert>
      `
    })
    class TestAlertComponent {
      type = 'info';
      showIcon = false;
      closeable = false;
      clickCount = 0;
      handleClose(value) {
        this.clickCount++;
      }
    }
    
    
    describe('AlertComponent', () => {
      let component: TestAlertComponent;
      let fixture: ComponentFixture<TestAlertComponent>;
      let alertElement: HTMLElement;
    
    
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          imports: [AlertModule],
          declarations: [ TestAlertComponent ]
        })
        .compileComponents();
      }));
    
    
      beforeEach(() => {
        fixture = TestBed.createComponent(TestAlertComponent);
        component = fixture.componentInstance;
        alertElement = fixture.debugElement.query(By.directive(AlertComponent)).nativeElement;
        fixture.detectChanges();
      });
    
    
      describe('alert instance test', () => {
        it('should create', () => {
          expect(component).toBeTruthy();
        });
      });
    
    
      describe('alert type test', () => {
        it('Alert should has info type', () => {
          expect(alertElement.querySelector('.info')).not.toBe(null);
        });
    
    
        it('Alert should has success type', () => {
          // 修改type,判断类型改变是否正确
          component.type = 'success';
          fixture.detectChanges();
          expect(alertElement.querySelector('.success')).not.toBe(null);
        });
      }

    通过执行 ng test my-lib就可以执行单元测试了,默认会打开一个窗口展示我们的测试结果

    到这一步,组件开发态引用、测试就完成了,功能和交互没有问题的话,就可以准备发布到npm了。

    更多测试内容参考官方介绍:https://angular.cn/guide/testing

    发布组件

    组件开发完成后,单元测试也满足我们定义的门禁指标,就可以准备发布到npm提供给其他同学使用了。

    首先我们构建组件库,由于ng9之后默认使用ivy引擎。官方并不建议把 Ivy 格式的库发布到 NPM 仓库。因此在发布到 NPM 之前,我们使用 –prod 标志构建它,此标志会使用老的编译器和运行时,也就是视图引擎(View Engine),以代替 Ivy。

    ng build my-lib --prod

    如何用DevUI搭建自己的Angular组件库

    构建成功后,就可以着手发布组件库了,这里以发布到npm官方仓库为例

    如果还没有npm账号,请到官网网站注册一个账号,选用public类型的免费账号就可以

    已有账号,先确认配置的registry是否指向npm官方registry https://registry.npmjs.org/

    在终端中执行npm login登录已注册的用户

    准备工作都完成后,进入构建目录,这里是publish目录,然后执行 npm publish –access public就可以发布了,注意我们的库名需要是在npm上没有被占用的,名字的修改在my-lib目录下的package.json中修改。

    如何用DevUI搭建自己的Angular组件库

    npm发布参考: https://docs.npmjs.com/packages-and-modules/contributing-packages-to-the-registry

    如果是内部私有库,按照私有库的要求配置registry就可以了,发布命令都是一样的。

    以上就是如何用DevUI搭建自己的Angular组件库的详细内容,更多关于DevUI搭建自己的Angular组件库的资料请关注NICE源码其它相关文章!

    免责声明:
    1、本网站所有发布的源码、软件和资料均为收集各大资源网站整理而来;仅限用于学习和研究目的,您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。 不得使用于非法商业用途,不得违反国家法律。否则后果自负!

    2、本站信息来自网络,版权争议与本站无关。一切关于该资源商业行为与www.niceym.com无关。
    如果您喜欢该程序,请支持正版源码、软件,购买注册,得到更好的正版服务。
    如有侵犯你版权的,请邮件与我们联系处理(邮箱:skknet@qq.com),本站将立即改正。

    NICE源码网 JavaScript 如何用DevUI搭建自己的Angular组件库 https://www.niceym.com/27718.html