从零开始搭建Vue组件库 VV-UI
前言:
前端组件化是当今热议的话题之一,也是我们在开发单页应用经常会碰到的一个问题,现在我们有了功能非常完善的Element-UI。各个大厂也相继宣布开源XXX-UI。但是也会存在一些问题,比如每个公司可能需要的业务组件不尽相同,或者我们想自己开发一套属于自己的组件库,来增强对组件的可控性。那么我们该如何去做呢?
这里记录一下我从零开始搭建起来的组件库的过程,目前只有简单几个组件,不过我也会慢慢更新维护。
1. 环境准备
我们搭建组件库,需要准备一系列环境,首先我们要考虑一下问题:
- 脚手架如何搭建
- 如何规划目录结构
- 如何编写文档
首先,对于脚手架环境的问题,目前已经有非常成熟的vue官方的脚手架,我们拿来用就好了
1 2 3 4 5 6 7 8 | # 全局安装 vue-cli $ npm install --global vue-cli # 创建一个基于 webpack 模板的新项目 $ vue init webpack my-project # 安装依赖,走你 $ cd my-project $ npm install $ npm run dev |
接着我们看第二个问题,如何规划好我们组建的目录结构?首先我们需要有一个目录存放组件,有一个目录存放示例。所以我们要对vue-cli 生成的项目结构做一下改造:
1 2 3 4 5 6 | . ... |-- examples // 原 src 目录,改成 examples 用作示例展示 |-- packages // 新增 packages 用于编写存放组件 ... . |
这样的话 我们需要再把我们webpack配置文件稍作一下调整,首先是把原先的编译指向src的目录改成examples,其次为了 npm run build
能正常编译 packages 我们也需要为 babel-loader 再增加一个编译目录:
1 2 3 4 5 | { test: /\.js$/, loader: 'babel-loader' , include: [resolve( 'examples' ), resolve( 'test' ), resolve( 'packages' )] } |
这样我们搭建起来一个简易的目录结构。
紧接着我们需要考虑如何编写文档。对于文档的编写,自然是markdown最合适不过了,那么怎么让我们在vue下可以去写 markdown 文档呢?答案当然是 vue-markdown-loader。然后我们按照文档配置了相关的插件信息:
rules: [ { test: /\.md$/, loader: 'vue-markdown-loader' } ]
好了,我们可以开始尝试写文档了,在 example/docs 目录下新建 test.md。
# test
> Hello World
同时创建一个新的路由,指向我们的md文件:
1 2 3 4 5 | { path: '/test' , name: 'test' , component: r => require.ensure([], () => r(require( '../docs/test.md' ))) } |
打开我们的浏览器http://localhost:8080/#/test
哈哈 真的成功了。别高兴的太早.... 问题还在后面:我们期望的文档不仅能编译markdown,而且最好能识别demo代码块一方面做演示,一方面可以显示演示代码最好了,就像这样:
那我们需要怎么做呢?vue-mark-down 功能肯定不止这些!于是我们继续阅读它的文档,发现其实他就是封装了 markdown-it,支持 options 选项。这样我们就可以为我们的markdown定义独特的标识符,这里我用 demo 标识需要显示代码块的地方,所以我需要配置options 选项 :
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 | const vueMarkdown = { preprocess: (MarkdownIt, source) => { MarkdownIt.renderer.rules.table_open = function () { return '<table class="table">' } MarkdownIt.renderer.rules.fence = utils.wrapCustomClass(MarkdownIt.renderer.rules.fence) return source }, use: [ [MarkdownItContainer, 'demo' , { // 用于校验包含demo的代码块 validate: params => params.trim().match(/^demo\s*(.*)$/), render: function (tokens, idx) { var m = tokens[idx].info.trim().match(/^demo\s*(.*)$/); if (tokens[idx].nesting === 1) { var desc = tokens[idx + 2].content; // 编译成html const html = utils.convertHtml(striptags(tokens[idx + 1].content, 'script' )) // 移除描述,防止被添加到代码块 tokens[idx + 2].children = []; return `<demo-block> <div slot= "desc" >${html}</div> <div slot= "highlight" >`; } return '</div></demo-block>\n' ; } }] ] } |
这里简单的描述一下这段代码是干什么的:首先把内容里面vue片段编译成html,用于显示,另一方面用highlight来高亮代码块。demo-block
本身是我们定义好的组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <template> <div class = "docs-demo-wrapper" > <div :style= "{maxHeight: isExpand ? '700px' : '0'}" class = "demo-container" > <div span= "14" > <div class = "docs-demo docs-demo--expand" > <div class = "highlight-wrapper" > <slot name= "highlight" ></slot> </div> </div> </div> </div> <span class = "docs-trans docs-demo__triangle" @click= "toggle" >{{isExpand ? '隐藏代码' : '显示代码' }}</span> </div> </template> |
这样,我们的 test.md 便可以这么去写了:
2. 如何编写组件
环境准备完毕,紧接着要开始编写组件,考虑的是组件库,所以我们竟可能让我们的组件支持全局引入和按需引入,如果全局引入,那么所有的组件需要要注册到Vue component 上,并导出:
1 2 3 4 5 6 7 8 | const install = function (Vue) { if (install.installed) return ; components.map(component => Vue.component(component.name, component)); }; export default { install }; |
接着要实现按需加载,我们只需要单个导出组件即可:
import Button from './button/index.js'; import Row from './row/index' import Col from './col/index' const components = [ Button, Row, Col ]; const install = function(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, Button, Row, Col };
其次,我们还需要考虑一个问题:既然是单页面应用,必然要去解决样式冲突问题,如果组件内使用soped,那么样式就无法从组件内抽离出来,达不到可定制化主题颜色的目的。我们需要一套可以分离处理的样式,可以自行编译,可以相互不污染。这时候css 的BEM规范就显得尤为重要。如果你还不知道什么是BEM 参考: http://www.w3cplus.com/css/css-architecture-1.html。
说到这里,目前对BEM规范支持较好的插件就是postcss了,他允许我们配置BEM之间的连接符和缩写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | { "browsers" : [ "ie > 8" , "last 2 versions" ], "features" : { "bem" : { "shortcuts" : { "component" : "b" , "modifier" : "m" , "descendent" : "e" }, "separators" : { "descendent" : "__" , "modifier" : "--" } } } } |
这样我们就可以把样式单独的抽离出来,通过gulp进行打包编译:
1 2 3 4 5 6 | gulp.task( 'compile' , function () { return gulp.src( './src/*.css' ) .pipe(postcss([salad])) .pipe(cssmin()) .pipe(gulp.dest( './lib' )); }); |
最后生成我们的样式代码。
好了开始我们的测试:
1 2 3 4 | import VVUI from '../packages/index' import '../packages/theme-default/lib/index.css' Vue.use(VVUI) |
一切显得那么美好....
优化与不足
- 组件导出代码暂不支持自动化生成:比如我们的组件index文件,每次添加组件都需要不断地改写,我们2*
可以尝试进行webpack配置,npm run dev
的时候自动进行组件检测,然后帮我们写好导出代码。 - 目录结构划分缺陷:目前所有内容仅支持中文,如果想要做到支持国际化,那么还需要重新调整目录结构。
- 发布tag: 需要编写脚本支持tag发布
- 组件太少:文档刚写,组件还不是很多,慢慢去维护,相信会越来越多的组件,做业务的过程中也可以把常用的组件加进去,这样更加方便自己以后的维护和学习
结语:
项目github地址:github
项目演示地址: 演示
欢迎 PR 一起维护,欢迎 Star
关于
作者:monkeyWang
本人主页:monkeyWang
微信公众号:前端知识铺
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?