序言
在組件庫開發的精彩旅程中🚀,我們已經成功打造并完善了圖標組件體系,賦予其強大的功能和豐富的表現力🎉。然而,隨著業務版圖的不斷擴張🌐,手動逐個編寫 SVG Vue 組件的傳統方式,逐漸暴露出效率低下的短板。這種重復性的工作不僅耗費大量寶貴的時間?與精力💪,還容易在機械的操作中引入人為錯誤🚫。今天,讓我們一同踏上探索自動化生成的奇妙之路🧭,聚焦于如何借助神奇的代碼力量,通過.svg 文件自動生成 SVG Vue 組件,開啟簡化操作的嶄新時代🌟。這一轉變將如同為組件庫開發裝上了渦輪增壓引擎,推動開發效率實現質的飛躍,讓我們的開發工作更加高效、流暢 。
思路
為深入理解自動生成的實現邏輯,我們從阿里圖庫下載了一個典型的.svg 文件。其內部結構如下:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#2c2c2c" d="M474 152m8 0l60 0q8 0 8 8l0 704q0 8-8 8l-60 0q-8 0-8-8l0-704q0-8 8-8Z" /><path fill="#2c2c2c" d="M168 474m8 0l672 0q8 0 8 8l0 60q0 8-8 8l-672 0q-8 0-8-8l0-60q0-8 8-8Z" />
</svg>
剖析這個文件可知,我們所需的關鍵信息集中在<svg>
節點及其子節點內容。為達成從.svg 文件到 SVG Vue 組件的轉換,首要任務是讀取該文件📄,精準提取所需數據📊,巧妙移除<svg>
節點的非必要屬性,最終依據處理后的數據生成對應的 Vue 文件📝。
讀取 .svg 文件
const readFile = () => {const result = {}const files = fs.readdirSync(assetsRoot)files.forEach(file => {const path = `${assetsRoot}/${file}`let content = fs.readFileSync(path, 'utf8')content = format(content)const name = file.replace('.svg', '')result[name] = content})return result
}
此函數猶在指定目錄assetsRoot
中,讀取所有.svg 文件。對每個文件,它打開并讀取內容,進行格式化處理,然后提取文件名(去除.svg 后綴)作為鍵,將處理后的內容作為值,存入結果對象。
提取 svg 標簽
function extractSvg(content) {const startIndex = content.indexOf('<svg')const endIndex = content.lastIndexOf('</svg>') + '</svg>'.lengthreturn content.slice(startIndex, endIndex)
}
該函數在整個文件內容中定位<svg>
標簽的起始和結束位置,然后將這部分內容完整地裁剪出來。這一步確保我們只保留了與 SVG 圖形直接相關的部分,為后續的處理提供了純凈的數據基礎 。
移除svg標簽非必要屬性
function removeAttribute(content) {const removeAttrs = ['id', 'pid', 'class', 'width', 'height', 'version', 'fill']removeAttrs.forEach(attr => {const reg = new RegExp(` ${attr}="[^"]*"`, "g")content = content.replace(reg, '')})content = content.replace('<svg', '<svg fill="currentColor"')return content
}
這部分代碼專門清理<svg>
標簽中那些我們不需要的屬性。通過正則表達式,它逐個匹配并移除諸如id
、pid
、class
等非必要屬性。同時,為了確保圖標顏色能根據上下文靈活變化,它將<svg>
標簽的fill
屬性替換為fill="currentColor"
。經過這一番清理和設置,<svg>
標簽變得簡潔且符合我們的需求 。
將 提取的 svg 內容 轉為 vue 內容
function toComponentContent(name, content) {const result = `
<template>${content}
</template><script setup>defineOptions({name: 'N${name}Svg',})
</script>
`return result
}
這個函數將提取并處理好的 SVG 內容,巧妙地包裝成 Vue 組件的形式。它創建了一個包含<template>
和<script setup>
的 Vue 組件模板,將 SVG 內容放入<template>
中,并在<script setup>
中定義了組件的名稱。經過這一步,原本的 SVG 數據搖身一變,成為了可以在 Vue 項目中直接使用的組件 。
駝峰轉換
function toGreatHump(value) {return value.split('-').map(item => item.replace(item.charAt(0), item.charAt(0).toUpperCase())).join('')
}
該函數對字符串進行駝峰命名法的轉換。它將以-
分隔的字符串,轉換為每個單詞首字母大寫的駝峰形式。例如,icon-close
會被轉換為IconClose
。這種轉換使得組件名稱在符合 Vue 命名規范的同時,也更具可讀性和一致性 。
寫入vue文件
function writeComponentFile(key, content) {const greatHumpName = toGreatHump(key)const data = toComponentContent(greatHumpName, content)const path = `${componentsRoot}/${key}.vue`fs.writeFile(path, data, error => error && console.error(error))
}
這部分代碼將轉換好的 Vue 組件內容寫入到指定路徑的.vue 文件中。它先將文件名轉換為駝峰形式,然后結合之前生成的 Vue 組件內容,將數據寫入到componentsRoot
目錄下對應的.vue 文件。如果在寫入過程中出現錯誤,它會在控制臺輸出錯誤信息,方便我們及時排查和解決問題 。
轉成import、export語句
function toImport(keys) {return keys.map(key => `import ${toGreatHump(key)} from './components/${key}.vue'`).join('\n')
}
function toExport(keys) {return `export {${keys.map(key => toGreatHump(key)).join(',\n ')}
}`
}
這兩個函數負責生成導入和導出語句。toImport
函數遍歷所有的組件文件名,生成對應的import
語句,這些語句將每個組件從其對應的.vue 文件中引入。toExport
函數則將所有組件名以合適的格式組合成export
語句,方便在其他地方統一引入這些組件。通過這兩個函數,我們構建了一個清晰的組件導入導出體系,使得組件在項目中的使用更加便捷 。
轉為 TS type語句
function toType(keys) {return `export const svgs = [\n${keys.map(key => ` '${toGreatHump(key)}'`).join(',\n')},\n] as const`
}
此函數將所有組件名整理成一個類型聲明語句。這個語句定義了一個包含所有組件名的常量數組svgs
,并且使用as const
確保其類型為只讀常量數組。在 TypeScript 項目中,這個聲明有助于在使用這些組件時進行類型檢查,提高代碼的安全性和穩定性 。
寫入主入口文件
function writeMainFile(keys) {const importData = toImport(keys)const exportData = toExport(keys)const typeData = toType(keys)const data = `${importData}\n\n${exportData}\n\n${typeData}\n`const path = `${mainRoot}/index.ts`fs.writeFile(path, data, error => error && console.error(error))
}
這個函數是整個自動化流程的 “收尾大師”🎨,它將前面生成的導入語句、導出語句和類型聲明語句整合在一起,寫入到主入口文件index.ts
中。這個文件就像是組件庫的大門,所有外部對組件庫的訪問都通過這個文件進行。通過將這些關鍵信息寫入其中,我們完成了組件庫的整體搭建,使得所有組件能夠有序地被引入和使用 。
文件寫入
const writeFile = json => {for (const key in json) {if (Object.prototype.hasOwnProperty.call(json, key)) {writeComponentFile(key, json[key])}}const keys = Object.keys(json)writeMainFile(keys)
}
這個函數是整個自動化流程的 “指揮官”👨???,它統籌協調各個部分的工作。它遍歷包含所有 SVG 文件內容的對象,對每個文件內容調用writeComponentFile
函數,將其寫入對應的.vue 文件。然后,收集所有的文件名,調用writeMainFile
函數,將相關的導入、導出和類型聲明語句寫入主入口文件。通過這個函數的調度,整個自動化生成過程得以有條不紊地完成 。
運行
function run() {const json = readFile()writeFile(json)
}run()
只要運行這個run
方法,就會在packages/svgs/components
下生成對應的 Vue 文件,在packages/svgs/
生成index.ts
主入口文件。
你以為到這里就夠了嗎?還不行!每次運行的時候都要切換到packages/svgs
目錄下,這顯然不是很便捷。所以,我們可以在根目錄下的package.json
文件中scripts
屬性下增加一行?"svg:build": "pnpm -C packages/svgs run init"
?。這樣,每次只要在packages\svgs\assets
目錄下放上.svg 文件,并在命令行運行npm run svg:build
,就會自動生成所需的組件和主入口文件。是不是超級方便呢?這一優化讓我們的開發流程更加流暢,大大提高了工作效率,為組件庫的持續發展和完善提供了有力支持 。
🦀🦀感謝看官看到這里,如果覺得文章不錯的話🙌,點個關注不迷路?。
誠邀您加入我的微信技術交流群🎉,群里都是志同道合的開發者👨?💻,大家能一起交流分享摸魚🐟。期待與您在群里相見🚀,咱們攜手在開發路上共同進步? !
👉點我
感謝各位大俠一路相伴,實在感激! 不瞞您說,在下還有幾個開源項目 📦,它們就像精心培育的幼苗 🌱,急需您的澆灌。要是您瞧著還不錯,麻煩動動手指,給它們點亮幾顆 Star ?,您的支持就是它們成長的最大動力,在此謝過各位大俠啦!
Nova UI
組件庫:https://github.com/gmingchen/nova-ui- 基于 Vue3 + Element-plus 管理后臺基礎功能框架
- 預覽:https://admin.gumingchen.icu
- Github:https://github.com/gmingchen/agile-admin
- Gitee:https://gitee.com/shychen/agile-admin
- 基礎版后端:https://github.com/gmingchen/java-spring-boot-admin
- 文檔:http://admin.gumingchen.icu/doc/
- 基于 Vue3 + Element-plus + websocket 即時聊天系統
- 預覽:https://chatterbox.gumingchen.icu/
- Github:https://github.com/gmingchen/chatterbox
- Gitee:https://gitee.com/shychen/chatterbox
- 基于 node 開發的后端服務:https://github.com/gmingchen/node-server