手把手教你搭建本身的Angular組件庫

DevUI是一支兼具設計視角和工程視角的團隊,服務於華爲雲 DevCloud平臺和華爲內部數箇中後臺系統,服務於設計師和前端工程師。
官方網站: devui.design
Ng組件庫: ng-devui(歡迎Star)

引言

做爲前端開發者,隨着公司業務的不斷髮展和增加,業務對組件功能、交互的訴求會愈來愈多,不一樣產品或者團隊之間公用的組件也會愈來愈多,這時候就須要有一套用於支撐內部使用的組件庫,也能夠是基於已有組件擴展或者封裝一些原生三方庫。本文會手把手教你搭建本身的Angular組件庫。css

 

建立組件庫

咱們首先建立一個Angular項目,用來管理組件的展現和發佈,用如下命令生成一個新的項目html

ng new <my-project>前端

項目初始化完成後,進入到項目下運行如下cli命令初始化lib目錄和配置, 生成一個組件庫骨架node

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

my-lib爲本身指定的library名稱,好比devui,my-prefix爲組件和指令前綴,好比d-xxx,默認生成的目錄結構以下github

angular.json配置文件中也能夠看到projects下面多出了一段項目類型爲library的配置npm

"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,咱們對目錄結構進行自定義修改以下:json

修改說明:bootstrap

  • 刪除了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便可

修改以下:api

// 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": "de

庫構建關鍵配置

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組件主要是根據用戶傳入的類型呈現不一樣的顏色和圖標,用於向用戶顯示不一樣的警告信息。視覺顯示以下

組件結構分解

首先,咱們看一下alert組件目錄包含哪些文件

目錄結構說明:

  • 組件是一個完整的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>

打開頁面,就能夠看到當前開發的效果,這時候咱們就能夠根據頁面表現來調整樣式和交互邏輯,此處就不繼續展現了

編寫單元測試

前面提到咱們有一個單元測試文件,組件開發爲了保證代碼的質量和後續重構組件的穩定性,在開發組件的時候,有條件的建議加上單元測試。

因爲咱們調整了目錄結構,咱們先修改一下相關配置

// 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

構建成功後,就能夠着手發佈組件庫了,這裏以發佈到npm官方倉庫爲例

  1. 若是尚未npm帳號,請到官網網站註冊一個帳號,選用public類型的免費帳號就能夠
  2. 已有帳號,先確認配置的registry是否指向npm官方registry https://registry.npmjs.org/
  3. 在終端中執行npm login登陸已註冊的用戶

準備工做都完成後,進入構建目錄,這裏是publish目錄,而後執行 npm publish --access public就能夠發佈了,注意咱們的庫名須要是在npm上沒有被佔用的,名字的修改在my-lib目錄下的package.json中修改。

npm發佈參考: https://docs.npmjs.com/packages-and-modules/contributing-packages-to-the-registry

 

若是是內部私有庫,按照私有庫的要求配置registry就能夠了,發佈命令都是同樣的。

 

加入咱們
 

咱們是DevUI團隊,歡迎來這裏和咱們一塊兒打造優雅高效的人機設計/研發體系。招聘郵箱:muyang2@huawei.com。

文/DevUI June
相關文章
相關標籤/搜索