最近使用主流的UI框架,忽然心血来潮,何不自己搭建属于自己的UI组件库呢,类似Mint-UI、Element-UI、Vant等组件框架,好在大多UI框架都是开源,所以经过学习实践,终于搭建了属于自己UI组件库——LInView。
当然想要跟主流框架一样,当然不能只停留在本地运行,我会发布到NPM上,并使用VuePress形成文档,以方便开发使用。
前言
本文章着重与全流程的开发,不单于仅限于开发一点,真正的从0到1的开发,LinView也不是基于其他UI框架进行二次开发,完全的样式开发。更着文章步骤,你也能实现自己UI组件库。
为何造轮子:造轮子的意义在于理解其原理,并且对于一些UI框架出现的问题能及时定位,能对UI框架进行高度的自定义,使其更加贴合业务开发。
在开发自定义组件库前先来看下思维导图,文章将主要分为六大步骤。
工程化目录
一个好的工程目录能较好的体现项目的健壮性和可维护性。这里使用Vue/Cli形成目录。
首先电脑需要下载安装好Node,我的Node版本为v14.17.6
下载Vue/CLi:
npm install -g @vue/cli
npm install -g @vue/cli-service-global
创建项目:
vue create linview
配置文件直接默认Vue2:
修改工程化目录:
|-- linview
|-- .gitignore
|-- babel.config.js
|-- package-lock.json
|-- package.json
|-- README.md
|-- vue.config.js //Vue配置文件
|-- components //组件代码
| |-- css //组件样式
| | |-- demo.scss
| |-- lib //组件核心
| |-- card
| |-- demo
| |-- index.js
| |-- src
| |-- Index.vue
|-- docs //文档API
|-- examples //业务代码
| |-- App.vue
| |-- main.js
| |-- assets
| |-- logo.png
|-- public
|-- favicon.ico
|-- index.html
src被修改后需要配置vue的入口文件,新建vue.config.js:
module.exports = {
pages: {
index: {
entry: 'examples/main.js',
template: 'public/index.html',
filename:'index.html'
}
}
}
如果按照先前的步骤,到这里会提示sass-loader没有安装。
npm i sass-loader -D
如果按照上面命令,又会出现如下错误:
原因是版本过高,我们需要换下版本:
卸载:
npm uninstall sass-loader -D
安装:
npm i sass-loader@5 -D
如果此时运行,可能又会提示如下错误
原因是没有安装node-sass。
npm i node-sass@4 -D
运行项目,没有报错,至此,工程化目录已经基本完成。
代码样式编写
在正式组件开发之前,我们应该写一个Demo组件测试一下项目是否能跑起来。
linview/components/lib/demo/src/Index.vue:
<template>
<div class="l-demo">
Demo
</div>
</template>
<script>
export default {
name:'Demo'
}
</script>
linview/components/css/demo.scss:
.l-demo{
color: aqua;
}
linview/examplesma/main.js
import Vue from 'vue'
import '../components/css/demo.scss'//引入样式
import Demo from '../components/lib/demo/src/Index.vue'
import App from './App.vue'
Vue.config.productionTip = false
Vue.use(Demo)
new Vue({
render: h => h(App),
}).$mount('#app')
linview/examplesma/App.vue:
<template>
<div id="app">
<Demo></Demo>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
如果按照上面的代码书写会发现,组件不起作用,可以我们在使用第三方UI组件库的时候确实是Use这个组件就可以使用了,我们打开控制台,会发现报以下错误:
大概意思是组件没有注册,那么接下来对组件的全局注册:
修改linview/examplesma/main.js:
import '../components/css/demo.scss'
import Demo from '../components/lib/demo/index.js'
//Vue.component('name',Demo)
import App from './App.vue'
Vue.config.productionTip = false
Vue.use(Demo)//Demo.install -> vue.component
new Vue({
render: h => h(App),
}).$mount('#app')
增加linview/components/lib/demo/index.js
import Demo from './src/Index.vue';
Demo.install = function (Vue) {
Vue.component(Demo.name, Demo)
}
export default Demo
运行项目,没有报错,则Demo组件完成。
写到这里会发现,其实就是一个全局的组件,但是在开发过程中,首先要考虑的组件的通用性,再而是组件的可维护,组件库的意义个人觉得是在于便于开发,想想我只要npm install一个包,然后我在main引入,在组件传递几个数值,一个非常好看的页面就形成,何乐而不为呢
接下来编写正式的第一个卡片组件:
Index.vue
<template>
<div class="l-card" :style="width?{width:width+'px'}:{}">
<div class="l-card-img" :style="imgHeight?{height:imgHeight+'px'}:{}">
<img :src="imgSrc" alt="img">
</div>
<div v-if="summary" class="l-card-summary">
{{summary}}
</div>
<div v-else class="l-card-summary">
<slot></slot>
</div>
<slot name="footer"></slot>
</div>
</template>
<script>
export default {
name:'Card',
props:{
//卡片宽度
width:{
type:Number,
default:0
},
//卡片图片资源
imgSrc:{
type:String,
default:''
},
//卡片图片高度
imgHeight:{
type:Number,
default:0
},
//卡片概要
summary:{
type:String,
default:''
}
}
}
</script>
<style lang="scss" scoped>
</style>
card.scss
.l-card{
width: 270px;
border-radius: 8px;
background-color: #fff;
overflow: hidden;
box-shadow: 0 6px 10px 0 rgba(95,101,105,0.15);
padding-bottom: 8px;
&-img{
height: 152px;
img{
width: 100%;
height: 100%;
}
}
&-summary{
padding: 8px;
text-align: left;
font-size: 14px;
}
}
index.js
import Card from './src/Index.vue';
Card.install = function (Vue) {
Vue.component(Card.name, Card)
}
export default Card
组件测试
编写好的组件需要在本地进行测试才能发布,这里的测试不是非常正规的单元测试,而是直接使用examples目录下的App文件测试框架。
App.vue
<template>
<div id="app">
<Card imgSrc="./img/card.png" summary="Vue.js 源码全方位深入解析 全面深入理解Vue实现原理,掌握源码分析技巧"></Card>
<br />
<Card imgSrc="./img/card.png" summary="Vue.js 源码全方位深入解析 全面深入理解Vue实现原理,掌握源码分析技巧">
<template v-slot:footer>
<div class="footer">
<div class="level">高级 · 4256人报名 </div>
<div class="price">¥488.00</div>
</div>
</template>
</Card>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
<style>
#app {
margin: 0 auto;
width: 270px;
}
.footer {
padding: 0 8px;
font-size: 12px;
text-align: left;
}
.level {
color: #9199a1;
margin-bottom: 8px;
}
.price {
color: #f01414;
}
</style>
预览效果:
打包
完成以上的步骤,其实已经可以发布到NPM上,但是有两个问题,
- 现在使用该组件的时候,只能一个一个组件引用,不能全局引入,这需要两个入口文件,一个SCSS,一个JS。
- 项目没有打包优化,包体积大
对于以上问题,我使用WebPack来打包JS,Gulp打包CSS
安装依赖:
npm i gulp@4 gulp-minify-css gulp-sass@4 webpack-cli@4 -D
build/webpack.component.js
const {VueLoaderPlugin} = require('vue-loader')
const glob = require('glob');
const utlis =require('./utlis')
const list = {}
async function makeList (dirPath, list) {
const files = glob.sync(`${dirPath}/**/index.js`)
//[ 'components/lib/card/index.js', 'components/lib/demo/index.js' ]
console.log('files', files);
for (let file of files) {
//component card component demo
const component = file.split(/[/.]/)[2];
console.log('component', component);
list[component] = `./${file}`;
}
console.log(list);
}
makeList('components/lib',list)
module.exports = {
entry: list,//入口文件
mode:'production',//生产模式
output: {
filename: '[name].umd.js',
path: utlis.DIST_PATH,
library: 'lview',
libraryTarget:'umd'
},
plugins: [
new VueLoaderPlugin()
],
module: {
rules: [
{
test: /\.vue$/,
use: [
{
loader:'vue-loader'
}
]
}
]
}
}
build/utlis.js
const path = require('path')
exports.resolve = function resolve (dir) {
return path.join(__dirname, '..', dir)
}
exports.APP_PATH = exports.resolve('src')
exports.DIST_PATH = exports.resolve('dist')
bulid/gulpfile.js
const utlis =require('./utlis')
const gulp = require('gulp')
const sass = require('gulp-sass')
const minifyCSS = require('gulp-minify-css')
gulp.task('sass', async function () {
return gulp.src('../components/css/**/*.scss')
.pipe(sass())
.pipe(minifyCSS())
.pipe(gulp.dest(utlis.DIST_PATH+'/css'))
})
Script:
"build": "npm run build:js && npm run build:css",
"build:js": "webpack --config ./build/webpack.component.js",
"build:css": "npx gulp sass --gulpfile build/gulpfile.js",
打包完成后会在项目根目录形成dist文件夹,这是等会我们要NPM上传的。
NPM发布
本地编写测试好的组件就要发布到NPM上,方便别人的下载使用,在使用之前,需要个人先注册好NPM账号。
- 首先需要配置package.json文件:
"name": "linview",
"version": "0.1.2",
"description": "个人开发者UI组件库",
"main": "dist/index.umd.js",
"keywords": [
"linview",
"UI",
"vue"
],
"author": "bamboo_lsf",
"files": [
"dist",
"components"
],
name:包的名字
version:版本,低版本不能覆盖高版本
description:描述,在你搜索这个包的时候会出现的描述
main:入口主文件,当别人引用包的名字时候,其实就是用的这个文件
keywords:搜索的关键词
author:作者
files:要上传的文件
-
书写好根目录的RedeMe文件,它是npm包上的RedeMe文件。
-
把本地的下载源切换会官方,否则无法登录。
npm config set registry https://registry.npmjs.org/
window上如果登录失败可以在要登录的窗口上在次执行,因为我在切回官方源后,登录报错。
登录命令:
npm login
正常情况输入用户名,密码,邮箱即可登录完成。
接下来使用npm publish上传,随后我们就可以在Npm看到自己发布的包了
文档站点
一个好的UI框架,当然是需要一个文档展示,这样才方便以后的开发与维护。接下来使用VuePress来形成文档的站点。
首先我们需要明白一个概念,文档和项目代码是在一起的,那么我们应该把文档的静态文件和仓库代码放一起,怎么做到呢,其实,我们只需要把文档形成的静态文件和项目代码放在仓库的不同分支即可。这样两者就不会冲突
安装VuePress:
npm i VuePress -D
生成以下文件和文件夹:
config.js
module.exports = {
title: 'LinView',
base:'/linview/',
description: 'Just playing around',
themeConfig: {
//导航
nav: [
{ text: '首页', link: '/' },
{ text: '指南', link: '/componentDocs/'},
{text:'更新日志',link:'/update/index'},
{ text: 'GitHub', link: 'https://github.com/LSFCXZ/linview' },
{ text: 'NPM', link: 'https://www.npmjs.com/package/linview' },
],
sidebar: {
'/componentDocs/': [
{
title: '简介',
path:'/componentDocs/'
},
{
title: '快速开始',
path:'/componentDocs/start'
},
{
title: '卡片',
path:'/componentDocs/card'
}
]
}
}
}
注意:base是你仓库的名字,如果你的站点没有后缀,那么可以不填
根目录index.md
---
home: true
heroImage:
heroText: LinView
tagline: 简洁、美观、开源的UI框架
actionText: 快速上手 →
actionLink: /componentDocs/start
features:
- title: 简洁至上
details: 以 Markdown 为中心的项目结构,以最少的配置帮助你专注于写作。
- title: 美观
details: 享受 Vue + webpack 的开发体验,在 Markdown 中使用 Vue 组件,同时可以使用 Vue 来开发自定义主题。
- title: 开源免费
details: VuePress 为每个页面预渲染生成静态的 HTML,同时在页面被加载的时候,将作为 SPA 运行。
footer: MIT Licensed | Copyright © 2021-present BambooLSF
---
具体可以参考vuepress的官网配置:vuepress.vuejs.org/zh/
接下来就是配置脚本:
"docs:dev": "vuepress dev docs",
"docs:build": "vuepress build docs",
"depoly": "bash ./build/deploy.sh"
docs:dev:运行vuepress
docs:build:打包
depoly:部署
接下来就是部署到githubpage:
build/deploy.sh
#!/usr/bin/env sh
# 确保脚本抛出遇到的错误
set -e
# 生成静态文件
npm run docs:build
# 进入生成的文件夹
cd docs/.vuepress/dist
# 如果是发布到自定义域名
# echo 'www.example.com' > CNAME
git init
git add -A
git commit -m 'deploy'
# 如果发布到 https://<USERNAME>.github.io
# git push -f git@github.com:<USERNAME>/<USERNAME>.github.io.git master
# 如果发布到 https://<USERNAME>.github.io/<REPO>
git push -f git@github.com:LSFCXZ/linview.git master:gh-pages
cd -
注意: git push是要换成你的github地址
执行命令:
npm run depoly
如果在window上无法执行,那么使用git命令窗口即可,这样站点已经完成。
再把我们的项目代码上传:git init ,git add . ,git commit -m '''第一次提交' ,git push等一把梭上传代码
至此,UI框架基本完成,可以新建项目,npm install 自己的UI库进行测试。
版本问题
为了避免因为版本问题引起的错误,这里列举开发时版本环境:
"dependencies": {
"core-js": "^3.6.5",
"vue": "^2.6.11"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"gulp": "^4.0.2",
"gulp-minify-css": "^1.2.4",
"gulp-sass": "^4.1.1",
"node-sass": "^4.14.1",
"sass-loader": "^5.0.1",
"vue-loader": "^15.9.8",
"vue-template-compiler": "^2.6.11",
"vuepress": "^1.8.2",
"webpack-cli": "^4.8.0"
}
总结
经过已上步骤,基本已经形成一个较为完整的UI框架,接下来就是不断优化更新框架和文档
LinView是我个人开发的开源UI框架,目前是处于探索尝试阶段,如果你觉得这篇文章对你有帮助,有一定的启发,欢迎给我的仓库点个star。
Github开源地址:github.com/LSFCXZ/linv…
官方文档站点:lsfcxz.github.io/linview/
个人博客:lsfcxz.gitee.io/