如何用DevUI搭建自己的Angular組件庫
前言
作為前端開發(fā)者,隨著公司業(yè)務的不斷發(fā)展和增長,業(yè)務對組件功能、交互的訴求會越來越多,不同產(chǎn)品或者團隊之間公用的組件也會越來越多,這時候就需要有一套用于支撐內(nèi)部使用的組件庫,也可以是基于已有組件擴展或者封裝一些原生三方庫。本文會手把手教你搭建自己的Angular組件庫。
創(chuàng)建組件庫
我們首先創(chuàng)建一個Angular項目,用來管理組件的展示和發(fā)布,用以下命令生成一個新的項目
ng new <my-project>
項目初始化完成后,進入到項目下運行以下cli命令初始化lib目錄和配置, 生成一個組件庫骨架
ng generate library <my-lib> --prefix <my-prefix>
my-lib為自己指定的library名稱,比如devui,my-prefix為組件和指令前綴,比如d-xxx,默認生成的目錄結(jié)構(gòu)如下

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"
}
}
},
...
關(guān)鍵配置修改
目錄布局調(diào)整
從目錄結(jié)構(gòu)可以看出默認生成的目錄結(jié)構(gòu)比較深,參考material design,我們對目錄結(jié)構(gòu)進行自定義修改如下:

