前言 🍊
市面上目前已有各种各样的UI组件库,他们的强大毋庸置疑。但是有时候我们有必要开发一套属于自己团队的定制化组件库。还有时候原有的组件不能满足我们的各种需求,就需要在原有的组件上进行改造。
所以本文的目的就是让读者能通过此文,小能做一个简单的插件供人使用,大能架构和维护一个组件库不在话下。欢迎指点,有疑问的也可以欢迎下方评论留言。
技术栈🍊
vue-cli3的基本创建和打包技巧 vue的基本语法 npm的发布
完整项目目录结构🍊
vase-ui
├─ .eslintrc.js
├─ .gitignore
├─ .npmignore
├─ babel.config.js
├─ deploy.sh
├─ docs // vuepress开发目录
│ ├─ .vuepress
│ │ ├─ components // 在markdown中可以使用的vue组件
│ │ │ ├─ vs-button.vue
│ │ │ └─ vs-home.vue
│ │ ├─ config.js // vurepess配置修改入口,包括左边sidebar,右上方nav导航菜单等
│ │ └─ dist // vuepress打包目录
│ │ ├─ 404.html
│ │ ├─ assets
│ │ │ ├─ css
│ │ │ ├─ img
│ │ │ └─ js
│ │ ├─ index.html
│ │ └─ views // vuepress视图文件,格式是markdown
│ │ ├─ components
│ │ │ └─ basic
│ │ │ └─ index.html
│ │ └─ guide
│ │ ├─ get-started.html
│ │ └─ install.html
│ ├─ README.md
│ └─ views
│ ├─ components
│ │ └─ basic
│ │ └─ README.md
│ └─ guide
│ ├─ get-started.md
│ └─ install.md
├─ package-lock.json
├─ package.json // 与npm发布相关,记录版本号,包入口文件地址
├─ packages // 组件库源码目录
│ ├─ button
│ │ ├─ index.js
│ │ └─ src
│ │ └─ button.vue
│ ├─ fonts
│ │ ├─ font.scss
│ │ └─ src
│ │ ├─ element-icons.ttf
│ │ └─ element-icons.woff
│ ├─ index.js // 组件库源码组件入口文件,执行npm run build的目标文件
│ └─ keep-alive
│ └─ index.js
├─ public //公共资源入口,如favicon
│ ├─ favicon.ico
│ └─ index.html
├─ README.md
├─ test
│ ├─ App.vue
│ ├─ main.js
│ └─ TestButton.vue
├─ types
│ ├─ button.d.ts
│ ├─ component.d.ts
│ ├─ index.d.ts
│ └─ vase-ui.d.ts
├─ vue.config.js
└─ yarn.lock
项目规划🍊
在指定目录中使用命令创建一个默认的项目,或者根据自己需要自己选择。
创建项目
项目名起自己觉得有意义的名字就行了
$ vue create vase-ui
注意:由于我们是开发一个第三方依赖库,我们选择 Manually select features
。
选择那些特性需要安装在项目中
(*) Babel
( ) TypeScript
( ) Progressive Web App (PWA) Support
( ) Router
( ) Vuex
(*) CSS Pre-processors
(*) Linter / Formatter
( ) Unit Testing
( ) E2E Testing
复制代码系统默认的包含了基本的 Babel + ESLint
设置的 preset
,我们只需要选择CSS配置。移动键盘上下键选择需要的特性,按下键盘空格键即可选中
安装哪一种 CSS 预处理语言
Sass/SCSS (with dart-sass)
Sass/SCSS (with node-sass)
Less
Stylus
复制代码由于Element UI中的样式采用Sass,所以我们选择第一项即可
为什么不选择第二项呢?
因为dart-sass
比node-sass
更好下载
选择代码风格
ESLint with error prevention only
ESLint + Airbnb config
ESLint + Standard config
ESLint + Prettier
一般选 ESLint + Prettier
的组合
那种方式进行代码格式检测
(*) Lint on save
( ) Lint and fix on commit
复制代码选择Ctrl+S
保存时检测代码格式即可
配置文件生成方式
In dedicated config files
In package.json
看个人喜好,我选第一种
是否保存预配置
Save this as a preset for future projects? (y/N)
复制代码看项目需要,我这里选择 N
。回车后,系统会自动帮我们把选择的配置集成到模板中,然后生成一个完整的项目。
调整目录
这里我们参考element的目录结构
删除src、assets目录,在根目录中创建一个
packages
目录用来存放我们要开发的UI组件;在根目录创建一个test目录用于测试我们自己开发的UI组件(再引入了vuepress之后这个文件夹也不再需要了)。
|-- packages // 将原src目录改为 packages 用于编写存放组件
新增packages目录,该目录未加入webpack编译
注:cli3 提供一个可选的
vue.config.js
配置文件。如果这个文件存在则他会被自动加载,所有的对项目和webpack
的配置,都在这个文件中。
注: vue的package文件夹采用了
monorepo
:monorepo是一种将多个package放在一个repo中的代码管理模式,摒弃了传统的多个package多个repo的模式。关于monorepo可以在https://juejin.im/post/6844903961896435720
这篇文章中了解
webpack配置修改
packages 是我们新增的一个目录,默认是不被 webpack 处理的,所以需要添加配置对该目录的支持。
chainWebpack 是一个函数,会接收一个基于 webpack-chain 的 ChainableConfig 实例。允许对内部的 webpack 配置进行更细粒度的修改。在根目录创建vue.config.js文件,作如下配置:
// 修改 src 为 test
const path = require("path");
module.exports = {
pages: {
index: {
entry: "test/main.js",
template: "public/index.html",
filename: "index.html"
}
},
// 扩展 webpack 配置,使 packages 加入编译
chainWebpack: config => {
config.module
.rule("js")
.include.add(path.resolve(__dirname, "packages"))
.end()
.use("babel")
.loader("babel-loader")
.tap(options => {
return options;
});
}
};
组件编写🍊
button组件
在 packages
目录下,所有的单个组件都以文件夹的形式存储,所有这里创建一个目录button
在 button
目录下创建src
目录存储组件源码在 button
目录下创建index.js
文件对外提供对组件的引用。创建 fonts
文件夹作用是存放element
的一些基础样式
/packages/button/src/button.vue
核心组件代码如下(样式代码这里不贴出,可以在代码仓库找到):
<template>
<button
class="vs-button"
:disabled="disabled"
@click="handleClick"
:class="[
type ? `vs-button--${type}` : '',
buttonSize ? `vs-button--${buttonSize}` : '',
{
'is-plain': plain,
'is-round': round,
'is-circle': circle,
'is-disabled': disabled
}
]"
>
<i :class="icon" v-if="icon"></i>
<!-- 如果没有传入插槽的时候才显示 -->
<span v-if="$slots.default"><slot></slot></span>
</button>
</template>
<script>
export default {
name: "VsButton",
props: {
size: String,
type: {
type: String,
default: "default"
},
plain: {
type: Boolean,
default: false
},
round: {
type: Boolean,
default: false
},
circle: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
icon: {
type: String,
default: ""
}
},
methods: {
handleClick(e) {
this.$emit("click", e);
}
}
};
</script>
<style lang="scss">
</style>
修改 /packages/button/index.js
文件,对外提供引用:
import VsButton from "./src/button.vue";
// 为组件提供 install 安装方法,供按需引入
VsButton.install = function(Vue) {
Vue.component(VsButton.name, VsButton);
};
export default VsButton;
keep-alive组件
介绍:这个组件是对Vue前进刷新后退不刷新,简易页面的堆栈实现
在 packages
目录下创建keep-alive
在 keep-alive
目录下创建index.js
文件在里头用函数式组件并对外提供对组件的引用。
完整代码如下:
import Vue from "vue";
let cacheKey = "cacheTo";
let $router = { beforeEach: () => {} };
// Vue.observable处理使组件的store具有可插拔性
const state = Vue.observable({
caches: []
});
const clearCache = () => {
if (state.caches.length > 0) {
state.caches = [];
}
};
const addCache = name => state.caches.push(name);
const beforeEach = () => {
$router.beforeEach((to, from, next) => {
// 1. 都不是类列表页
// 清空缓存
// 2. 都是类列表页
// 若`to`不在`from`的配置中,清空缓存,同时要新增`to`缓存
// 保留`from`的缓存,新增`to`缓存
// 3. 新路由是类列表页
// 若`from`不在`to`的配置中,清空缓存,新增`to`缓存
// 否则,无需处理
// 4. 旧路由是类列表页
// 若`to`不在`from`的配置中,清空缓存
const toName = to.name;
const toCacheTo = (to.meta || {})[cacheKey];
const isToPageLikeList = toCacheTo && toCacheTo.length > 0;
const fromName = from.name;
const fromCacheTo = (from.meta || {})[cacheKey];
const isFromPageLikeList = fromCacheTo && fromCacheTo.length > 0;
if (!isToPageLikeList && !isFromPageLikeList) {
clearCache();
} else if (isToPageLikeList && isFromPageLikeList) {
if (fromCacheTo.indexOf(toName) === -1) {
clearCache();
}
addCache(toName);
} else if (isToPageLikeList) {
if (toCacheTo.indexOf(fromName) === -1) {
clearCache();
addCache(toName);
}
} else if (isFromPageLikeList) {
if (fromCacheTo.indexOf(toName) === -1) {
clearCache();
}
}
next();
});
};
const VsKeepAlive = {
install(Vue, options = { key: "", router: "" }) {
const { key = "cacheTo", router } = options;
if (key) {
cacheKey = key;
$router = router;
beforeEach();
}
const component = {
name: "VsKeepAlive",
functional: true,
render(h, { children }) {
return h("keep-alive", { props: { include: state.caches } }, children);
}
};
Vue.component("VsKeepAlive", component);
}
};
export default VsKeepAlive;
使用注意事项:
路由配置不能少了name属性,并且这个name需要和组件name一样 cacheTo优先级小于keepAlive,所以,处理这种需求的页面不要设置keepAlive 可以设置2个页面之前仅在相互切换时缓存,不过我还没发现可用的场景
导出组件
整合所有的组件,对外导出,即一个完整的组件库 修改 /packages/index.js
文件,对整个组件库进行导出:
import Button from "./button";
import KeepAlive from "./keep-alive";
import "./fonts/font.scss";
// 存储组件列表
const components = [Button];
// 定义 install 方法,接收 Vue 作为参数。如果使用 use 注册插件,则所有的组件都将被注册
const install = function(Vue, options = { key: "", router: {} }) {
const { key = "cacheTo", router } = options;
// 遍历注册全局组件
components.forEach(function(item) {
if (item.install) {
Vue.use(item);
} else if (item.name) {
Vue.component(item.name, item);
}
});
Vue.use(KeepAlive, { key, router });
};
// 判断是否是直接引入文件
if (typeof window !== "undefined" && window.Vue) {
install(window.Vue);
}
export { Button, KeepAlive };
export default {
// 导出的对象必须具有 install,才能被 Vue.use() 方法安装
version: "0.3.4",
install
};
注:如果需要使用我们的keep-alive组件时,需要在注册时传入router参数。
这里我们可以看下element是怎么对这一块进行定义的:
npm发布🍊
package.json
中的script
下新增一条编译为库的命令,然后就可以使用npm run lib
命令进行打包啦
"scripts": {
"lib": "vue-cli-service build --target lib --name vase-ui --dest lib packages/index.js"
},
注:
--target
: 构建目标,默认为应用模式。这里修改为 lib 启用库模式。dest
: 输出目录,默认 dist。这里我们改成 lib[entry]
: 最后一个参数为入口文件,默认为 src/App.vue。这里我们指定编译 packages/ 组件库目录
执行编译库命令
npm run lib
配置 package.json
文件中发布到npm
的字段
name: 包名,该名字是唯一的。可在 npm 官网搜索名字,如果存在则需换个名字。 version: 版本号,每次发布至 npm 需要修改版本号,不能和历史版本号相同。 description: 描述。 main: 入口文件,该字段需指向我们最终编译后的包文件。 keyword:关键字,以空格分离希望用户最终搜索的词。 author:作者 private:是否私有,需要修改为 false 才能发布到 npm license: 开源协议 希望打包库的产生文件 browserslist: 指定了项目的目标浏览器的范围 repository: 指定代码所在的位置。这对想要贡献的人很有帮助。如果git repo在GitHub上,那么该npm docs 命令将能够找到你。
以下为我的参考设置
{
"name": "vase-ui",
"version": "0.3.4",
"description": "A Component Library for Vue.js.",
"private": false,
"main": "lib/vase-ui.common.js",
"files": [
"lib",
"src",
"packages",
"types"
],
"repository": {
"type": "git",
"url": "git@github.com:JohnYu588/vase-ui.git"
},
"author": "yzx",
"license": "MIT",
"browserslist": [
"> 1%",
"last 2 versions"
]
}
添加 .npmignore 文件,设置忽略发布文件
# 忽略目录
test/
packages/
public/
docs/
node_modules/
# 忽略指定文件
vue.config.js
babel.config.js
*.map
.editorconfig.js
发布npm
到npm官网注册一个账号 执行 npm login
输入账号密码登录执行 npm publish
上传发布成功后稍等几分钟,即可在 npm 官网
搜索到。以下是刚提交的
注意
一定要在package.json的scripts中添加main方便其他人下载时找到对应打包的文件 上传到npm上时,要将package.json中的private属性值改为false 修改源码后发布到npm时一定要更改项目的版本号
官网制作🍊
使用vue press
在原有项目中使用
# 安装依赖
npm install -D vuepress
# 创建一个 docs 目录
mkdir docs
在 package.json
中进行脚本配置
"scripts": {
"docs:dev": "vuepress dev docs",
"docs:build": "vuepress build docs"
},
简单配置 在 docs/.vuepress
下新建文件config.js
module.exports = {
base: '/vase-ui/',
title: 'Vase UI',
description: 'Inspiration from heian vase',
head: [['link', { rel: 'icon', href: '/favicon.ico' }]],
themeConfig: {
nav: [
{ text: 'Home', link: '/' },
{ text: 'Github', link: 'https://github.com/JohnYu588/vase-ui/' },
],
sidebar: [
{
title: '开发指南',
collapsable: true,
children: ['views/guide/install.md', 'views/guide/get-started.md'],
},
{
title: '组件',
collapsable: true,
children: ['views/components/basic/'],
},
],
},
};
使用vue组件
官网中提到,所有在 .vuepress/components
中找到的 *.vue
文件将会自动地被注册为全局的异步组件,可以在markdown
中引用,我们可以在这里编写展示案例 vue文件中的代码高亮我用的是vue-highlightjs
:
在/docs/.vuepress/components/
下创建按钮vs-botton.vue
文件代码如下:
<template>
<div>
<h3>基础用法</h3>
<vs-button>default</vs-button>
<vs-button type="primary">primary</vs-button>
<vs-button type="info">info</vs-button>
<vs-button type="success">success</vs-button>
<vs-button type="warning">warning</vs-button>
<vs-button type="danger">danger</vs-button>
<pre v-highlightjs><code class="vue">{{code1}}</code></pre>
</div>
</template>
<script>
import btn from '../../../packages/button/src/button';
import Vue from 'vue'
import VueHighlightJS from 'vue-highlightjs';
Vue.use(VueHighlightJS);
export default {data() {
return {
code1: `
<vs-button>default</vs-button>
<vs-button type="primary">primary</vs-button>
`
.replace(/^\s*/gm, '')
.trim(),
code2: `
<s-button disabled type="primary">disabled</s-button>
`
.replace(/^\s*/gm, '')
.trim(),
code3: `
<s-button icon="home" type="primary">home</s-button>
<s-button icon="phone-fill" type="primary" icon-position="right">call</s-button>
<s-button icon="visible" type="primary">show password</s-button>
`
.replace(/^\s*/gm, '')
.trim(),
code4: `
<s-button loading icon="download" type="primary">加载中</s-button>
`
.replace(/^\s*/gm, '')
.trim(),
code5: `
<s-button-group>
<s-button icon="left" icon-position="left">prev</s-button>
<s-button>middle</s-button>
<s-button icon="right" icon-position="right">next</s-button>
</s-button-group>
`
.replace(/^ {8}/gm, '')
.trim(),
};
},
components: {
'vs-button': btn,
},
};
</script>
// 样式这里不贴出
<style lang="scss" scoped></style>
编写文档
由于所有的页面在生成静态 HTML 时都需要通过 Node.js 服务端渲染,对于SSR 不怎么友好的组件(比如包含了自定义指令),你可以将它们包裹在内置的 ClientOnly 组件中,而且注意因为是ssr,组件内部beforeCreate, created生命周期钩子函数访问不到浏览器 / DOM 的 API,只能在beforeMount和mounted中调用。
/docs/views/components/basic
下创建README.md
:
---
title: 'Basic 基础'
sidebarDepth: 2
---
## Button 按钮
<ClientOnly>
<vs-button/>
<font size=5>Attributes</font>
| 参数| 说明 | 类型 | 可选值 | 默认值 |
| :------ | ------ | ------ | ------ | ------ |
| type | 按钮类型 | string |primary, info, success, warning, danger | - |
| disabled | 按钮是否禁用 | boolean |- | false |
| icon | 按钮上图标名称 | string |- | - |
| icon-position | 图标在按钮的左右位置 | string|left, right | - |
| loading | 显示加载中图标 | boolean |- | false |
</ClientOnly>
注:参考上面在guide目录下创建安装帮助和开始页的文档(具体看我的git)
部署到github
官网上介绍的很清楚,点这里。 在项目根目录下新增deploy.sh,windows下直接命令行运行./deploy.sh即可发布到github pages上。
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:JohnYu588/vase-ui.git master:gh-pages
cd -
预览 执行 yarn docs:dev
命令预览官网:
上传完毕后在https://johnyu588.github.io/vase-ui/
上直接可以看到
使用新发布的组件库🍊
npm install vase-ui -S
引用 在项目的main.js中引入有两种方式
全局注册
import VaseUI from "vase-ui";
Vue.use(VaseUI, { router });
按需引入
import { Button, KeepAlive } from "vase-ui";
Vue.use(Button)
.use(KeepAlive, { router });
使用
<vs-button icon="vs-icon-check" circle plain type="primary">测试</vs-button>
关于keep-alive的使用参考Vue前进刷新后退不刷新,简易页面堆栈实现[1]
git地址:https://github.com/JohnYu588/vase-ui
参考🍊
参考资料
Vue前进刷新后退不刷新,简易页面堆栈实现: https://juejin.im/post/6844904002526642184。
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: johnYu 原文链接:https://juejin.im/post/6847902225063215111