上文建立了一堆 utils、component-info,並實現了新元件模組相關目錄和檔案的建立。本文繼續實現後面的內容。
在 src/service 目錄中建立 init-scss.ts 檔案,該檔案匯出 initScss 函數。
由於 .vue 型別的元件的樣式就直接寫在了 style 中,故首先判斷元件型別是否是 tsx,tsx 型別的元件才進行這一步的操作:
程式碼實現如下:
import { ComponentInfo } from '../domain/component-info'
import path from 'path'
import { scssTemplate } from '../util/template-utils'
import fs from 'fs'
import { g } from '../util/log-utils'
const updateComponentScssIndex = (scssRootPath: string, lineName: string) => {
const indexScssPath = path.resolve(scssRootPath, 'components/index.scss')
const content = fs.readFileSync(indexScssPath).toString()
const newContent = content.substring(0, content.length) + `@use "${lineName}.module";\n`
fs.writeFileSync(indexScssPath, newContent)
}
/**
* 建立元件庫 scss 檔案,並在 scss/components/index.scss 中引入該檔案
*/
export const initScss = (componentInfo: ComponentInfo) => new Promise((resolve, reject) => {
// tsx 型別需要建立scss檔案
if (componentInfo.type === 'tsx') {
const { parentPath, lineName, lineNameWithPrefix } = componentInfo
// scss 根目錄(packages/scss)
const scssRootPath = path.resolve(parentPath, 'scss')
// 1. 建立元件的 scss 檔案
const componentScssPath = path.resolve(scssRootPath, `components/_${lineName}.module.scss`)
fs.writeFileSync(componentScssPath, scssTemplate(lineNameWithPrefix))
// 2. 在元件庫 scss 入口檔案 (packages/components/index.scss)引入上面建立的檔案
updateComponentScssIndex(scssRootPath, lineName)
g('component scss init success')
}
resolve(componentInfo)
})
上面的 init-scss.ts 在建立 scss 檔案時呼叫了 template-utils.ts 中的 scssTemplate 函數獲取模板。故需要在 util/template-utils.ts 中新增該函數:
/**
* scss 檔案模板
*/
export const scssTemplate = (lineNameWithPrefix: string): string => {
return `@import "../tools";
@import "../acss/mp";
@import "../base/var.module";
@include b('${lineNameWithPrefix}') {
}
`
}
新元件和樣式建立完成,接下來便是將新元件模組安裝到元件庫入口模組的依賴中。在 src/service 目錄中建立 update-component-lib.ts 檔案,該檔案匯出函數 updateComponentLib。該函數需要完成兩件事:
程式碼實現如下:
import { ComponentInfo } from '../domain/component-info'
import { execCmd } from '../util/cmd-utils'
import path from 'path'
import { Config } from '../config'
import fs from 'fs'
import { g } from '../util/log-utils'
const updateComponentLibIndex = (libPath: string, componentInfo: ComponentInfo) => {
const indexPath = path.join(libPath, 'index.ts')
const content = fs.readFileSync(indexPath).toString()
const index1 = content.indexOf('// import component end')
const index2 = content.indexOf('] // components')
const result = `${content.substring(0, index1)}` +
`import ${componentInfo.upCamelName} from '${componentInfo.nameWithLib}'\n` +
content.substring(index1, index2 - 1) +
`,\n ${componentInfo.upCamelName}\n` +
content.substring(index2)
fs.writeFileSync(indexPath, result)
}
/**
* 更新元件庫入口
*/
export const updateComponentLib = async (componentInfo: ComponentInfo) => {
// 元件庫入口的路徑
const libPath = path.resolve(componentInfo.parentPath, Config.COMPONENT_LIB_NAME)
// 1. 新增新建立的元件到依賴中
await execCmd(`cd ${libPath} && pnpm install ${componentInfo.nameWithLib}`)
// 2. 更新入口 index.ts
updateComponentLibIndex(libPath, componentInfo)
g('component library update success')
}
在 src/service 目錄中建立 init-doc.ts 檔案,該檔案匯出函數 initDoc。該函數需要完成三件事:
程式碼實現如下:
import { ComponentInfo } from '../domain/component-info'
import { g } from '../util/log-utils'
import path from 'path'
import fs from 'fs'
import { demoTemplate, mdTemplate } from '../util/template-utils'
/**
* 建立元件檔案、demo及更新選單
*/
export const initDoc = (componentInfo: ComponentInfo) => {
// 元件庫檔案根路徑
const docRootPath = path.resolve(componentInfo.parentPath, '../docs')
const { lineName, lineNameWithPrefix, upCamelName, zhName } = componentInfo
// 1. 建立元件的 MD 檔案
fs.writeFileSync(path.resolve(docRootPath, `components/${lineName}.md`), mdTemplate(componentInfo))
// 2. 建立元件檔案中的 Demo
fs.mkdirSync(path.resolve(docRootPath, `demos/${lineName}`))
fs.writeFileSync(path.resolve(docRootPath, `demos/${lineName}/${lineName}-1.vue`), demoTemplate(lineNameWithPrefix))
// 3. 更新元件庫檔案選單
const menuPath = path.resolve(docRootPath, 'components.ts')
const content = fs.readFileSync(menuPath).toString()
const index = content.indexOf('] // end')
const result = content.substring(0, index - 1) +
`,\n { text: '${upCamelName} ${zhName}', link: '/components/${lineName}' }\n` +
content.substring(index)
fs.writeFileSync(menuPath, result)
g('component document init success')
}
上面的 init-doc.ts 呼叫了 mdTemplate 和 demoTemplate 兩個函數,在 template-utils.ts 中新增這兩個函數:
export const mdTemplate = (componentInfo: ComponentInfo) => {
return `
# ${componentInfo.upCamelName} ${componentInfo.zhName}
## 基本使用
<preview path="../demos/${componentInfo.lineName}/${componentInfo.lineName}-1.vue" title="基本使用" description=" "></preview>
## 元件 API
### Attributes 屬性
| 引數 | 說明 | 型別 | 可選值 | 預設值 |
| ---- | ---- | ---- | ---- | ---- |
| | | | | |
### Methods 方法
| 方法名 | 說明 | 引數 | 返回值 |
| ---- | ---- | ---- | ---- |
| | | | |
### Events 事件
| 事件名 | 說明 | 引數 | 返回值 |
| ---- | ---- | ---- | ---- |
| | | | |
### Slots 插槽
| 插槽名 | 說明 | 引數 |
| ---- | ---- | ---- |
| | | |
`
}
export const demoTemplate = (lineNameWithPrefix: string) => {
return `<template>
<${lineNameWithPrefix}></${lineNameWithPrefix}>
</template>
<script lang="ts" setup>
</script>
<style scoped lang="scss">
</style>
`
}
這兩個函數的模板可以自己去定義。
四個步驟都已實現,最後需要在 src/command/create-component.ts 檔案中的 createNewComponent 函數中完成上面四個 service 的呼叫。
匯入四個service及使用到的其他函數:
import { ComponentInfo } from '../domain/component-info'
import { closeLoading, showLoading } from '../util/loading-utils'
import { g, r } from '../util/log-utils'
import { initComponent } from '../service/init-component'
import { initScss } from '../service/init-scss'
import { updateComponentLib } from '../service/update-component-lib'
import { initDoc } from '../service/init-doc'
該函數首先根據使用者輸入,構造 ComponentInfo 物件,然後依次呼叫引入的四個 service,完成元件建立的全部流程:
const createNewComponent = async (componentName: string, description: string, componentType: string) => {
console.log(componentName, description, componentType)
showLoading('Generating, please wait...')
try {
// 1. 構造 ComponentInfo 物件
const componentInfo = new ComponentInfo(componentName, description, componentType)
// 2. 建立元件目錄及檔案
await initComponent(componentInfo)
// 3. 建立樣式
await initScss(componentInfo)
// 4. 更新元件庫入口
await updateComponentLib(componentInfo)
// 5. 元件庫檔案
initDoc(componentInfo)
closeLoading()
g(`component [${componentInfo.lineName} ${componentInfo.zhName}] created done!`)
} catch (e: any) {
closeLoading()
r(e.message)
}
}
元件庫 cli 就這樣完成了。執行 pnpm run gen,依次輸入元件名、元件中文名,選擇元件型別,便自動完成元件的建立、註冊、檔案的建立了。優雅哥花了大量篇幅介紹 cli 的開發,不僅僅可以在這裡使用,通過本案例的實現,希望大家可以將這種方式移植到其他地方,如從 github 拉取程式碼模板、自動化 CI/CD 等。
下一篇文章將介紹元件庫的打包構建和釋出。
感謝閱讀本文,如果本文給了你一點點幫助或者啟發,還請三連支援一下,瞭解更多內容工薇號「程式設計師優雅哥」。