溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

使用 Vue cli 3.0 構(gòu)建自定義組件庫的方法

發(fā)布時(shí)間:2020-08-28 03:10:36 來源:腳本之家 閱讀:280 作者:Coding and Fixing 欄目:web開發(fā)

本文旨在給大家提供一種構(gòu)建一個(gè)完整 UI 庫腳手架的思路:包括如何快速并優(yōu)雅地構(gòu)建UI庫的主頁、如何托管主頁、如何編寫腳本提升自己的開發(fā)效率、如何生成 CHANGELOG 等

前言

主流的開源 UI 庫代碼結(jié)構(gòu)主要分為三大部分:

  • 組件庫本身的代碼:這部分代碼會(huì)發(fā)布到 npm 上
  • 預(yù)覽示例和查看文檔的網(wǎng)站代碼:類似 Vant、ElementUI 這類網(wǎng)站。
  • 配置文件和腳本文件:用于打包和發(fā)布等等

編寫此博文的靈感 UI 框架庫( vue-cards ),PS:此 UI框架庫相對(duì)于Vant、ElementUI會(huì)比較簡(jiǎn)單點(diǎn),可以作為一份自定義UI框架庫的入坑demo,同時(shí)這篇博文也是解讀這份 UI 框架庫的構(gòu)建到上線的一個(gè)過程

前置工作

以下工作全部基于 Vue CLI 3.x,所以首先要保證機(jī)子上有 @vue/cli

vue create vtp-component # vtp-component 作為教學(xué)的庫名vue-router , dart-sass , babel , eslint 這些是該項(xiàng)目使用的依賴項(xiàng),小主可以根據(jù)自己的需求進(jìn)行相應(yīng)的切換

start

開始造輪子了

工作目錄

在根目錄下新增四個(gè)文件夾,一個(gè)用來存放組件的代碼(packages),一個(gè)用來存放 預(yù)覽示例的網(wǎng)站 代碼(examples)(這里直接把初始化模板的 src 目錄更改為 examples 即可,有需要的話可以將該目錄進(jìn)行清空操作,這里就不做過多的說明),一個(gè)用來存放編譯腳本代碼(build)修改當(dāng)前的工作目錄為以下的格式嗎,一個(gè)用來存放自定義生成組件和組件的說明文檔等腳本(scripts)

|--- build     
|
|--- examples
|
|--- packages
|

|--- scripts

讓 webpack 編譯 examples

由于我們將 src 目錄修改成了 examples,所以在 vue.config.js 中需要進(jìn)行相應(yīng)的修改

const path = require('path')
function resolve (dir) {
 return path.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 推薦和創(chuàng)建的項(xiàng)目名一致

{
 "scripts": {
 "lib": "vue-cli-service build --target lib --name vtp-component --dest lib packages/index.js"
 }
}

修改 main 主入口文件

{
 "main": "lib/vtp-component.common.js"
}

一個(gè)組件例子

創(chuàng)建組件和組件文檔生成腳本

在 scripts 中創(chuàng)建以下幾個(gè)文件,其中 create-comp.js 是用來生成自定義組件目錄和自定義組件說明文檔腳本, delete-comp.js 是用來刪除無用的組件目錄和自定義組件說明文檔腳本, template.js 是生成代碼的模板文件

|--- create-comp.js
|
|--- delete-comp.js
|
|--- template.js

相關(guān)的代碼如下,小主可以根據(jù)自己的需求進(jìn)行相應(yīng)的簡(jiǎn)單修改,下面的代碼參考來源 vue-cli3 項(xiàng)目?jī)?yōu)化之通過 node 自動(dòng)生成組件模板 generate View、Component

create-comp.js

