手把手教你搭建自己的Angular组件库 - DevUI
摘要:DevUI 是一款面向企业中后台产品的开源前端解决方案,它倡导沉浸、灵活、至简的设计价值观,提倡设计者为真实的需求服务,为多数人的设计,拒绝哗众取宠、取悦眼球的设计。如果你正在开发 ToB 的工具类产品,DevUI 将是一个很不错的选择!
DevUI是一支兼具设计视角和工程视角的团队,服务于华为云DevCloud平台和华为内部数个中后台系统,服务于设计师和前端工程师。
官方网站:devui.designNg组件库:ng-devui(欢迎Star)
引言
作为前端开发者,随着公司业务的不断发展和增长,业务对组件功能、交互的诉求会越来越多,不同产品或者团队之间公用的组件也会越来越多,这时候就需要有一套用于支撑内部使用的组件库,也可以是基于已有组件扩展或者封装一些原生三方库。本文会手把手教你搭建自己的Angular组件库。
创建组件库
我们首先创建一个Angular项目,用来管理组件的展示和发布,用以下命令生成一个新的项目
ng new <my-project>
项目初始化完成后,进入到项目下运行以下cli命令初始化lib目录和配置, 生成一个组件库骨架
ng generate library <my-lib> --prefix <my-prefix>
my-lib为自己指定的library名称,比如devui,my-prefix为组件和指令前缀,比如d-xxx,默认生成的目录结构如下
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,我们对目录结构进行自定义修改如下:
修改说明:
- 删除了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组件主要是根据用户传入的类型呈现不同的颜色和图标,用于向用户显示不同的警告信息。视觉显示如下
组件结构分解
首先,我们看一下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官方仓库为例
- 如果还没有npm账号,请到官网网站注册一个账号,选用public类型的免费账号就可以
- 已有账号,先确认配置的registry是否指向npm官方registry https://registry.npmjs.org/
- 在终端中执行
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。
手把手教你搭建自己的Angular组件库 - DevUI的更多相关文章
- 手把手教你如何构建Vue前端组件库
在前端开发中可能会遇到将相同的功能模板集合成一个组件,供他人调用,这样可以减少重复造轮子,也可以节约人力.财力,更能够提高代码的可维护度:下面将通过详细的步骤教你如何构建一个Vue前端组件. 1.在本 ...
- 庐山真面目之十一微服务架构手把手教你搭建基于Jenkins的企业级CI/CD环境
庐山真面目之十一微服务架构手把手教你搭建基于Jenkins的企业级CI/CD环境 一.介绍 说起微服务架构来,有一个环节是少不了的,那就是CI/CD持续集成的环境.当然,搭建CI/CD环境的工具很多, ...
- 大数据江湖之即席查询与分析(下篇)--手把手教你搭建即席查询与分析Demo
上篇小弟分享了几个“即席查询与分析”的典型案例,引起了不少共鸣,好多小伙伴迫不及待地追问我们:说好的“手把手教你搭建即席查询与分析Demo”啥时候能出?说到就得做到,差啥不能差人品,本篇只分享技术干货 ...
- 手把手教你搭建Pytest+Allure2.X环境详细教程,生成让你一见钟情的测试报告(非常详细,非常实用)
简介 宏哥之前在做接口自动化的时候,用的测试报告是HTMLTestRunner,虽说自定义模板后能满足基本诉求,但是仍显得不够档次,高端,大气,遂想用其他优秀的report框架替换之.一次偶然的机会, ...
- 手把手教你搭建FastDFS集群(下)
手把手教你搭建FastDFS集群(下) 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/u0 ...
- 手把手教你搭建FastDFS集群(中)
手把手教你搭建FastDFS集群(中) 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/u0 ...
- 手把手教你搭建FastDFS集群(上)
手把手教你搭建FastDFS集群(上) 本文链接:https://blog.csdn.net/u012453843/article/details/68957209 FastDFS是一个 ...
- 手把手教你搭建 ELK 实时日志分析平台
本篇文章主要是手把手教你搭建 ELK 实时日志分析平台,那么,ELK 到底是什么呢? ELK 是三个开源项目的首字母缩写,这三个项目分别是:Elasticsearch.Logstash 和 Kiban ...
- 手把手教你搭建SSH框架(Eclipse版)
原文来自公众号[C you again],若需下载完整源码,请在公众号后台回复"ssh". 本期文章详细讲解了SSH(Spring+SpringMVC+Hibernate)框架的搭 ...
随机推荐
- XSS跨站脚本攻击(1)
将跨站脚本攻击缩写为XSS,恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页面的时候,嵌入其中的Web里面的Script代码就会被执行,从而达到恶意攻击用户的目的. 反射型XSS 反射 ...
- 快速入门Redis调用Lua脚本及使用场景介绍
Redis 是一种非常流行的内存数据库,常用于数据缓存与高频数据存储.大多数开发人员可能听说过redis可以运行 Lua 脚本,但是可能不知道redis在什么情况下需要使用到Lua脚本. 一.阅读本文 ...
- WPF权限控制框架——【4】抛砖引玉
写第一篇"权限控制框架"系列博客是在2021-01-29,在这不到一个月的时间里,收集自己零碎的时间,竟然写出了一个"麻雀虽小,五脏俱全"的权限控制框架:对于一 ...
- bootstrap日期范围选择插件daterangepicker详细使用方法
插件官方网站地址 bootstrap-daterangepicker是个很方便的插件,但是对我这种菜鸟来说,文档不够详细,摆弄了好久才整好.记录下来供以后参考,也希望能帮到有需要的朋友. 目前版本是2 ...
- 企业安全_检测SQL注入的一些方式探讨
目录 寻找SQL注入点的 way MySQL Inject 入门案例 自动化审计的尝试之旅 人工审计才能保证精度 寻找SQL注入点的 way 在企业中有如下几种方式可以选择: 自动化 - 白盒基于源码 ...
- 模式识别Pattern Recognition
双目摄像头,单目摄像头缺少深度 Train->test->train->test->predicive
- Kibana 插件环境搭建教程
原文 环境背景, Kibana 7.4.0, Elasticsearch 7.4.0 注意, 执行以下命令时, 尽量在管理员权限的命令行窗口里执行, 避免一些没有权限的报错; 1. 准备 Kibana ...
- 一篇看懂JVM底层详解,利用class反编译文件了解文件执行流程
JVM之内存结构详解 JVM内存结构 java虚拟机在执行程序的过程中会将内存划分为不同的区域,具体如图1-1所示. 五个区域 JVM分为五个区域:堆.虚拟机栈.本地方法栈.方法区(元空间).程序计数 ...
- C语言编程 菜鸟练习100题(21-30)
[练习21]计算自然数的和 0. 题目: 计算自然数的和 1. 分析: 练习使用 for 循环结构.for 循环允许一个执行指定次数的循环控制结构. 2. 程序: #include <stdio ...
- HDU_6693 Valentine's Day 【概率问题】
一.题目 Valentine's Day 二.分析 假设$ s_0 $代表不开心的概率,$ s_1 $代表开心一次的概率. 那么随便取一个物品,那么它的开心概率为$ p _i $,可以推导加入之后使女 ...