从零开始的组件库开发之旅:Onionl-UI 的诞生🚀

引言

Hey,大家好!今天想和大家分享一个特别的项目 ------ Onionl-UI,这是我最近在开发的一个组件库。为什么叫 Onionl-UI?因为像洋葱一样,要一层层剥开才能看到其中的奥秘(其实是因为我的Github网名叫 Onion-L 啦 😆)。

为什么要开发这个组件库?

相信很多人看到这篇文章都会向我发出灵魂拷问:"市面上已经有那么多成熟的组件库了,为什么还要重复造轮子?"说得对,但我的目标并不是造轮子,而是通过这个项目来学习开发的流程,深入探索前端工程化的实践。

项目现状

目前 Onionl-UI 还处于萌芽阶段。作为前端行业的一名小学生,我深知项目中还存在诸多不完善之处。但我始终认为:"实践是检验真理的唯一标准。"因此我希望通过项目得到成长,并且我也会尽自己的努力让项目不烂尾。

接下来,就让我向大家汇报一下,我是如何开发的。

💡 特别提醒:本项目还在十分早期的开发阶段,功能并不完善,如果您发现任何问题或有任何建议,都欢迎在 GitHub 上提出 issue 或 PR。感谢您的每一个建议!

技术选型:Vue 3 + Vite 的黄金搭档 🤔

作为一名 Vue 开发者,选择 Vue 3 作为基础框架是一个自然而然的决定。这里也就不过多赘述。在构建工具的选择上,我最终选择了 Vite。说实话,这个决定做出得很快,因为 Vite 实在是太香了!但说到库的打包,很多人可能会想到 Rollup,它确实是一个非常优秀的打包工具。但在项目初期,考虑到开发效率和上手难度,我选择了更加简单直接的 Vite。并且 Vite 底层用的就是 Rollup,这就意味着如果未来需要更细粒度的打包控制,我随时可以平滑迁移到 Rollup。

UnoCSS:原子化 CSS 的探索

在样式解决方案上,我选择了 UnoCSS 这个新兴的原子化 CSS 引擎。为什么是 UnoCSS 而不是 Tailwind CSS?因为 UnoCSS 更加轻量和灵活,它的按需生成特性让最终的样式文件体积小得惊人。具体可以看看Anthony Fu 大佬的文章:重新构想原子化 CSS,详细讲解了关于UnoCSS的功能,我个人认为是很酷的。

目录结构

python 复制代码
onionl-ui/
├── packages/                # 组件源码目录
│   ├── components/         # 基础组件
│   │   ├── button/
│   │   ├── input/
│   │   └── ...
│   ├── hooks/             # 通用 hooks
│   ├── utils/             # 工具函数
│   └── onionl-ui/         # 组件入口文件夹
├── docs/                  # 文档站点
├── play/                  # 组件测试预览
├── preset/                # UnoCSS预设
└── scripts/              # 构建脚本

组件库打包

组件库的打包是一个非常关键的环节,它决定了组件如何被其他开发者使用。让我来详细解释一下 Onionl-UI 的打包策略。

打包配置的核心思路

打包脚本主要完成三个核心任务:收集源文件、构建不同格式的输出、复制必要的文档文件。来看看具体实现:

ts 复制代码
async function buildAll() {
  // 1. 收集所有需要打包的源文件
  const input = excludeFiles(await glob('**/*.{js,ts,vue}', {
    cwd: pkgPath,
    absolute: true,
    onlyFiles: true,
  }))

  // 2. 针对不同的输出格式进行构建
  buildConfig.forEach(async ({ outPath, format, extend }) => {
    await build({
      build: {
        rollupOptions,
        minify: false,  // 保持代码可读性,方便调试
        sourcemap: true,  // 支持源码映射
        outDir: resolve(rootPath, outPath),
        lib: {
          entry: input,
          formats: [format],
          fileName: () => `[name].${extend}`
        }
      },
      plugins: [
        vue(),
        vueJsx(),
        UnoCSS(),
        dts({  // 生成类型声明文件
          include: ['packages/**/**/*.{vue,ts,tsx}'],
          exclude: ['packages/**/test/**'],
          outDir: 'dist/es',
          staticImport: true,
          insertTypesEntry: true,
        })
      ]
    })
  })

  // 3. 复制必要的文档文件
  await copyFiles()
}

接下来关于组件的写法就不过多赘述了,简单讲一下就是组件可以写成我们平时开发时那样的 vue 文件,同样可以使用 defineComponent结合 TSX/JSX 语法来实现更加高自由度高性能的复杂组件。

组件库单元测试:从 Button 组件说起 🧪

Button组件是最简单的,同时也是当前组件库第一个实现的组件(虽然目前为止也只写了两个😅),在众多测试框架中,我选择了 Vitest 作为测试框架。因为它完美集成了 Vite 生态。

安装必要的依赖:

css 复制代码
pnpm i -D vitest happy-dom @vue/test-utils

测试这一方面我之前不太了解,只知道单元测试可以用来测函数的输入与输出,但是组件是跑在浏览器上的,显然nodejs环境无法完成,这时就需要 happy-dom 在nodejs环境来模拟DOM。而@vue/test-utils 则是 Vue 官方的测试工具集,能更方便地测试 Vue 组件。

配置Vitest:

ts 复制代码
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vitest/config'
  
export default defineConfig({
  plugins: [vue()],
  test: {
    environment: 'happy-dom',
    globals: true,
    testTimeout: 10000,
    coverage: {
      reporter: ['text', 'json', 'html'],
      exclude: ['play/**'],
    },
  },
})

之后就可以编写测试用例了,这里先给出一个最简单的测试,测试了按钮组件的一些默认的内容:

ts 复制代码
describe('button Component', () => {
  it('renders default button correctly', () => {
    const wrapper = mount(Button, {
      slots: {
        default: 'Button Text',
      },
    })

    expect(wrapper.classes()).toContain('ol-button')
    expect(wrapper.text()).toBe('Button Text')
    expect(wrapper.classes()).toContain('ol-button__size-sm')
    expect(wrapper.classes()).toContain('ol-button__type-primary')
  })
})

总结

开发这个组件库的初衷,并不是为了重复造轮子,而是希望通过实践来深入学习前端工程化的流程。作为一名前端新人,我深知项目中还有很多不完善之处,但正是这种不完美才给了我改进的空间和学习的机会。

如果您觉得这个项目有价值,欢迎给我一个 star ⭐️(项目地址:Onionl-UI)。您的每一个建议和反馈,都将帮助这个项目变得更好。让我们一起在实践中成长!

道阻且长,行则将至。与诸君共勉。

相关推荐
一枚前端小能手16 分钟前
🚀 Webpack构建等到怀疑人生?试试这几个优化
前端·webpack
入秋30 分钟前
2025年项目中是怎么初始化Three.js三维场景的
前端·three.js
托尼_Captain36 分钟前
uniapp封装全局request请求
前端
ze_juejin1 小时前
Fetch API 详解
前端
用户66982061129821 小时前
js今日理解 blob和arrayBuffer 二进制数据
前端·javascript
想想肿子会怎么做1 小时前
Flutter 环境安装
前端·flutter
断竿散人1 小时前
Node 版本管理工具全指南
前端·node.js
转转技术团队1 小时前
「快递包裹」视角详解OSI七层模型
前端·面试
1024小神1 小时前
Ant Design这个日期选择组件最大值最小值的坑
前端·javascript
卸任1 小时前
Electron自制翻译工具:自动更新
前端·react.js·electron