// 創(chuàng)建自定義組件腳本
const chalk = require('chalk')
const path = require('path')
const fs = require('fs-extra')
const uppercamelize = require('uppercamelcase')
const resolve = (...file) => path.resolve(__dirname, ...file)
const log = message => console.log(chalk.green(`${message}`))
const successLog = message => console.log(chalk.blue(`${message}`))
const errorLog = error => console.log(chalk.red(`${error}`))
const {
 vueTemplate,
 entryTemplate,
 mdDocs
} = require('./template')
const generateFile = (path, data) => {
 if (fs.existsSync(path)) {
 errorLog(`${path}文件已存在`)
 return
 }
 return new Promise((resolve, reject) => {
 fs.writeFile(path, data, 'utf8', err => {
  if (err) {
  errorLog(err.message)
  reject(err)
  } else {
  resolve(true)
  }
 })
 })
}
// 這里生成自定義組件
log('請(qǐng)輸入要生成的組件名稱,形如 demo 或者 demo-test')
let componentName = ''
process.stdin.on('data', async chunk => {
 let inputName = String(chunk).trim().toString()
 inputName = uppercamelize(inputName)
 const componentDirectory = resolve('../packages', inputName)
 const componentVueName = resolve(componentDirectory, `${inputName}.vue`)
 const entryComponentName = resolve(componentDirectory, 'index.js')
 const hasComponentDirectory = fs.existsSync(componentDirectory)
 if (inputName) {
 // 這里生成組件
 if (hasComponentDirectory) {
  errorLog(`${inputName}組件目錄已存在,請(qǐng)重新輸入`)
  return
 } else {
  log(`生成 component 目錄 ${componentDirectory}`)
  await dotExistDirectoryCreate(componentDirectory)
 }
 try {
  if (inputName.includes('/')) {
  const inputArr = inputName.split('/')
  componentName = inputArr[inputArr.length - 1]
  } else {
  componentName = inputName
  }
  log(`生成 vue 文件 ${componentVueName}`)
  await generateFile(componentVueName, vueTemplate(componentName))
  log(`生成 entry 文件 ${entryComponentName}`)
  await generateFile(entryComponentName, entryTemplate(componentName))
  successLog('生成 component 成功')
 } catch (e) {
  errorLog(e.message)
 }
 } else {
 errorLog(`請(qǐng)重新輸入組件名稱:`)
 return
 }
 // 這里生成自定義組件說明文檔
 const docsDirectory = resolve('../examples/docs')
 const docsMdName = resolve(docsDirectory, `${inputName}.md`)
 try {
 log(`生成 component 文檔 ${docsMdName}`)
 await generateFile(docsMdName, mdDocs(`${inputName} 組件`))
 successLog('生成 component 文檔成功')
 } catch (e) {
 errorLog(e.message)
 }
 process.stdin.emit('end')
})
process.stdin.on('end', () => {
 log('exit')
 process.exit()
})
function dotExistDirectoryCreate (directory) {
 return new Promise((resolve) => {
 mkdirs(directory, function () {
  resolve(true)
 })
 })
}
// 遞歸創(chuàng)建目錄
function mkdirs (directory, callback) {
 var exists = fs.existsSync(directory)
 if (exists) {
 callback()
 } else {
 mkdirs(path.dirname(directory), function () {
  fs.mkdirSync(directory)
  callback()
 })
 }
}delete-comp.js 
// 刪除自定義組件腳本
const chalk = require('chalk')
const path = require('path')
const fs = require('fs-extra')
const uppercamelize = require('uppercamelcase')
const resolve = (...file) => path.resolve(__dirname, ...file)
const log = message => console.log(chalk.green(`${message}`))
const successLog = message => console.log(chalk.blue(`${message}`))
const errorLog = error => console.log(chalk.red(`${error}`))
log('請(qǐng)輸入要?jiǎng)h除的組件名稱,形如 demo 或者 demo-test')
process.stdin.on('data', async chunk => {
 let inputName = String(chunk).trim().toString()
 inputName = uppercamelize(inputName)
 const componentDirectory = resolve('../packages', inputName)
 const hasComponentDirectory = fs.existsSync(componentDirectory)
 const docsDirectory = resolve('../examples/docs')
 const docsMdName = resolve(docsDirectory, `${inputName}.md`)
 if (inputName) {
 if (hasComponentDirectory) {
  log(`刪除 component 目錄 ${componentDirectory}`)
  await removePromise(componentDirectory)
  successLog(`已刪除 ${inputName} 組件目錄`)
  log(`刪除 component 文檔 ${docsMdName}`)
  fs.unlink(docsMdName)
  successLog(`已刪除 ${inputName} 組件說明文檔`)
 } else {
  errorLog(`${inputName}組件目錄不存在`)
  return
 }
 } else {
 errorLog(`請(qǐng)重新輸入組件名稱:`)
 return
 }
 process.stdin.emit('end')
})
process.stdin.on('end', () => {
 log('exit')
 process.exit()
})
function removePromise (dir) {
 return new Promise(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/b a/m
   files = files.map(file => removePromise(file)) // 這時(shí)候變成了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 `<template>
 <div class="vtp-${compoenntName}">
 ${compoenntName}
 </div>
</template>
<script>
export default {
 name: 'vtp-${compoenntName}',
 data () {
 return {
 }
 },
 props: {
 },
 methods: {}
}
</script>
<style lang="scss" scope>
.vtp-${compoenntName}{}
</style>
`
 },
 entryTemplate: compoenntName => {
 return `import ${compoenntName} from './${compoenntName}'
${compoenntName}.install = function (Vue) {
 Vue.component(${compoenntName}.name, ${compoenntName})
}
export default ${compoenntName}
if (typeof window !== 'undefined' && window.Vue) {
 window.Vue.component(${compoenntName}.name, ${compoenntName})
}
`
 },
 mdDocs: (title) => {
 return `# ${title}
<!-- {.md} -->
---
<!-- {.md} -->
## 如何使用
<!-- {.md} -->
## Attributes
<!-- {.md} -->
| 參數(shù) | 說明 | 類型 | 可選值 | 默認(rèn)值 |
|-----|-----|-----|-----|-----|
| - | - | - | - | - |
 `
 }
}
`
 },
 entryTemplate: compoenntName => {
 return `import ${compoenntName} from './${compoenntName}'
${compoenntName}.install = function (Vue) {
 Vue.component(${compoenntName}.name, ${compoenntName})
}
if (typeof window !== 'undefined' && window.Vue) {
 window.Vue.component(${compoenntName}.name, ${compoenntName})
}
 }
}

在 build 中創(chuàng)建以下幾個(gè)文件,其中 build-entry.js 腳本是用來生成自定義組件導(dǎo)出 packages/index.js , get-components.js 腳本是用來獲取 packages 目錄下的所有組件

|--- build-entry.js
|
|--- get-components.js

相關(guān)的代碼如下,小主可以根據(jù)自己的需求進(jìn)行相應(yīng)的簡(jiǎn)單修改,下面的代碼參考來源 vue-cards

build-entry.js

const fs = require('fs-extra')
const path = require('path')
const chalk = require('chalk')
const uppercamelize = require('uppercamelcase')
const Components = require('./get-components')()
const packageJson = require('../package.json')
const log = message => console.log(chalk.green(`${message}`))
const version = process.env.VERSION || packageJson.version
function buildPackagesEntry () {
 const uninstallComponents = []
 const importList = Components.map(
 name => `import ${uppercamelize(name)} from './${name}'`
 )
 const exportList = Components.map(name => `${uppercamelize(name)}`)
 const intallList = exportList.filter(
 name => !~uninstallComponents.indexOf(uppercamelize(name))
 )
 const content = `import 'normalize.css'
${importList.join('\n')}
const version = '${version}'
const components = [
 ${intallList.join(',\n ')}
]
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 {
 install,
 version,
 ${exportList.join(',\n ')}
}
export default {
 install,
 version,
 ...components
}
`
 fs.writeFileSync(path.join(__dirname, '../packages/index.js'), content)
 log('packages/index.js 文件已更新依賴')
 log('exit')
}
buildPackagesEntry()get-components.js 
const fs = require('fs')
const path = require('path')
const excludes = [
 'index.js',
 'theme-chalk',
 'mixins',
 'utils',
 '.DS_Store'
]
module.exports = function () {
 const dirs = fs.readdirSync(path.resolve(__dirname, '../packages'))
 return dirs.filter(dirName => excludes.indexOf(dirName) === -1)
}

讓 vue 解析 markdown

文檔中心的 UI 是如何編碼的這里不做闡述,小主可以自行參照 vue-cards 中的實(shí)現(xiàn)方式進(jìn)行改造

需要安裝以下的依賴,讓 vue 解析 markdown

npm i markdown-it-container -D
npm i markdown-it-decorate -D
npm i markdown-it-task-checkbox -D
npm i vue-markdown-loader -D

關(guān)于 vue.config.js 的配置在 vue-cards 該項(xiàng)目中也有了,不做闡述

這里將補(bǔ)充高亮 highlight.js 以及點(diǎn)擊復(fù)制代碼 clipboard 的實(shí)現(xiàn)方式

安裝依賴

npm i clipboard highlight.js改造 App.vue ,以下只是列出部分代碼,小主可以根據(jù)自己的需求進(jìn)行添加

<script>
import hljs from 'highlight.js'
import Clipboard from 'clipboard'
const highlightCode = () => {
 const preEl = document.querySelectorAll('pre')
 preEl.forEach((el, index) => {
 hljs.highlightBlock(el)
 const lang = el.children[0].className.split(' ')[1].split('-')[1]
 const pre = el
 const span = document.createElement('span')
 span.setAttribute('class', 'code-copy')
 span.setAttribute('data-clipboard-snippet', '')
 span.innerHTML = `${lang.toUpperCase()} | COPY`
 pre.appendChild(span)
 })
}
export default {
 name: 'App',
 mounted () {
 if ('onhashchange' in window) {
  window.onhashchange = function (ev) {
  let name = window.location.hash.substring(2)
  router.push({ name })
  }
 }
 highlightCode()
 let clipboard = new Clipboard('.code-copy', {
  text: (trigger) => {
  return trigger.previousSibling.innerText
  }
 })
 // 復(fù)制成功執(zhí)行的回調(diào)
 clipboard.on('success', (e) => {
  e.trigger.innerHTML = `已復(fù)制`
 })
 },
 updated () {
 highlightCode()
 }
}
</script>

生成命令

package.json 中添加以下內(nèi)容,使用命令 yarn new:comp 創(chuàng)建組件目錄及其文檔或者使用命令 yarn del:comp 即可刪除組件目錄及其文檔

{
 "scripts": {
 "new:comp": "node scripts/create-comp.js && node build/build-entry.js",
 "del:comp": "node scripts/delete-comp.js && node build/build-entry.js"
 }
}

changelog

在 package.json 中修改 script 字段,接下來你懂的,另一篇博客有介紹哦,小主可以執(zhí)行搜索

{
 "scripts": {
 "init": "npm install commitizen -g && commitizen init cz-conventional-changelog --save-dev --save-exact && npm run bootstrap",
 "bootstrap": "npm install",
 "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
 }
}

總結(jié)

以上所述是小編給大家介紹的使用 Vue cli 3.0 構(gòu)建自定義組件庫的方法,希望對(duì)大家有所幫助,如果大家有任何疑問歡迎給我留言,小編會(huì)及時(shí)回復(fù)大家的!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI