使用 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构建自定义组件库的方法,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!