vite2 + vue3 开发组件库
使用vite2 + vue3开发一个组件库
完成后的效果图展示:
一、搭建一个 Vite 项目
参照vite官网
通过附加的命令行选项直接指定项目名称和你想要使用的模板。例如,要构建一个 Vite + Vue 项目
# yarn yarn create vite my-baseui --template vue
接下来执行
cd my-baseui yarn yarn dev
运行后打开页面长这样子:

在根目录下创建packages文件夹,目录结构如下:

描述:
packages/components: 存放组件。packages/styles: 存放全局样式和组件样式。packages/index.js
二、组件编写
一、components中,编写一个测试的button组件

二、components中的 button组件index.vue代码
<template>
<button
class="m-button"
:class="styleClass"
:disabled="disabled"
:round="round">
<slot></slot>
</button>
</template>
<script>
import { computed } from 'vue';
export default {
name: 'm-button',
props: {
type: {
type: String,
default: 'default',
validator(value) {
return ['default','primary', 'success', 'info', 'warning', 'danger','text'].indexOf(value) > -1;
}
},
disabled: {
type: Boolean,
default: false
},
round: {
type: Boolean,
default: false
}
},
setup(props) {
const styleClass = computed(() => {
return {
[`m-button--${props.type}`]: props.type,
'is-disabled': props.disabled,
'is-round': props.round
}
})
return {
styleClass
}
}
}
</script>
<style lang="scss" scoped>
@import '../../styles/components/button.scss';
</style>
三、styles中的button样式代码
.m-button {
display: inline-block;
padding: 12px 20px;
margin-right: 10px;
border: 1px solid #d9d9d9;
background: #fff;
color: #333;
cursor: pointer;
}
.is-disabled{
color: #c0c4cc;
cursor: not-allowed;
background-color: #fff;
border-color: #ebeef5;
}
.m-button--primary{
color: #fff;
background-color: #409eff;
border-color: #409eff;
}
.m-button--success {
color: #fff;
background-color: #67c23a;
border-color: #67c23a;
}
.m-button--info {
color: #fff;
background-color: #909399;
border-color: #909399;
}
.m-button--warning {
color: #fff;
background-color: #e6a23c;
border-color: #e6a23c;
}
.m-button--danger {
color: #fff;
background-color: #f56c6c;
border-color: #f56c6c;
}
.m-button--text {
border-color: transparent;
color: #409eff;
background: transparent;
padding-left: 0;
padding-right: 0;
}
// 圆角
.is-round{
border-radius: 20px;
}
四、button组件中 index.js文件,用来把写的组件暴露出去
import mButton from './index.vue'
mButton.install = app => {
app.component(mButton.name, mButton)
}
export default mButton;
五、packages文件夹下新建index.js文件,用来管理所有的组件
import mButton from "./components/button/index";
import mInput from "./components/input/index";
// 组件列表
const components = [
mButton,
mInput
]
// 定义 install 方法,接收 Vue 作为参数。如果使用 use 注册插件,那么所有的组件都会被注册
const install = (Vue) => {
// 判断是否安装
if (install.installed) return
// 遍历注册全局组件
components.map(component => Vue.component(component.name, component))
}
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
export default {
install,
mButton,
mInput
}
三、组件引入
一、在src 的main.js中引入
import { createApp } from 'vue'
import App from './App.vue'
import myBaseui from "../packages/index";
const app = createApp(App);
app.use(myBaseui)
app.mount('#app')
二、在src/App.vue中使用组件
改写 src/App.vue,引入button
<template>
<div>
<m-button>默认按钮</m-button>
<m-button type="primary">主要按钮</m-button>
<m-button type="success">成功按钮</m-button>
</div>
</template>
浏览器上查看效果,可以看到已经成功了

