摘要: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官方仓库为例

  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

手把手教你搭建自己的Angular组件库 - DevUI的更多相关文章

  1. 手把手教你如何构建Vue前端组件库

    在前端开发中可能会遇到将相同的功能模板集合成一个组件,供他人调用,这样可以减少重复造轮子,也可以节约人力.财力,更能够提高代码的可维护度:下面将通过详细的步骤教你如何构建一个Vue前端组件. 1.在本 ...

  2. 庐山真面目之十一微服务架构手把手教你搭建基于Jenkins的企业级CI/CD环境

    庐山真面目之十一微服务架构手把手教你搭建基于Jenkins的企业级CI/CD环境 一.介绍 说起微服务架构来,有一个环节是少不了的,那就是CI/CD持续集成的环境.当然,搭建CI/CD环境的工具很多, ...

  3. 大数据江湖之即席查询与分析(下篇)--手把手教你搭建即席查询与分析Demo

    上篇小弟分享了几个“即席查询与分析”的典型案例,引起了不少共鸣,好多小伙伴迫不及待地追问我们:说好的“手把手教你搭建即席查询与分析Demo”啥时候能出?说到就得做到,差啥不能差人品,本篇只分享技术干货 ...

  4. 手把手教你搭建Pytest+Allure2.X环境详细教程,生成让你一见钟情的测试报告(非常详细,非常实用)

    简介 宏哥之前在做接口自动化的时候,用的测试报告是HTMLTestRunner,虽说自定义模板后能满足基本诉求,但是仍显得不够档次,高端,大气,遂想用其他优秀的report框架替换之.一次偶然的机会, ...

  5. 手把手教你搭建FastDFS集群(下)

    手把手教你搭建FastDFS集群(下) 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/u0 ...

  6. 手把手教你搭建FastDFS集群(中)

    手把手教你搭建FastDFS集群(中) 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/u0 ...

  7. 手把手教你搭建FastDFS集群(上)

    手把手教你搭建FastDFS集群(上) 本文链接:https://blog.csdn.net/u012453843/article/details/68957209        FastDFS是一个 ...

  8. 手把手教你搭建 ELK 实时日志分析平台

    本篇文章主要是手把手教你搭建 ELK 实时日志分析平台,那么,ELK 到底是什么呢? ELK 是三个开源项目的首字母缩写,这三个项目分别是:Elasticsearch.Logstash 和 Kiban ...

  9. 手把手教你搭建SSH框架(Eclipse版)

    原文来自公众号[C you again],若需下载完整源码,请在公众号后台回复"ssh". 本期文章详细讲解了SSH(Spring+SpringMVC+Hibernate)框架的搭 ...

随机推荐

  1. iOS图片预览、放大缩小

    思路 图片预览,优先考虑基础控件UIImageView.UIButton 图片预览中可能需设置不同的mode,优先考虑UIImageView typedef NS_ENUM(NSInteger, UI ...

  2. TERSUS无代码开发(笔记07)-简单实例手机端后台逻辑开发

    提交申请逻辑开发 1.添加父级对象引用(从父级对象中获取前端输入框的值) 1.设计数据库表(表名和字段名称不能用中文) 2.设计置数据库主键(可设联合主键) 3.传值形成数据实列处理 4.服务器端处理 ...

  3. 翻译:《实用的Python编程》03_03_Error_checking

    目录 | 上一节 (3.2 深入函数) | 下一节 (3.4 模块) 3.3 错误检查 虽然前面已经介绍了异常,但本节补充一些有关错误检查和异常处理的其它细节. 程序是如何运行失败的 Python 不 ...

  4. 你不知道的Scheduled定时任务骚操作

    目录 一.什么是定时任务 二.项目依赖 三.注解式定时任务 3.1 cron 3.2 fixedDelay 3.3 fixedDelayString 3.4 fixedRate 3.5 fixedRa ...

  5. 一个 java 文件的执行过程详解

    平时我们都使用 idea.eclipse 等软件来编写代码,在编写完之后直接点击运行就可以启动程序了,那么这个过程是怎么样的? 总体过程 我们编写的 java 文件在由编译器编译后会生成对应的 cla ...

  6. CCF(JSON查询:40分):字符串+模拟

    JSON查询 201709-3 纯字符串模拟,考的就是耐心和细心.可惜这两样我都缺... #include<iostream> #include<cstdio> #includ ...

  7. Lua生成Guid(uuid)

    全局唯一标识符(GUID,Globally Unique Identifier)也称作 UUID(Universally Unique IDentifier) .GUID是一种由算法生成的二进制长度为 ...

  8. 手把手教你集成华为机器学习服务(ML Kit)人脸检测功能

    当给自己拍一张美美的自拍照时,却发现照片中自己的脸不够瘦.眼睛不够大.表情不够丰富可爱-如果此时能够一键美颜瘦脸并且添加可爱的贴纸的话,是不是很棒? 当家里的小孩观看iPad屏幕时间过长或者眼睛离屏幕 ...

  9. Apache配置 4.访问日志

    (1)介绍 访问日志作用很大,不仅可以记录网站的访问情况,还可以在网站有异常发生时帮助我们定位问题. (2)配置 # vi /usr/local/apache2.4/conf/extra/httpd- ...

  10. C# AppDomain.CurrentDomain.BaseDirectory

    AppDomain.CurrentDomain.BaseDirectory   是获取基目录,它由程序集冲突解决程序用来探测程序集.由显示的路径可以看出,它代表的是程序集所在的目录,它具有读取和写入的 ...