修改說明:
- 刪除了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對應我們的發(fā)布庫名
@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"
庫構(gòu)建關(guān)鍵配置
ng-package.json配置文件,angular library構(gòu)建時依賴的配置文件
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../publish",
"lib": {
"entryFile": "./index.ts"
},
"whitelistedNonPeerDependencies": ["lodash-es"]
}
關(guān)鍵配置說明:
- dest,lib構(gòu)建輸出路徑,這里我們修改為publish目錄,和項目構(gòu)建dist目錄區(qū)分開
- lib/entryFile,指定庫構(gòu)建入口文件,此處指向我們上文的index.ts
whitelistedNonPeerDependencies(可選),如果組件庫依賴了第三方庫,比如lodash,需要在此處配置白名單,因為ng-packagr構(gòu)建時為了避免第三方依賴庫可能存在多版本沖突的風險,會檢查package.json的dependencies依賴配置,如果不配置白名單,存在dependencies配置時就會構(gòu)建失敗。
package.json配置,建議盡量使用peerDependcies,如果業(yè)務也配置了相關(guān)依賴項的話
{
"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
開發(fā)一個Alert組件
組件功能介紹
我們參考DevUI組件庫的alert組件開發(fā)一個組件,用來測試我們的組件庫,alert組件主要是根據(jù)用戶傳入的類型呈現(xiàn)不同的顏色和圖標,用于向用戶顯示不同的警告信息。視覺顯示如下

組件結(jié)構(gòu)分解
首先,我們看一下alert組件目錄包含哪些文件

目錄結(jié)構(gòu)說明:
- 組件是一個完整的module(和普通業(yè)務模塊一樣),并包含了一個單元測試文件
- 組件目錄下有一個package.json,用于支持二級入口(單個組件支持按需引入)
- public-api.ts用于導出module、組件、service等,是對外暴露的入口,index.ts會導出public-api,方便其它模塊
關(guān)鍵內(nèi)容如下:
// package.json
{
"ngPackage": {
"lib": {
"entryFile": "public-api.ts"
}
}
}
//public-api.ts
/*
* Public API Surface of Alert
*/
export * from './alert.component';
export * from './alert.module';
定義輸入輸出
接下來我們就開始實現(xiàn)組件,首先我們定義一下組件的輸入輸出,alert內(nèi)容我們采用投影的方式傳入,Input參數(shù)支持指定alert類型、是否顯示圖標、alert是否可關(guān)閉,Output返回關(guān)閉回調(diào),用于使用者處理關(guān)閉后的邏輯
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;
// 是否可關(guān)閉
@Input() closeable = false;
// 關(guān)閉回調(diào)
@Output() closeEvent: EventEmitter<boolean> = new EventEmitter<boolean>();
hide = false;
constructor() {}
close(){
this.closeEvent.emit(true);
this.hide = true;
}
}
定義布局
根據(jù)api定義和視覺顯示我們來實現(xiàn)頁面布局結(jié)構(gòu),布局包含一個關(guān)閉按鈕、圖標占位和內(nèi)容投影 ,組件關(guān)閉時,我們采用清空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>
到這里,我們組件的頁面布局和組件邏輯已經(jīng)封裝完成,根據(jù)視覺顯示再加上對應的樣式處理就開發(fā)完成了。
測試Alert組件
開發(fā)態(tài)引用組件
組件開發(fā)過程中,我們需要能夠?qū)崟r調(diào)試邏輯和調(diào)整UI展示,打開根目錄下的tsconfig.json,修改一下paths路徑映射,方便我們在開發(fā)態(tài)就可以本地調(diào)試我們的組件,這里直接把my-lib指向了組件源碼,當然也可以通過ng build my-lib --watch來使用默認的配置, 指向構(gòu)建好的預發(fā)布文件,此時這里就要配置成我們修改過的目錄public/my-lib/*
"paths": {
"my-lib": [
"projects/my-lib/index.ts"
],
"my-lib/*": [
"projects/my-lib/*"
],
}
配置完成后,就可以在應用中按照npm的方式使用我們正在開發(fā)的庫了,我們在app.module.ts中先導入我們的正在開發(fā)的組件,這里可以從my-lib.module導入全部組件,或者直接導入我們的AlertModule(前面已經(jīng)配置支持二級入口)
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頁面中就可以直接使用我們正在開發(fā)的alert組件了
<section> <dev-alert>我是一個默認類型的alert</dev-alert> </section>
打開頁面,就可以看到當前開發(fā)的效果,這時候我們就可以根據(jù)頁面表現(xiàn)來調(diào)整樣式和交互邏輯,此處就不繼續(xù)展示了

編寫單元測試
前面提到我們有一個單元測試文件,組件開發(fā)為了保證代碼的質(zhì)量和后續(xù)重構(gòu)組件的穩(wěn)定性,在開發(fā)組件的時候,有條件的建議加上單元測試。
由于我們調(diào)整了目錄結(jié)構(gòu),我們先修改一下相關(guān)配置
// angular.json
"my-lib": {
...
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "projects/my-lib/test.ts", // 這里指向調(diào)整后的文件路徑
"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);
});
}
通過執(zhí)行 ng test my-lib就可以執(zhí)行單元測試了,默認會打開一個窗口展示我們的測試結(jié)果
到這一步,組件開發(fā)態(tài)引用、測試就完成了,功能和交互沒有問題的話,就可以準備發(fā)布到npm了。
更多測試內(nèi)容參考官方介紹:https://angular.cn/guide/testing
發(fā)布組件
組件開發(fā)完成后,單元測試也滿足我們定義的門禁指標,就可以準備發(fā)布到npm提供給其他同學使用了。
首先我們構(gòu)建組件庫,由于ng9之后默認使用ivy引擎。官方并不建議把 Ivy 格式的庫發(fā)布到 NPM 倉庫。因此在發(fā)布到 NPM 之前,我們使用 --prod 標志構(gòu)建它,此標志會使用老的編譯器和運行時,也就是視圖引擎(View Engine),以代替 Ivy。
ng build my-lib --prod

構(gòu)建成功后,就可以著手發(fā)布組件庫了,這里以發(fā)布到npm官方倉庫為例
如果還沒有npm賬號,請到官網(wǎng)網(wǎng)站注冊一個賬號,選用public類型的免費賬號就可以
已有賬號,先確認配置的registry是否指向npm官方registry https://registry.npmjs.org/
在終端中執(zhí)行npm login登錄已注冊的用戶
準備工作都完成后,進入構(gòu)建目錄,這里是publish目錄,然后執(zhí)行 npm publish --access public就可以發(fā)布了,注意我們的庫名需要是在npm上沒有被占用的,名字的修改在my-lib目錄下的package.json中修改。

npm發(fā)布參考: https://docs.npmjs.com/packages-and-modules/contributing-packages-to-the-registry
如果是內(nèi)部私有庫,按照私有庫的要求配置registry就可以了,發(fā)布命令都是一樣的。
以上就是如何用DevUI搭建自己的Angular組件庫的詳細內(nèi)容,更多關(guān)于DevUI搭建自己的Angular組件庫的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
AngularJS框架中的雙向數(shù)據(jù)綁定機制詳解【減少需要重復的開發(fā)代碼量】
這篇文章主要介紹了AngularJS框架中的雙向數(shù)據(jù)綁定機制,結(jié)合實例形式分析了AngularJS雙向數(shù)據(jù)綁定機制的原理及實現(xiàn)方法,以及減少需要重復開發(fā)代碼量的優(yōu)勢,需要的朋友可以參考下2017-01-01
AngularJS自定義插件實現(xiàn)網(wǎng)站用戶引導功能示例
這篇文章主要介紹了AngularJS自定義插件實現(xiàn)網(wǎng)站用戶引導功能,結(jié)合實例形式分析了AngularJS自定義插件的實現(xiàn)步驟與相關(guān)功能技巧,需要的朋友可以參考下2016-11-11
AngularJS動態(tài)綁定ng-options的ng-model實例代碼
本篇文章主要介紹了AngularJS動態(tài)綁定ng-options的ng-model實例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06

