vue3-UI组件库开发

629 阅读7分钟

vue3-UI组件库开发

UI组件是对一组相关的交互和样式的封装,提供了简单的调用方式和接口,让开发者能很便捷地使用组件提供的功能来实现业务需求。

在开发一个组件库之前,我们通常会想想为什么要去开发UI组件库?随着开发的应用越来越多,在各个应用中会出现很多类似的组件,我们就可以把这些的组件抽离出来,封装成一个组件库。封装成一个组件库有很多好处,例如:节省开发成本、统一设计和交互等。

下面是我对于一个UI组件库开发的一些思考,它不完全是合理的,如有错漏,希望大家指正。

组件库开发需要解决的问题

  1. 制定规范
  2. 初始化项目
  3. 支持多种安装方式
  4. 全量加载和按需加载
  5. 提供组件
  6. 定制主题
  7. 国际化
  8. 组件库使用文档
  9. 单元测试
  10. 发布

制定规范

1、定制样式变量。规划好哪些使用变量,保持样式一致。可以参考别的组件库自定义主题有哪些内容

  • 主题色
  • 字体、字体大小、字体颜色
  • 背景颜色、阴影颜色、圆角大小
  • 禁用状态、hover状态等
  • 动画、间距
  • 等等

fontSize.png

2、组件设计规范

  • 统一的交互规则,譬如:默认状态是什么样,hover状态是什么样,disable状态是什么样,空状态、按下、点击、双击、失焦、聚焦、反馈等等
  • 定义好组件入参的类型
  • 是否支持无障碍访问

3、编码规范

  • typescript还是javascript

  • eslint

4、图标库

  • 提供一套风格统一的图标库
  • 使用字体实现还是svg实现

5、其他如命名规范、文档规范、git规范等

初始化项目

  1. 创建文件夹jUI并进入
  2. npm init
  3. 安装依赖
npm i rollup -g        # 全局安装
npm i rollup -D        # 项目本地安装

插件安装有问题,可能是node版本问题,找到适合当前node版本的插件即可。如果有其他插件兼容,可以百度。

npm i
rollup-plugin-babel @babel/core @babel/preset-env  // 用于转换es6
rollup-plugin-commonjs // 支持CommonJS模块
rollup-plugin-postcss postcss // 处理样式
autoprefixer // css3的一些属性加前缀
cssnano // css压缩
vue //
rollup-plugin-vue @vue/compiler-sfc // 编译vue文件
rollup-plugin-terser // 代码压缩
@rollup/plugin-json // 解析json
node-sass sass // sass样式
rollup-plugin-copy // 拷贝文件
rimraf // 删除文件夹
vitest // 单元测试框架
happy-dom // 模拟浏览器,用于测试
@vue/test-utils //  Vue.js 官方的单元测试实用工具库
@vitest/coverage-c8 // 测试用例覆盖率
 -D
// .babelrc
{
  "presets": [
    [
      "@babel/preset-env"
    ]
  ]
}
// rollup.config.js
import babel from 'rollup-plugin-babel'
import commonjs from 'rollup-plugin-commonjs'
import postcss from 'rollup-plugin-postcss'
import autoprefixer from 'autoprefixer'
import cssnano from 'cssnano' // css压缩
import vue from 'rollup-plugin-vue' // 处理vue文件插件
import { terser } from 'rollup-plugin-terser' // 压缩
import json from '@rollup/plugin-json'
import { name } from './package.json'
import path from 'path'
import copy from 'rollup-plugin-copy'

const file = type => `lib/${name}.${type}.js`

export { name, file }

export default {
  input: './src/index.js',
  output: {
    name,
    file: file('esm'),
    format: 'es',
    globals: {
      vue: 'Vue',
      'lodash-es': '_'
    }
  },
  plugins: [
    vue(),
    babel({
      exclude: 'node_modules/**'
    }),
    commonjs(),
    postcss({
      extract: path.resolve('lib/common.css'),
      plugins: [
        autoprefixer(),
        cssnano()
      ]
    }),
    terser(),
    json(),
    copy({
      targets: [
        { src: 'src/components/theme', dest: 'lib' },
        { src: 'src/locale', dest: 'lib' }
      ]
    })
  ],
  external: ['vue', 'jUI']
}
// rollup.esm.config.js
import basicConfig, {file, name} from './rollup.config'
export default {
    ...basicConfig,
  output: {
    name,
    file: file('esm'),
    format: 'es'
  }
}
// rollup.umd.config.js
import basicConfig, { name, file } from './rollup.config'
export default {
  ...basicConfig,
  output: {
    name,
    file: file('umd'),
    format: 'umd',
    globals: { // 设定全局变量的名称
      'vue': 'Vue',
      'lodash-es': '_'
    },
    exports: 'named'
  }
}
// rollup.components.config.js
import basicConfig from './rollup.config'
import components from './components'