一个组件库有很多个组件,每个组件都应该有它独立的文档。这个文档不仅有对组件各项功能的描述,更应该具有组件预览、组件代码查看等功能。同时为了良好的组件开发体验,我们希望这个文档是实时的,这边修改代码,那边就可以在文档里实时地看到最新的效果。接下来我们就来实现这么一个功能。
组件的文档一般是用 Markdown 来写。这里我们使用一个插件 vite-plugin-md
vite pulgin 将 md 文件转换成 vue 组件渲染的主要流程是:
- 配置 vue router 路由,指向 .md 文件
- 编写 vite 插件将 .md 文件解析成 vue 文件字符串
- 最后由 vite 的插件@vitejs/plugin-vue 将 vue 文件字符串编译成函数组件返回给前端
在src下,新建router/index.js文件
四、配置 vue router 路由,指向 .md 文件
import { createRouter, createWebHashHistory } from "vue-router";
const routes = [
{
path: '/',
name: '组件页面',
component: () => import('@/views/home.vue'),
children: [
{
path: '/button',
name: 'Button 按钮',
component: () => import('/packages/components/button/doc/doc.md')
},
{
path: '/input',
name: 'Input 输入框',
component: () => import('/packages/components/input/doc/doc.md')
}
]
}
]
const router = createRouter({
history: createWebHashHistory(),
routes,
});
export default router;
main.js中引入路由
import { createApp } from 'vue'
import App from './App.vue'
import router from "./router";
import myBaseui from "../packages/index";
const app = createApp(App);
app.use(router)
app.use(myBaseui)
app.mount('#app')
这里路由引入的是Markdown 文件,这个在默认的 Vite 配置里是无效的,我们需要引入 vite-plugin-md 插件来解析 Markdown 文件并把它变成 Vue 文件。
五、编写 vite 插件将 .md 文件解析成 vue 文件
vite.config.js 如下:
import { defineConfig } from 'vite'
import Vue from '@vitejs/plugin-vue';
import Markdown from 'vite-plugin-md';
import { resolve } from "path";
// https://vitejs.dev/config/
export default defineConfig({
base: '/my-baseui/',
plugins: [
Vue({
include: [/\.vue$/, /\.md$/],
}),
Markdown(),
],
resolve: {
alias: {
"@": resolve(__dirname, "src"),
'~': resolve(__dirname, "packages")
}
},
})
这样配置以后,任意的 Markdown 文件都能像一个 Vue 文件一样被使用了
接下来,编写home.vue页面,增加侧边栏和主区域
<template>
<div class="main-container">
<div class="sidebar">
<ul v-for="item in routes" :key="item">
<li
v-for="(ele,index) in item.children"
:key="ele"
:class="{'active': mIndex == index}"
@click="goPath(ele,index)">
{{ele.name}}
</li>
</ul>
</div>
<main class="app-main">
<router-view></router-view>
</main>
</div>
</template>
<script setup>
import { computed, ref, reactive, onMounted } from 'vue';
import { useRouter } from 'vue-router';
const router = useRouter()
const mIndex = ref(sessionStorage.getItem("mIndex") || '0');
const routes = computed(() => router.options.routes)
const goPath = (ele,index) => {
mIndex.value = index
router.push({
path:ele.path
})
sessionStorage.setItem("mIndex", index);
}
</script>
<style lang="scss" scoped>
.main-container{
display: flex;
justify-content: space-between;
overflow: hidden;
.sidebar{
width: 200px;
height: 100%;
border-right: 1px solid #eee;
text-align: center;
ul{
li{
height: 50px;
line-height: 50px;
cursor: pointer;
}
.active{
color: #409eff;
background-color: #ECF5FF;
border-right: 1px solid #409eff;
}
}
}
.app-main{
flex: 1;
padding: 20px 50px;
overflow-y: auto;
}
}
</style>
把App.vue改下
<template> <router-view></router-view> </template> <script setup> </script> <style> </style>
往 /packages/components/button/doc/doc.md 里面随便写点东西,
完成后,浏览器查看有效果了。

vite-plugin-md 是支持在 Markdown 里面写 setup 函数的!因此我们可以把需要执行 JS 逻辑的代码封装成一个组件,然后在 Markdown 里通过 setup 来引入。
在packages/components/button/doc 目录下新建一个 demo.vue
<template>
<div>
<m-button>默认按钮</m-button>
<m-button type="primary">主要按钮</m-button>
<m-button type="success">成功按钮</m-button>
<m-button type="danger">危险按钮</m-button>
</div>
</template>
然后在md文件中引入
<script setup> import demo from './demo.vue'; </script> # Button 按钮 <demo/>
完成后,查看页面显示:

六、代码预览功能
接下来我们来实现代码预览功能,虽然说代码预览也很简单,可以直接在 Markdown 中贴代码,但是代码又写一遍就显得很繁琐了
有没有方法可以直接把demo.vue中的代码展示出来呢?
答案是可以的。
在 Vite 的开发文档里有记载到,它支持在资源的末尾加上一个后缀来控制所引入资源的类型。比如可以通过 import xx from 'xx?raw' 以字符串形式引入 xx 文件。基于这个能力,我们可以编写一个 <Preview /> 组件来获取所需要展示的文件源码。
一、新建一个 Preview.vue 文件
<template>
<div class="pre-code-box">
<transition name="slide-fade">
<pre
class="language-html"
v-if="showCode"
>
<code class="language-html">{{ sourceCode }}</code>
</pre>
</transition>
<div class="showCode" @click="showOrhideCode">
<i :class="iconClass"></i>
<span>{{ showCode ? "隐藏代码" : "显示代码" }}</span>
</div>
</div>
</template>
<script setup>
import { computed, onMounted, ref } from "vue";
const props = defineProps({
compName: {
type: String,
default: "",
require: true,
},
demoName: {
type: String,
default: "",
require: true,
},
});
const showCode = ref(false);
const sourceCode = ref("");
const iconClass = computed(() => {
return [
'iconfont',
showCode.value ? 'icon-arrow-up-filling' : 'icon-arrow-down-filling'
]
})
const showOrhideCode = () => {
showCode.value = !showCode.value;
}
const getSourceCode = async () => {
let msg = await import(/* @vite-ignore */ `/packages/components/${props.compName}/doc/${props.demoName}.vue?raw`)
// console.log(msg.default);
sourceCode.value = msg.default
}
onMounted(() => {
getSourceCode()
})
</script>
这里需要加 @vite-ignore 的注释是因为 Vite 基于 Rollup,在 Rollup 当中动态 import 是被要求传入确定的路径,不能是这种动态拼接的路径。此处加上该注释则会忽略 Rollup 的要求而直接支持该写法。
但是这样的写法,在开发模式下可行,build打包到线上后报错,拿不到资源,我们可以通过判断环境变量,在 build 模式下通过 fetch 请求文件的源码来绕过,我们后面再讲。
二、在md文件中引入preview
<script setup> import demo from './demo.vue'; import preview from '@/components/preview.vue'; </script> # Button 按钮 <demo/> <preview compName="button" demoName="demo"/>
查看页面效果:

我们发现,样式没有高亮效果,接下来下载插件 highlight.js 来实现代码高亮
三、main.js中引入 highlight.js ,样式有很多种,我们随便找个看看效果
import { createApp } from 'vue'
import App from './App.vue'
import router from "./router";
import myBaseui from "../packages/index";
import hljs from "highlight.js";
import "highlight.js/styles/color-brewer.css";
const app = createApp(App);
app.use(router)
app.use(myBaseui)
app.directive("highlight", function (el) {
const blocks = el.querySelectorAll("pre code");
blocks.forEach((block) => {
hljs.highlightBlock(block);
});
});
app.mount('#app')
preview.vue中加入 v-highlight 自定义指令
<pre
class="language-html"
v-if="showCode"
v-highlight
>
<code class="language-html">{{ sourceCode }}</code>
</pre>
调整后,页面高亮效果出来了

到这里,我们的组件库基本结构算是完成了,接下来,我们上传到github,部署静态站点。
七、部署站点
查看vite文档,在vite.config.js 中加入 base: '/my-baseui/',

执行npm run build 时,默认会打包成dist文件,我们把输出目录改成docs
vite.config.js加如下配置:
build: {
outDir: 'docs'
},
接下来,在你的项目中,创建一个 deploy.sh 脚本,包含以下内容(注意高亮的行,按需使用),运行脚本来部署站点
#!/usr/bin/env sh # 发生错误时终止 set -e # 构建 npm run build:docs # 进入构建文件夹 cd docs # 如果你要部署到自定义域名 # 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 main # 如果你要部署在 https://<USERNAME>.github.io/<REPO> git push -f git@github.com:wangibook/my-baseui.git master:gh-pages cd -
packages.json中的 scripts 修改下:
"scripts": {
"dev": "vite",
"build:docs": "vite build",
"preview": "vite preview",
"deploy": "bash deploy.sh"
},
之后执行 npm run deploy 命令时,会把docs文件夹打包上传到github的 gh-pages 分支上
切换到gh-pages分支,可以看到看到内容就是我们打包的docs文件夹下的内容,
点击Settings--->Pages---->Source选择gh-pages分支然后点击链接https://wangibook.github.io/my-baseui/,

此时预览代码会报错,拿不到资源,其原因是由于 Rollup 无法进行静态分析,因此无法在构建阶段处理需要动态 import 的文件,导致会出现找不到对应资源。
我们可以通过判断环境变量,在 build 模式下通过 fetch 请求文件的源码来绕过。
把preview.vue做下修改:
const isDev = import.meta.env.MODE === 'development';
const getSourceCode = async () => {
if(isDev) {
let msg = await import(/* @vite-ignore */ `/packages/components/${props.compName}/doc/${props.demoName}.vue?raw`)
// console.log(msg.default);
sourceCode.value = msg.default
} else {
sourceCode.value = await fetch(`/my-baseui/components/${props.compName}/doc/${props.demoName}.vue`).then(res => res.text());
}
}
打包后的文件夹里并没有components文件,我们得把packages下的components文件加入进去

每次复制粘贴这个components就会显得很麻烦,在这里可以用一个插件自动帮我们实现,下载插件 copy-dir
新建config/copyDocs.js
let copydir = require('copy-dir')
copydir.sync(
process.cwd() + '/packages/components',
process.cwd() + '/docs/components',
{
utimes: true,
mode: true,
cover: true
},
function (err) {
if (err) throw err
console.log('done')
}
)
再次执行构建命令 npm run deploy, 等一会儿查看部署的站点,代码预览可以拿到资源了。
八、分开文档和库的构建逻辑
我们目前打包出来的文件是交互文档docs,为了构建一个 my-baseui 组件库并发布到 npm,可供别人下载使用,我们需要将构建的逻辑分开
在根目录下添加一个 /build 目录,依次写入 base.config.js,dist.config.js,docs.config.js,分别为基础配置、库配置和文档配置

base.config.js:
基础配置,需要确定路径别名、配置 Vue 插件和 Markdown 插件用于对应文件的解析。
import { defineConfig } from 'vite';
import Vue from '@vitejs/plugin-vue';
import Markdown from 'vite-plugin-md';
import { resolve } from "path";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
Vue({
include: [/\.vue$/, /\.md$/],
}),
Markdown(),
],
resolve: {
alias: {
"@": resolve(__dirname, "../src")
}
},
})
dist.config.js:
import baseConfig from './base.config';
import { defineConfig } from 'vite';
export default defineConfig({
...baseConfig,
build: {
outDir: 'dist',
rollupOptions: {
// 请确保外部化那些你的库中不需要的依赖
external: ["vue"],
output: {
// 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
globals: {
vue: "Vue",
}
},
},
lib: {
entry: "./packages/index.js",
name: "my-baseui",
},
}
});
docs.config.js:
import baseConfig from './base.config';
import { defineConfig } from 'vite';
export default defineConfig({
...baseConfig,
base: '/my-baseui/',
build: {
outDir: 'docs',
},
});
完成了上面这些构建配置以后,修改一下packages.json
"scripts": {
"dev": "vite --host",
"build:docs": "vite build --config ./build/docs.config.js && node ./config/copyDocs.js",
"build:dist": "vite build --config ./build/dist.config.js",
"preview": "vite preview",
"deploy": "bash deploy.sh"
},
执行npm run build:docs 结果

执行npm run build:dist 结果

九、上传npm
一、在package.json中添加发布的一些信息

描述:
description:描述信息
main:入口
keywords:关键词
npm发布时,要将package.json中的private属性值改为false
README.md中写使用说明
# my-baseui
## 安装
使用npm 或 yarn 安装
```
npm install my-baseui
yarn add my-baseui
```
## 引入 my-baseui
### 完整引入
#### 需要注意的是 css 样式文件需要单独引入。
在 main.js 中写入以下内容:
```js
import { createApp } from 'vue'
import App from './App.vue'
// 导入组件库
import myBaseui from 'my-baseui'
import 'my-baseui/dist/style.css';
const app = createApp(App)
app.use(myBaseui)
app.mount('#app')
```
<br/>
## 愉快开始
#### 至此 my-baseui 就引入完成并且可以使用了。
```html
<!-- html -->
<m-button>默认按钮</m-button>
<m-button type="primary">主要按钮</m-button>
```
<br/>
发布npm
执行npm login 命令,输入用户名和密码,输入密码时是看不到的
之后提示输入email,成功后你的邮箱会收到一个one-time password,填入这个一次性密码
登录之后,执行npm publish进行发布(每次进行发布的时候记得改下版本号)
发布成功后,到npm上,查看头像---->packages,就可以看到发布的包了。
至此,大体就算完成了,后面就是完善每个组件。
如果你感兴趣的话,请前往 GitHub 查看源码和完整文档
github地址: https://github.com/wangibook/my-baseui


浙公网安备 33010602011771号