前言
即便是一个青铜,也要用王者的心去编码!
Github上关于Vue的UI库,大大小小不计其数,即便是已经被推广使用的成熟库,也有很多。很多时候,我们自研一套UI库,不是想要做得多牛逼,竞争过别人(事实咱也干不过人家,除非你不是一个人在战斗。毕竟这不仅是个技术活,还是个体力活),我们仅仅是源自一个青铜对王者的仰望或者是为满足内心的需求。
这里跟大家讲一个一步一步自研UI库的故事。
原文地址:github.com/qiud...
项目地址:Ange UI
开发一套UI库,做成不难(这里指的是半半半成品,全品也好难。。。),做好很难。但不慌,咱有秘籍:
- 一定的内功修为。所谓打铁还需自身硬,要做高复用组件的开发工作,对
Vue.js
和CSS3
还是有一定的技术要求。那我要多牛逼才能写好这个UI库啊?这取决于你这套UI库的实现高度。 - 一些招式套路。藏经阁(Github)上有很多关于Vue的UI库:六脉神剑、独孤九剑应有尽有。人家怎么写的,咱们跟着来就好了。有人可能意见很大,那不是在模仿吗?这里要严正声明,我们不是在模仿,我们只是向标准靠拢。因为牛逼的就是标准的。
UI库的必要架构
一套成熟的开源UI库一般都具备以下几个特点:
- 包含了组件源码
- 完备的说明文档
- 符合Eslint校验标准
- 全面的单元测试
- 完整的构建生态
因此,它们的目录架构也出现了主要的两种形式:
& 有点大同小异,这里我们按照第一种架构去开发。- build:存放构建配置文件
- docs:官方说明文档
- src:组件源码
- test:单元测试用例(这里不作阐述)
- eslintrc:基于eslint-plugin-vue的开发规范标准
搭建UI开发环境
观察社区几大UI库发现,它们都是基于webpack
搭建了自己的构建配置,包括本地开发、生产环境构建、UI库的打包等,建立一套自己的构建生态。我们就不同了,业余一点(其实是善于应用开源工具),我们的docs文档是基于vuepress的,vuepress有自己一套的构建体系,所以我们只需要针对UI组件源码写一份打包配置就好了。
下面开始搭建打包配置(其实就是很久以前我们做的基于webpack的构建,现在cli用得多了,配置也不会写了,码耶): 首先在根目录建立config
和build
文件夹,然后往两个目录分别新建文件,如下:
- build-lib.js:node执行的脚本,读取并执行lib的配置进行构建;
- webpack.base.conf.js:公共的构建配置;
- webpack.lib.conf.js:针对UI组件打包的配置;
- index.js:可变的配置信息;
- prod.env.js:声明当前构建环境为生产环境的配置;
不是说只有一份打包的配置文件么?咋还多了那么多文件呢?是这样,虽然我们是业余的,但咱也想做得专业一点对吧(有利于对配置进行扩展管理)!
我们看 config/index.js
有什么?
webpack.lib.conf.js
中。
再看看build/webpack.base.conf.js
里面的关键配置:
entry
和
output
会被
webpack.lib.conf.js
覆写;
rules
定义一系列loader的转换规则,其中eslint的校验就是在这里定义了一个
eslint-loader
。
最后看一眼build/webpack.lib.conf.js
配置:
entry
的规则是,根据传参值(components)分别走不同的入口文件,一种只有一个
./src/index.js
,这个是UI对外注册的入口文件(这使得我们可以引入整个UI);另一种是各个UI组件的注册入口文件(这使得我们可以按需引入组件);
先给大家贴图直观感受下源码的目录结构:
那实际打包的时候走的是哪一种呢?真相是都走!在build-lib.js
中,执行了三次打包,打包输出效果如下:
开发一个组件
前面扎好了马步,终于到修炼招式的阶段了! 我们都知道,在应用某个插件的时候需要经过下面代码的调用:
import Ange from 'ange-ui'
Vue.use(Ange)
复制代码
好奇下Vue.use
在做什么处理呢?它其实就是注册/安装这个插件,根据use内部的定义,它通过调用install方法去注册插件,那么,Ange就必须是一个Function
(会被use当做是install
方法调用)或者是一个包含了install
方法的Object
。
道理我都懂,可是install
方法里面到底写些什么?
试想一下,我们希望在项目的任意位置都能引用这个插件,那我们的每一个组件是不是要在全局注册?比如通过下面这种方式全局注册组件:
Vue.component('pagination', pagination)
复制代码
没错,install
方法内部就是批量地全局注册组件。
搭好目录架构
首先我们按照下图的方式新建目录和文件:
在_src目录的index.js_文件中定义install
方法:
import './scss/ange.scss' // 引入组件样式表,也可以让用户在使用的时候自行引入
import components from './components'
function install(Vue, opts = {}) {
Object.values(components).forEach((each) => {
Vue.component(each.name, each)
})
}
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
export default {
version: '1.0.0',
install,
...components
}
复制代码
核心逻辑install就是对所有的组件循环注册在全局。components目录的 index.js 则是逐个对外暴露组件对象,其次每一个组件也有一个 index.js ,它的作用是为当前组件注入install方法。理所当然地,install
里面是将该组件注册在全局,于是我们可以按需引用组件。
开发Button组件
组件开发的通用模板
<template>
<component :is="'button'"></component>
</template>
<script>
export default {
name: 'ag-button',
props: {}
}
</script>
复制代码
component是vue的内置组件,is
参数设置成button
,表明最终渲染的html是button
标签,我们也可以直接使用button标签,但我们的按钮组件不一定是button,还可能是a标签,为了更好的拓展,这里使用component。
声明组件参数
export default {
name: 'ag-button',
props: {
// 按钮类别
primary: Boolean,
secondary: Boolean,
dashed: Boolean,
link: Boolean,
// 按钮状态
color: {
type: String,
validator (val) {
return new Set(['success', 'warn', 'danger']).has(val)
}
},
// 按钮尺寸
size: {
type: String,
validator (val) {
return new Set(['large', 'normal', 'small']).has(val)
}
},
// 图标按钮
icon: String,
// 原型按钮(一般结合图标按钮使用)
circle: Boolean,
// 外链按钮
external: Boolean,
// 异步按钮
loading: Boolean
}
}
复制代码
完善组件模板
<template>
<component
:is="tag"
class="ange-btn"
:class="[ btnSize, color, {
'default': isDefault,
'primary': primary,
'secondary': secondary,
'dashed': dashed,
'link': link,
'icon': icon,
'circle': circle
}]"
@click="$emit('click', $event)"
:disabled="loading">
<span class="ange-btn-content">
<!-- 图标按钮依赖 ag-icon 组件 -->
<ag-icon
v-if="icon"
:icon="icon" />
<slot /> <!-- 插槽接收按钮文本 -->
</span>
</component>
</template>
<script>
export default {
// ...
computed: {
tag () {
return this.external ? 'a' : 'button'
},
isDefault() {
const type = [this.primary, this.secondary, this.dashed, this.link]
return type.every((each) => !each)
},
btnSize() {
return this.size || 'normal'
}
}
}
</script>
复制代码
剩下的工作就是写好样式表了,你可以选择直接写在vue文件里面,也可以新建_scss/scss_样式表。
组件应用及效果(在线查看)
以上,便开发好一个button组件了,执行一下node build-lib.js
或者npm run build:lib
(现在package.json声明script)就可以打包这个UI框架,然后再将其发布到npm平台(如果你想...)
写好一份文档
在线查看 Ange UI Docs 写好文档是一个库不可或缺的部分,写的过程通过实际应用各组件,还可以对其进行测试校验。前面说到,我们的文档要基于vuepress开发,简洁的Markdown写法,很是方便。这里一篇 指南 可以很好地帮助你,它告诉了你如何搭建架构,写好配置以及部署上线,或者参考我这个 仓库 的配置。
假设docs/views/button.md
是你的button
组件的文档页面,要如何引用你的组件?
button
组件:
import '@scss/ange.scss'
import Button from '@component/button'
Vue.use(Button)
复制代码
至此,文档也就写好了!
最后的最后,按照 vuepress doc 部署到你的github仓库上就可以了!
本文通过button组件从0到1的开发,深入浅出阐述了Vue UI框架的开发流程,你对Vue.js的理解越深,组件的功能越复杂,你就会用到更多的高级用法。通过自研UI框架,我们也有很大的收获:
- 重新温习webpack配置
- 深入理解Vue内部机制,掌握更多的vue高级用法
- 夯实js和css3基础
- 掌握编程范式和设计模式
PS:CSS其实是UI开发中占比很重的部分,大家按照自己的风格组件化开发就好。给大家推荐几个很棒的配色网站:
最后,希望大家也能多多去尝试,这个青铜自研UI库的故事到这里就结束了。
The end.