vue3 组件库框架实践

本文介绍了 vue3组件库框架实践


1 组件样式文件并导入

src/service 目录中创建 init-scss.ts 文件,该文件导出 initScss 函数。

由于 .vue 类型的组件的样式就直接写在了 style 中,故首先判断组件类型是否是 tsx,tsx 类型的组件才进行这一步的操作:

  1. scss/components/ 目录下创建组件的 scss 文件 _xxx.module.scss
  2. scss/components/index.scss 中导入 _xxx.module.scss

1.1 init-scss.ts

代码实现如下:

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)
    })

1.2 template-utils.ts

上面的 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}') {
    }
    `
    }

    2 添加到组件库入口模块

    新组件和样式创建完成,接下来便是将新组件模块安装到组件库入口模块的依赖中。在 src/service 目录中创建 update-component-lib.ts 文件,该文件导出函数 updateComponentLib。该函数需要完成两件事:

    1. 在组件库入口模块中安装新组件为依赖;
    2. 更新组件库入口模块的 index.ts 文件,引入新组件。

    代码实现如下:

    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')
    }

    3 组件库文档相关文件

    3.1 init-doc.ts

    src/service 目录中创建 init-doc.ts 文件,该文件导出函数 initDoc。该函数需要完成三件事:

    1. 创建组件的 MarkDown 文档;
    2. 创建组件 MD 文档中的 demo;
    3. 更新组件库文档菜单。

    代码实现如下:

    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')
    }

    3.2 template-utils.ts

    上面的 init-doc.ts 调用了 mdTemplatedemoTemplate 两个函数,在 template-utils.ts 中添加这两个函数:

    export const mdTemplate = (componentInfo: ComponentInfo) => {
    return `

    ${componentInfo.upCamelName} ${componentInfo.zhName}

    基本使用

    组件 API

    Attributes 属性

    参数 说明 类型 可选值 默认值

    Methods 方法

    方法名 说明 参数 返回值

    Events 事件

    事件名 说明 参数 返回值

    Slots 插槽

    插槽名 说明 参数
    `
    }

    export const demoTemplate = (lineNameWithPrefix: string) => {
    return `

    `
    }

    这两个函数的模板可以自己去定义。

    4 create-component.ts

    四个步骤都已实现,最后需要在 src/command/create-component.ts 文件中的 createNewComponent 函数中完成上面四个 service 的调用。

    4.1 import

    导入四个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'

    4.2 createNewComponent

    该函数首先根据用户输入,构造 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 等。


    上一篇 下一篇


    推荐文章

    评论
    说点什么吧?

    发表评论

    取消回复
      最新文章