您好,登錄后才能下訂單哦!
本文旨在給大家提供一種構(gòu)建一個(gè)完整 UI 庫腳手架的思路:包括如何快速并優(yōu)雅地構(gòu)建UI庫的主頁、如何托管主頁、如何編寫腳本提升自己的開發(fā)效率、如何生成 CHANGELOG 等
前言
主流的開源 UI 庫代碼結(jié)構(gòu)主要分為三大部分:
編寫此博文的靈感 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ù)大家的!
免責(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)容。