vite2 + vue3 开发组件库
使用vite2 + vue3开发一个组件库
完成后的效果图展示:
一、搭建一个 Vite 项目
参照vite官网
通过附加的命令行选项直接指定项目名称和你想要使用的模板。例如,要构建一个 Vite + Vue 项目
1 2 | # yarn yarn create vite my-baseui --template vue |
接下来执行
1 2 3 | cd my-baseui yarn yarn dev |
运行后打开页面长这样子:
在根目录下创建packages文件夹,目录结构如下:
描述:
packages/components
: 存放组件。packages/styles
: 存放全局样式和组件样式。packages/index.js
二、组件编写
一、components中,编写一个测试的button组件
二、components中的 button组件index.vue代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | <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样式代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | .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文件,用来把写的组件暴露出去
1 2 3 4 5 | import mButton from './index.vue' mButton.install = app => { app.component(mButton.name, mButton) } export default mButton; |
五、packages文件夹下新建index.js文件,用来管理所有的组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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
中引入
1 2 3 4 5 6 7 8 | 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
1 2 3 4 5 6 7 | <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 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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中引入路由
1 2 3 4 5 6 7 8 9 10 | 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 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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页面,增加侧边栏和主区域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | <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改下
1 2 3 4 5 6 7 8 9 10 11 | <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
1 2 3 4 5 6 7 8 | <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文件中引入
1 2 3 4 5 6 | <script setup> import demo from './demo.vue' ; </script> # Button 按钮 <demo/> |
完成后,查看页面显示:
六、代码预览功能
接下来我们来实现代码预览功能,虽然说代码预览也很简单,可以直接在 Markdown 中贴代码,但是代码又写一遍就显得很繁琐了
有没有方法可以直接把demo.vue中的代码展示出来呢?
答案是可以的。
在 Vite 的开发文档里有记载到,它支持在资源的末尾加上一个后缀来控制所引入资源的类型。比如可以通过 import xx from 'xx?raw'
以字符串形式引入 xx 文件。基于这个能力,我们可以编写一个 <Preview />
组件来获取所需要展示的文件源码。
一、新建一个 Preview.vue
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | <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
1 2 3 4 5 6 7 8 9 | <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 ,样式有很多种,我们随便找个看看效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 自定义指令
1 2 3 4 5 6 7 | <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加如下配置:
1 2 3 | build: { outDir: 'docs' }, |
接下来,在你的项目中,创建一个 deploy.sh
脚本,包含以下内容(注意高亮的行,按需使用),运行脚本来部署站点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #!/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 修改下:
1 2 3 4 5 6 | "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做下修改:
1 2 3 4 5 6 7 8 9 10 11 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 插件用于对应文件的解析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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:
1 2 3 4 5 6 7 8 9 10 | import baseConfig from './base.config' ; import { defineConfig } from 'vite' ; export default defineConfig({ ...baseConfig, base: '/my-baseui/' , build: { outDir: 'docs' , }, }); |
完成了上面这些构建配置以后,修改一下packages.json
1 2 3 4 5 6 7 | "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中写使用说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | # 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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)