const config = []

Object.keys(components).forEach(key => {
  config.push({
    ...basicConfig,
    input: components[key],
    output: {
      file: `./lib/${key}/index.js`,
      format: 'es',
      globals: {
        vue: 'Vue',
        'lodash-es': '_'
      }
    }
  })
})
export default config
// components.js
export default {
  "button": "./src/components/button/index.js",
  "icon": "./src/components/icon/index.js",
  "message": "./src/components/message/index.js"
}
// package.json
{
  "name": "jUI",
  "version": "1.0.2",
  "description": "",
  "main": "./lib/jUI.umd.js",
  "module": "./lib/jUI.esm.js",
  "scripts": {
    "build": "npm run build:esm && npm run build:umd && npm run build:components",
    "build:esm": "rollup --config ./rollup.esm.config.js",
    "build:umd": "rollup --config ./rollup.umd.config.js",
    "build:components": "rollup --config ./rollup.components.config.js",
    "clean": "rimraf ./lib",
    "test": "vitest",
    "coverage": "vitest run --coverage"
  },
  ...
}
// vitest.config.js
import { resolve as _resolve } from 'path'
import { defineConfig } from 'vitest/config'

const resolve = (p) => _resolve(__dirname, p)

export default defineConfig({
  test: {
    include: ['tests/unit/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
    globals: true,
    environment: 'happy-dom',
    alias: {
      '@': resolve('src')
    },
    coverage: {
      provider: 'c8',
      reporter: ['text', 'json', 'html'],
    },
  }
})
jUI                                             //
|-.babelrc                                      //
|-.eslintrc.js                                  //
|-.gitignore                                    // git忽略配置文件
|-.npmignore                                    // npm忽略配置文件
|-components.js                                 // 需要单独打包的组件
|-lib                                           // 打包后代码
| |-button                                      //
| | |-index.js                                  //
| |-common.css                                  //
| |-icon                                        //
| | |-index.js                                  //
| |-jUI.esm.js                                  //
| |-jUI.umd.js                                  //
| |-locale                                      //
| | |-index.js                                  //
| | |-lang                                      //
| | | |-es.js                                   //
| | | |-zh-CN.js                                //
| |-message                                     //
| | |-index.js                                  //
| |-theme                                       //
| | |-button.scss                               //
| | |-common                                    //
| | | |-base.scss                               //
| | | |-var.scss                                //
| | |-icon.scss                                 //
| | |-index.scss                                //
| | |-message.scss                              //
|-package-lock.json                             //
|-package.json                                  //
|-rollup.components.config.js                   // 单独组件打包配置文件
|-rollup.config.js                              // rollup配置
|-rollup.esm.config.js                          //
|-rollup.umd.config.js                          //
|-src                                           // 源代码
| |-components                                  // 组件
| | |-button                                    //
| | | |-index.js                                //
| | | |-src                                     //
| | | | |-Button.js                             //
| | |-icon                                      //
| | | |-index.js                                //
| | | |-src                                     //
| | | | |-Icon.js                               //
| | |-message                                   //
| | | |-index.js                                //
| | | |-src                                     //
| | | | |-Message.js                            //
| | |-theme                                     // 主题
| | | |-button.scss                             //
| | | |-common                                  //
| | | | |-base.scss                             //
| | | | |-var.scss                              //
| | | |-icon.scss                               //
| | | |-index.scss                              //
| | | |-message.scss                            //
| |-index.js                                    // 总入口文件
| |-locale                                      // 国际化
| | |-index.js                                  //
| | |-lang                                      //
| | | |-es.js                                   //
| | | |-zh-CN.js                                //
|-tests                                         // 测试
| |-unit                                        //
| | |-specs                                     //
| | | |-button.spec.js                          //
| | |-util.js                                   //
|-vitest.config.js                              // 测试配置文件

提供组件

Button组件

src/components中新建button文件夹,如下

|-button                                    // 组件文件夹
  |-index.js                                // 组件入口文件
  |-src                                     //
    |-Button.js                             // 组件
// Button.js
import { h } from 'vue'
import Icon from '../../icon/index.js'
// import { t } from '../../../locale/index.js'
import { t } from 'jUI/lib/locale/index.js'

export default {
  name: 'jButton',
  props: ['icon'],
  setup (props, { emit }) {
    return () => {
      return h('button', {
        class: 'j_button',
        onClick: () => {
          emit('buttonClick')
        }
      }, [
        props.icon ? h(Icon, {
          type: props.icon
        }) : '',
        t('jhs.button.text')
      ])
    }
  }
}

t函数是为国际化做的,具体在国际化部分讲

// src/components/button/index.js
// 导入组件,组件必须声明 name
import Button from './src/Button.js'

// 为组件提供 install 安装方法,供按需引入
Button.install = function (app) {
  app.component(Button.name, Button)
}

// 默认导出组件
export default Button

Icon组件

src/components中新建icon文件夹,如下

|-icon                                    // 组件文件夹
  |-index.js                              // 组件入口文件
  |-src                                   //
    |-Icon.js                             // 组件
// Icon.js
import { h } from 'vue'

const customCache = new Set()

function createFromIconfont (scriptUrl = '//at.alicdn.com/t/font_1452953_5psa3u7r2bs.js') {
  if (typeof document !== 'undefined' && typeof window !== 'undefined'
    && typeof document.createElement === 'function'
    && typeof scriptUrl === 'string' && scriptUrl.length
    && !customCache.has(scriptUrl)) {
    var script = document.createElement('script')
    script.setAttribute('src', scriptUrl)
    script.setAttribute('data-namespace', scriptUrl)
    customCache.add(scriptUrl)
    document.body.appendChild(script)
  }
  return {
    name: 'jIcon',
    props: {
      type: String,
      size: [Number, String]
    },
    setup (props) {
      return () => h('i', {
        class: {
          j_icon: true
        },
        style: {
          fontSize: props.size ? `${props.size}px` : '12px'
        },
      }, [h('svg', {
        viewBox: '0 0 1024 1024',
        fill: 'currentColor',
        width: '1em',
        height: '1em',
        focusable: 'false'
      }, [h('use', { 'xlink:href': '#' + props.type })])])
    }
  }
}

const Icon = createFromIconfont()
Icon.createFromIconfont = createFromIconfont

export default Icon
// src/components/icon/index.js
// 导入组件,组件必须声明 name
import Icon from './src/Icon.js'

// 为组件提供 install 安装方法,供按需引入
Icon.install = function (app) {
  app.component(Icon.name, Icon)
}

// 默认导出组件
export default Icon

Message组件

src/components中新建message文件夹,如下

|-message                                    // 组件文件夹
  |-index.js                                 // 组件入口文件
  |-src                                      //
    |-Message.js                             // 组件
// Message.js
let seed = 1

class Message {
  constructor (opt) {
    this.msg = typeof opt === 'string' ? opt : (opt && opt.msg ? opt.msg : '加载中...')
    this.time = opt && typeof opt.time !== 'undefined' ? opt.time : 3000
    this.id = `message_${seed++}`
    this.ele = null
    this.init()
  }

  init () {
    this.creatNode()
    if (typeof this.time === 'number' && this.time !== 0) {
      setTimeout(() => {
        this.close()
      }, this.time)
    }
  }

  creatNode () {
    this.ele = document.createElement('div')
    this.ele.id = this.id
    this.ele.className = 'message_box'
    let p = document.createElement('p')
    p.innerHTML = this.msg
    this.ele.appendChild(p)
    document.body.appendChild(this.ele)
  }
  close () {
    this.ele && document.body.removeChild(this.ele)
  }
}

export default function message (opt) {
  return new Message(opt)
}
// src/components/message/index.js

// 导入组件,组件必须声明 name
import Message from './src/Message'

// 为组件提供 install 安装方法,供按需引入
Message.install = function (app) {
  app.config.globalProperties.$message = Message
}
// 默认导出组件
export default Message

总入口

src/index.js作为整个组件库的入口文件

// src/index.js
import Button from './components/button/index.js'
import Icon from './components/icon/index.js'
import Message from './components/message/index.js'

import locale from './locale/index.js'
// import locale from 'jUI/lib/locale/index.js'

import { version } from '../package.json'

import './components/theme/index.scss'

// 存储组件列表
const components = {
  Button,
  Icon
}

// 定义 install 方法,接收 app 作为参数。如果使用 use 注册插件,则所有的组件都将被注册
export const install = function (app, opts = {}) {
  // locale.use(opts.locale)
  // 判断是否安装
  if (install.installed) return
  // 遍历注册全局组件
  Object.keys(components).forEach(key => {
    const component = components[key]
    app.component(component.name, component)
  })
  app.config.globalProperties.$message = Message
  
  locale.use(opts.locale)

  install.installed = true
}

export {
  Button,
  Icon,
  Message,
  locale
}

export default {
  version: version,
  // 导出的对象必须具有 install,才能被 app.use() 方法安装
  install
}

打包

npm run build 打包项目,输出在lib文件夹中

|-lib                                           // 打包后代码
| |-button                                      // Button单独打包后文件
| | |-index.js                                  //
| |-common.css                                  // 总的样式
| |-icon                                        // Icon单独打包后文件
| | |-index.js                                  //
| |-jUI.esm.js                                  // esm文件
| |-jUI.umd.js                                  // umd文件
| |-locale                                      // 国际化
| | |-index.js                                  //
| | |-lang                                      // 语言包
| | | |-es.js                                   //
| | | |-zh-CN.js                                //
| |-message                                     // Message单独打包后文件
| | |-index.js                                  //
| |-theme                                       // 主题
| | |-button.scss                               //
| | |-common                                    //
| | | |-base.scss                               //
| | | |-var.scss                                //
| | |-icon.scss                                 //
| | |-index.scss                                //
| | |-message.scss                              //

支持多种安装方式

  1. 页面中直接引入。打包后的jUI.umd.js的文件是支持直接引入到页面中的

  2. 支持npm安装,配置main、module

// package.json
{
  ...
  "main": "./lib/jUI.umd.js",
  "module": "./lib/jUI.esm.js",
  ...
}

全量加载和按需加载

  1. 全量加载,import jUi from 'jUI'

  2. 按需加载

    • import Message from 'jUI/lib/message'

    • import { Icon, Button, locale } from 'jUI',通过babel插件实现按需加载【babel-plugin-import】

    • Tree Shaking方式,需要打包成 esmodule 格式,可能因为一些文件的“副作用”导致没法按需加载。可参考【你的Tree-Shaking并没什么卵用】

    • unplugin-vue-components插件,完全不需要自己来引入组件,直接在模板里使用,由插件来扫描引入并注册,但是需要自己来实现解析器。这个插件做的事情只是帮我们引入组件并注册,实际上按需加载的功能还是得靠前面两种方式。

配置.babelrc

// .babelrc
{
  "presets": [
    "@vue/cli-plugin-babel/preset",
    [
      "@babel/preset-env"
    ]
  ],
  "plugins": [
    [
      "import", {
        "libraryName": "jUI",
        "libraryDirectory": "lib"
      }
    ]
  ]
}

当前组件库的按需加载只实现了1、2种方式

本地调试

vue create jUI-test 使用@vue/cli重新起一个项目,这个项目也可以作为使用文档

  1. 在组件库根目录npm link

  2. 在jUI-test项目的根目录npm link jUI

  3. jUI-testpackage.json里配置依赖

"dependencies": {
  "jUI": "1.0.0"
},
  1. jUI-test项目引入jUI
import { createApp } from 'vue'
import App from './App.vue'
// import jUi from 'jUI'
// import es from 'jUI/lib/locale/lang/es'
import zh from 'jUI/lib/locale/lang/zh-CN'
// import 'jUI/lib/common.css'
import { Icon, Button, locale } from 'jUI'
import './assets/scss/variables.scss'
locale.use(zh)
createApp(App).use(Icon).use(Button).mount('#app')

npm run build -- --report 输出依赖报告

定制主题

这里只介绍覆盖sass默认变量的方式

我们在引入组件库样式的时候,是设计的一次引入全部样式,即使是按需引入组件,如果你想按需引入组件样式,需要做结构调整。

这部分代码在src/components/theme

// src/components/theme/common/var.scss
// 主题色
$orange-color: #FF6800 !default;
// src/components/theme/index.scss
// 引入所有组件样式
@import "./common/base.scss";
@import "./button.scss";
@import "./icon.scss";
@import "./message.scss";

在使用的时候,做样式覆盖即可

// variables.scss
$orange-color: blue;
@import '~jhs-ui/lib/theme/index.scss'

在main.js里引入

import './assets/scss/variables.scss'

国际化

locale                                      // 国际化
| | |-index.js                              //
| | |-lang                                  //
| | | |-es.js                               //
| | | |-zh-CN.js                            //

src/locale文件夹中

// src/locale/index.js
import defaultLang from './lang/zh-CN'

let lang = defaultLang

export const t = function (path) {
  const array = path.split('.')
  let current = lang
  console.log(current)
  for (let i = 0, j = array.length; i < j; i++) {
    const property = array[i]
    const value = current[property]
    if (i === j - 1) return value
    if (!value) return ''
    current = value
  }
  return ''
}

export const use = function (l) {
  lang = l || lang
}

export default { use, t }

src/locale/lang创建es.jszh-CN.js

// es.js
export default {
  jhs: {
    button: {
      text: 'button'
    }
  }
}
// zh-CN.js
export default {
  jhs: {
    button: {
      text: '按钮'
    }
  }
}

在组件中引入src/locale/index.js

// src/components/button/src/Button.js
import { h } from 'vue'
import Icon from '../../icon/index.js'
// import { t } from '../../../locale/index.js'
import { t } from 'jUI/lib/locale/index.js'

export default {
  name: 'jButton',
  props: ['icon'],
  setup (props, { emit }) {
    return () => {
      return h('button', {
        class: 'j_button',
        onClick: () => {
          emit('buttonClick')
        }
      }, [
        props.icon ? h(Icon, {
          type: props.icon
        }) : '',
        t('jhs.button.text')
      ])
    }
  }
}

这里的t函数不能这样引入import { t } from '../../../locale/index.js',这样打包的时候会把t函数和默认的语言包打包进当前组件,导致自定义语言的时候失效

公共入口也是一样:

// import locale from './locale/index.js'
import locale from 'jUI/lib/locale/index.js'

export const install = function (app, opts = {}) {
  locale.use(opts.locale)
}

需要:

  1. import { t } from 'jUI/lib/locale/index.js'这样引入

  2. rollup.config.js里排除掉jUI

// rollup.config.js
{
  ...,
  external: ['vue', 'jUI']
}

项目中使用

// main.js
import { createApp } from 'vue'
import App from './App.vue'
// import jUi from 'jUI'
// import es from 'jUI/lib/locale/lang/es'
import zh from 'jUI/lib/locale/lang/zh-CN'
// import 'jUI/lib/common.css'
import { Icon, Button, locale } from 'jUI'
import './assets/scss/variables.scss'
locale.use(zh)
createApp(App).use(Icon).use(Button).mount('#app')

组件库使用文档

可以使用上面创建的jUI-test作为组件库的使用文档

书写文档使用markdown有时候更方便,在vue里使用markdown文档

npm i -D html-loader@1.3.0 markdown-loader
npm i github-markdown-css

vue.config.js里配置

chainWebpack: config => {
  config.module
    .rule('md')
    .test(/\.md/)
    .use('html-loader')
    .loader('html-loader')
    .end()
    .use('markdown-loader')
    .loader('markdown-loader')
    .end()
}

使用:

<div class="markdown-body" v-html="md"></div>
import md from './apiDoc/button.md'

单元测试

使用 vitest 框架

vue-test-utils 文档地址

配置vitest.config.js

// vitest.config.js
import { resolve as _resolve } from 'path'
import { defineConfig } from 'vitest/config'

const resolve = (p) => _resolve(__dirname, p)

export default defineConfig({
  test: {
    include: ['tests/unit/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
    globals: true,
    environment: 'happy-dom',
    alias: {
      '@': resolve('src')
    },
    coverage: {
      provider: 'c8',
      reporter: ['text', 'json', 'html'],
    },
  }
})

测试用例

|-tests                                         // 测试
| |-unit                                        //
| | |-specs                                     //
| | | |-button.spec.js                          //
| | |-util.js                                   //
|-vitest.config.js                              // 测试配置文件
// util.js
import { mount } from '@vue/test-utils'
/**
 * 创建一个 wrapper
 * @param {Object} Comp 组件对象
 * @param {Object} propsData props 数据
 * @return {Object} wrapper
 */
export const createWrapper = function (Comp, propsData = {}) {
  const wrapper = mount(Comp, {
    ...propsData
  })

  return wrapper
}

测试button组件

// tests/unit/specs/button.spec.js
import { describe, expect, test } from "vitest"
import { createWrapper } from '../util'
import Button from '@/components/button/'

describe('Button.vue', () => {
  let wrapper

  test('create', () => {
    wrapper = createWrapper(Button)
    expect(wrapper.classes()).toContain('j_button')
  })

  test('props', () => {
    wrapper = createWrapper(Button, {
      propsData: {
        icon: 'icon-close'
      }
    })
    const barByName = wrapper.findComponent({ name: 'jIcon' })
    expect(barByName.exists()).toBe(true)
  })

  test('click', async () => {
    
    wrapper = createWrapper(Button)

    const el = wrapper.find('button')

    await el.trigger('click')

    // 断言事件已经被触发
    expect(wrapper.emitted().buttonClick).toBeTruthy()

    // 断言事件的数量
    expect(wrapper.emitted().buttonClick.length).toBe(1)

    await el.trigger('click')
    expect(wrapper.emitted().buttonClick.length).toBe(2)
    
  })
})

package.json配置script命令

{
  "scripts": {
    "test": "vitest",
    "coverage": "vitest run --coverage"
  }
}

发布

package.json

  • 删除 private: true
  • 修改"license": "MIT"
  • 修改author
  • 添加关联的git仓库
{
  "name": "jUI",
  "version": "1.0.2",
  "description": "vue3组件库",
  "main": "./lib/jUI.umd.js",
  "module": "./lib/jUI.esm.js",
  "scripts": {
    "build": "npm run build:esm && npm run build:umd && npm run build:components",
    "build:esm": "rollup --config ./rollup.esm.config.js",
    "build:umd": "rollup --config ./rollup.umd.config.js",
    "build:components": "rollup --config ./rollup.components.config.js",
    "clean": "rimraf ./lib",
    "test": "vitest",
    "coverage": "vitest run --coverage"
  },
  "author": "zrd",
  "license": "MIT",
  "devDependencies": {
    "@babel/core": "^7.18.13",
    "@babel/preset-env": "^7.18.10",
    "@rollup/plugin-json": "^4.1.0",
    "@vitejs/plugin-vue": "^3.1.2",
    "@vitest/coverage-c8": "^0.24.3",
    "@vue/compiler-sfc": "^3.2.37",
    "@vue/test-utils": "^2.0.0-rc.18",
    "autoprefixer": "^10.4.8",
    "cssnano": "^5.1.13",
    "happy-dom": "^7.6.0",
    "node-sass": "^7.0.1",
    "postcss": "^8.4.16",
    "rimraf": "^3.0.2",
    "rollup-plugin-babel": "^4.4.0",
    "rollup-plugin-commonjs": "^10.1.0",
    "rollup-plugin-copy": "^3.4.0",
    "rollup-plugin-postcss": "^4.0.2",
    "rollup-plugin-terser": "^7.0.2",
    "rollup-plugin-vue": "^6.0.0",
    "sass": "^1.54.5",
    "vitest": "^0.24.3",
    "vue": "^3.2.37"
  },
  "browserslist": [
    "defaults",
    "not ie < 8",
    "last 2 versions",
    "> 1%",
    "iOS 7",
    "last 3 iOS versions"
  ],
  "dependencies": {},
  "repository": {
    "type": "git",
    "url": "xxx"
  }
}

配置.npmignore

.DS_Store
node_modules
src
tests
coverage

.eslintrc.js
..babelrc
.gitignore
.npmignore
components.js
vitest.config.js

rollup.*.js

# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

npm 发布,不要重名了,注意不要把公司代码发到外网,gitlab上可以通过 npm i git+gitlabURL 安装

npm login
npm publish

最后,一个UI组件库从开发到后期维护,也是需要投入很多的人力、精力,需要充分评估好可行性。

参考资料

vue3

rollup

babel-plugin-import

babel-plugin-import

你的Tree-Shaking并没什么卵用

unplugin-vue-components插件

vitest 框架

vue-test-utils 文档地址

vant

antd