使用 Vue cli 3.0 构建自定义组件库的方法
本文旨在给大家提供一种构建一个完整UI库脚手架的思路:包括如何快速并优雅地构建UI库的主页、如何托管主页、如何编写脚本提升自己的开发效率、如何生成CHANGELOG等
前言
主流的开源UI库代码结构主要分为三大部分:
- 组件库本身的代码:这部分代码会发布到npm上
- 预览示例和查看文档的网站代码:类似Vant、ElementUI这类网站。
- 配置文件和脚本文件:用于打包和发布等等
编写此博文的灵感UI框架库(vue-cards),PS:此UI框架库相对于Vant、ElementUI会比较简单点,可以作为一份自定义UI框架库的入坑demo,同时这篇博文也是解读这份UI框架库的构建到上线的一个过程
前置工作
以下工作全部基于VueCLI3.x,所以首先要保证机子上有@vue/cli
vuecreatevtp-component#vtp-component作为教学的库名vue-router,dart-sass,babel,eslint这些是该项目使用的依赖项,小主可以根据自己的需求进行相应的切换
start
开始造轮子了
工作目录
在根目录下新增四个文件夹,一个用来存放组件的代码(packages),一个用来存放预览示例的网站代码(examples)(这里直接把初始化模板的src目录更改为examples即可,有需要的话可以将该目录进行清空操作,这里就不做过多的说明),一个用来存放编译脚本代码(build)修改当前的工作目录为以下的格式吗,一个用来存放自定义生成组件和组件的说明文档等脚本(scripts)
|---build
|
|---examples
|
|---packages
|
|---scripts
让webpack编译examples
由于我们将src目录修改成了examples,所以在vue.config.js中需要进行相应的修改
constpath=require('path') functionresolve(dir){ returnpath.join(__dirname,dir) } module.exports={ productionSourceMap:true, //修改src为examples pages:{ index:{ entry:'examples/main.js', template:'public/index.html', filename:'index.html' } }, chainWebpack:config=>{ config.resolve.alias .set('@',resolve('examples')) } }
添加编译脚本
package.json
其中的组件name推荐和创建的项目名一致
{ "scripts":{ "lib":"vue-cli-servicebuild--targetlib--namevtp-component--destlibpackages/index.js" } }
修改main主入口文件
{ "main":"lib/vtp-component.common.js" }
一个组件例子
创建组件和组件文档生成脚本
在scripts中创建以下几个文件,其中create-comp.js是用来生成自定义组件目录和自定义组件说明文档脚本,delete-comp.js是用来删除无用的组件目录和自定义组件说明文档脚本,template.js是生成代码的模板文件
|---create-comp.js
|
|---delete-comp.js
|
|---template.js
相关的代码如下,小主可以根据自己的需求进行相应的简单修改,下面的代码参考来源vue-cli3项目优化之通过node自动生成组件模板generateView、Component
create-comp.js
//创建自定义组件脚本 constchalk=require('chalk') constpath=require('path') constfs=require('fs-extra') constuppercamelize=require('uppercamelcase') constresolve=(...file)=>path.resolve(__dirname,...file) constlog=message=>console.log(chalk.green(`${message}`)) constsuccessLog=message=>console.log(chalk.blue(`${message}`)) consterrorLog=error=>console.log(chalk.red(`${error}`)) const{ vueTemplate, entryTemplate, mdDocs }=require('./template') constgenerateFile=(path,data)=>{ if(fs.existsSync(path)){ errorLog(`${path}文件已存在`) return } returnnewPromise((resolve,reject)=>{ fs.writeFile(path,data,'utf8',err=>{ if(err){ errorLog(err.message) reject(err) }else{ resolve(true) } }) }) } //这里生成自定义组件 log('请输入要生成的组件名称,形如demo或者demo-test') letcomponentName='' process.stdin.on('data',asyncchunk=>{ letinputName=String(chunk).trim().toString() inputName=uppercamelize(inputName) constcomponentDirectory=resolve('../packages',inputName) constcomponentVueName=resolve(componentDirectory,`${inputName}.vue`) constentryComponentName=resolve(componentDirectory,'index.js') consthasComponentDirectory=fs.existsSync(componentDirectory) if(inputName){ //这里生成组件 if(hasComponentDirectory){ errorLog(`${inputName}组件目录已存在,请重新输入`) return }else{ log(`生成component目录${componentDirectory}`) awaitdotExistDirectoryCreate(componentDirectory) } try{ if(inputName.includes('/')){ constinputArr=inputName.split('/') componentName=inputArr[inputArr.length-1] }else{ componentName=inputName } log(`生成vue文件${componentVueName}`) awaitgenerateFile(componentVueName,vueTemplate(componentName)) log(`生成entry文件${entryComponentName}`) awaitgenerateFile(entryComponentName,entryTemplate(componentName)) successLog('生成component成功') }catch(e){ errorLog(e.message) } }else{ errorLog(`请重新输入组件名称:`) return } //这里生成自定义组件说明文档 constdocsDirectory=resolve('../examples/docs') constdocsMdName=resolve(docsDirectory,`${inputName}.md`) try{ log(`生成component文档${docsMdName}`) awaitgenerateFile(docsMdName,mdDocs(`${inputName}组件`)) successLog('生成component文档成功') }catch(e){ errorLog(e.message) } process.stdin.emit('end') }) process.stdin.on('end',()=>{ log('exit') process.exit() }) functiondotExistDirectoryCreate(directory){ returnnewPromise((resolve)=>{ mkdirs(directory,function(){ resolve(true) }) }) } //递归创建目录 functionmkdirs(directory,callback){ varexists=fs.existsSync(directory) if(exists){ callback() }else{ mkdirs(path.dirname(directory),function(){ fs.mkdirSync(directory) callback() }) } }delete-comp.js //删除自定义组件脚本 constchalk=require('chalk') constpath=require('path') constfs=require('fs-extra') constuppercamelize=require('uppercamelcase') constresolve=(...file)=>path.resolve(__dirname,...file) constlog=message=>console.log(chalk.green(`${message}`)) constsuccessLog=message=>console.log(chalk.blue(`${message}`)) consterrorLog=error=>console.log(chalk.red(`${error}`)) log('请输入要删除的组件名称,形如demo或者demo-test') process.stdin.on('data',asyncchunk=>{ letinputName=String(chunk).trim().toString() inputName=uppercamelize(inputName) constcomponentDirectory=resolve('../packages',inputName) consthasComponentDirectory=fs.existsSync(componentDirectory) constdocsDirectory=resolve('../examples/docs') constdocsMdName=resolve(docsDirectory,`${inputName}.md`) if(inputName){ if(hasComponentDirectory){ log(`删除component目录${componentDirectory}`) awaitremovePromise(componentDirectory) successLog(`已删除${inputName}组件目录`) log(`删除component文档${docsMdName}`) fs.unlink(docsMdName) successLog(`已删除${inputName}组件说明文档`) }else{ errorLog(`${inputName}组件目录不存在`) return } }else{ errorLog(`请重新输入组件名称:`) return } process.stdin.emit('end') }) process.stdin.on('end',()=>{ log('exit') process.exit() }) functionremovePromise(dir){ returnnewPromise(function(resolve,reject){ //先读文件夹 fs.stat(dir,function(_err,stat){ if(stat.isDirectory()){ fs.readdir(dir,function(_err,files){ files=files.map(file=>path.join(dir,file))//a/ba/m files=files.map(file=>removePromise(file))//这时候变成了promise Promise.all(files).then(function(){ fs.rmdir(dir,resolve) }) }) }else{ fs.unlink(dir,resolve) } }) }) }template.js module.exports={ vueTemplate:compoenntName=>{ compoenntName=compoenntName.charAt(0).toLowerCase()+compoenntName.slice(1) return`${compoenntName}
在build中创建以下几个文件,其中build-entry.js脚本是用来生成自定义组件导出packages/index.js,get-components.js脚本是用来获取packages目录下的所有组件
|---build-entry.js
|
|---get-components.js
相关的代码如下,小主可以根据自己的需求进行相应的简单修改,下面的代码参考来源vue-cards
build-entry.js
constfs=require('fs-extra') constpath=require('path') constchalk=require('chalk') constuppercamelize=require('uppercamelcase') constComponents=require('./get-components')() constpackageJson=require('../package.json') constlog=message=>console.log(chalk.green(`${message}`)) constversion=process.env.VERSION||packageJson.version functionbuildPackagesEntry(){ constuninstallComponents=[] constimportList=Components.map( name=>`import${uppercamelize(name)}from'./${name}'` ) constexportList=Components.map(name=>`${uppercamelize(name)}`) constintallList=exportList.filter( name=>!~uninstallComponents.indexOf(uppercamelize(name)) ) constcontent=`import'normalize.css' ${importList.join('\n')} constversion='${version}' constcomponents=[ ${intallList.join(',\n')} ] constinstall=Vue=>{ if(install.installed)return components.map(component=>Vue.component(component.name,component)) } if(typeofwindow!=='undefined'&&window.Vue){ install(window.Vue) } export{ install, version, ${exportList.join(',\n')} } exportdefault{ install, version, ...components } ` fs.writeFileSync(path.join(__dirname,'../packages/index.js'),content) log('packages/index.js文件已更新依赖') log('exit') } buildPackagesEntry()get-components.js constfs=require('fs') constpath=require('path') constexcludes=[ 'index.js', 'theme-chalk', 'mixins', 'utils', '.DS_Store' ] module.exports=function(){ constdirs=fs.readdirSync(path.resolve(__dirname,'../packages')) returndirs.filter(dirName=>excludes.indexOf(dirName)===-1) }
让vue解析markdown
文档中心的UI是如何编码的这里不做阐述,小主可以自行参照vue-cards中的实现方式进行改造
需要安装以下的依赖,让vue解析markdown
npmimarkdown-it-container-D npmimarkdown-it-decorate-D npmimarkdown-it-task-checkbox-D npmivue-markdown-loader-D
关于vue.config.js的配置在vue-cards该项目中也有了,不做阐述
这里将补充高亮highlight.js以及点击复制代码clipboard的实现方式
安装依赖
npmiclipboardhighlight.js改造App.vue,以下只是列出部分代码,小主可以根据自己的需求进行添加
生成命令
在package.json中添加以下内容,使用命令yarnnew:comp创建组件目录及其文档或者使用命令yarndel:comp即可删除组件目录及其文档
{ "scripts":{ "new:comp":"nodescripts/create-comp.js&&nodebuild/build-entry.js", "del:comp":"nodescripts/delete-comp.js&&nodebuild/build-entry.js" } }
changelog
在package.json中修改script字段,接下来你懂的,另一篇博客有介绍哦,小主可以执行搜索
{ "scripts":{ "init":"npminstallcommitizen-g&&commitizeninitcz-conventional-changelog--save-dev--save-exact&&npmrunbootstrap", "bootstrap":"npminstall", "changelog":"conventional-changelog-pangular-iCHANGELOG.md-s-r0" } }
总结
以上所述是小编给大家介绍的使用Vuecli3.0构建自定义组件库的方法,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!