尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Vue 3 UI组件库从零搭建:Monorepo架构、按需加载与工程化实践

Vue 3 UI组件库从零搭建:Monorepo架构、按需加载与工程化实践 如果你是一名 Vue 开发者是否曾有过这样的困惑项目里用了不少第三方 UI 库但总有几个组件样式不符合业务需求改起来又怕破坏原有逻辑或者团队内部缺乏一套统一的视觉和交互规范导致每个项目都像在“打补丁”更关键的是当面试官问起“你了解 UI 组件库的设计和实现吗”时很多人只能泛泛而谈却说不清一个按钮从设计到发布的全链路细节。这篇文章要解决的正是这个“知其然不知其所以然”的问题。我们不会空谈“组件化思想”或“设计模式”而是用 Vue 3.4 这个当前最稳定、性能最优的版本从零开始手把手带你搭建一套真正可发布、可维护、可复用的 UI 组件库。这不是一个简单的 Demo而是一个包含单包构建、多包管理Monorepo、自动化文档、按需加载、主题定制、单元测试的完整工程化实践。读完本文你将彻底掌握工程基石如何用 Vite TypeScript pnpm workspace 搭建一个现代前端 Monorepo 项目结构。组件核心如何设计一个高内聚、低耦合的 Vue 组件包括 Props、Slots、Events、Expose 等 API 设计规范。打包艺术如何配置构建工具同时输出 ES Module、CommonJS、UMD 等多种格式并支持 Tree Shaking。开发者体验如何集成 Vitepress 实现自动化组件文档和演示让使用和协作变得简单。质量保障如何为组件编写单元测试Vitest和类型声明确保代码健壮性。我们直接从最关键的工程决策开始。1. 为什么从零搭建理解现代 UI 库的完整生命周期在直接敲代码之前我们必须想清楚在 Ant Design、Element Plus 等成熟方案遍地的今天为什么还要自己造轮子答案不在于“替代”而在于“理解”和“掌控”。自己搭建一遍你会深刻理解以下问题这些是单纯使用库无法获得的认知依赖管理的边界你的组件库应该依赖 Vue 本身但要不要依赖 Lodash、Day.js 这些工具库如何避免版本冲突和包体积膨胀样式方案的抉择是用 CSS-in-JS如 unocss、预处理器Sass/Less还是纯 CSS 变量如何实现主题切换和暗黑模式类型安全的保障如何利用 TypeScript 提供完美的类型提示让使用者在编码阶段就能发现错误版本发布的流程如何管理多个包的版本号如何自动化生成 CHANGELOG如何发布到私有或公有仓库本次实战我们将采用目前社区最主流的“黄金组合”Vue 3.4 Vite 5 TypeScript pnpm Monorepo。这个组合在开发体验、构建速度和包管理效率上达到了最佳平衡。2. 项目初始化与 Monorepo 结构搭建我们首先创建项目的根目录并初始化包管理和工作区配置。2.1 初始化项目与 pnpm workspace# 创建项目根目录 mkdir vue-ui-library cd vue-ui-library # 初始化 package.json pnpm init # 创建 pnpm-workspace.yaml 文件定义工作区 cat pnpm-workspace.yaml EOF packages: - packages/* - docs - play # 用于开发调试的 playground EOFpnpm-workspace.yaml文件是 pnpm Monorepo 的核心它告诉 pnpm 哪些目录是独立的子包。2.2 创建核心包与文档包接下来我们创建主要的包结构# 创建 packages 目录存放核心包 mkdir -p packages/core mkdir -p packages/utils mkdir -p docs mkdir -p play # 初始化核心组件库包 cd packages/core pnpm init # 将包名改为 vue-ui-library/corescope 可以根据需要修改编辑packages/core/package.json设置基本信息和入口{ name: vue-ui-library/core, version: 0.0.1, description: A Vue 3 UI component library., type: module, main: ./dist/vue-ui-library.umd.cjs, module: ./dist/vue-ui-library.mjs, types: ./dist/index.d.ts, exports: { .: { import: ./dist/vue-ui-library.mjs, require: ./dist/vue-ui-library.umd.cjs, types: ./dist/index.d.ts }, ./*: ./* }, files: [ dist ], scripts: { build: vite build }, peerDependencies: { vue: ^3.4.0 }, devDependencies: { vitejs/plugin-vue: ^5.0.0, vite: ^5.0.0, vue: ^3.4.0 } }关键点解析name: 使用scope/package-name的形式这是管理私有或组织包的常见做法。exports: 现代包的入口定义清晰地声明了 ESM、CJS 和类型的路径对工具链友好。peerDependencies: 将vue声明为 peer 依赖是开发 UI 库的最佳实践。这能确保你的库使用项目中的 Vue 实例避免多个 Vue 副本导致的错误。files: 指定发布到 npm 时包含的文件通常只有构建产物dist目录。2.3 配置根目录的 TypeScript 和 Vite回到项目根目录安装共享的开发依赖# 在根目录执行 pnpm add -Dw typescript types/node vite vitejs/plugin-vue vue-tsc-Dw表示作为开发依赖devDependencies安装到根 workspace。创建根目录的tsconfig.json和vite.config.ts作为基础配置// tsconfig.json (根目录) { compilerOptions: { target: ES2020, useDefineForClassFields: true, lib: [ES2020, DOM, DOM.Iterable], module: ESNext, skipLibCheck: true, moduleResolution: bundler, allowImportingTsExtensions: true, resolveJsonModule: true, isolatedModules: true, noEmit: true, jsx: preserve, strict: true, noUnusedLocals: true, noUnusedParameters: true, noFallthroughCasesInSwitch: true, baseUrl: ., paths: { vue-ui-library/*: [packages/*/src] } }, include: [packages/**/*.ts, packages/**/*.tsx, packages/**/*.vue], references: [ { path: ./packages/core } ] }paths配置让我们在代码中可以使用vue-ui-library/core这样的别名导入。3. 开发第一个组件Button理论铺垫完成现在开始实战。我们从最基础的Button组件开始。3.1 组件源码结构在packages/core/src下创建组件目录和文件packages/core/src/ ├── button/ │ ├── Button.vue // 组件模板与逻辑 │ ├── index.ts // 组件导出文件 │ └── style.css // 组件样式 ├── index.ts // 库的主入口文件 └── vite-env.d.ts // Vite 环境类型声明首先编写Button.vue!-- packages/core/src/button/Button.vue -- template button classvul-button :class[ vul-button--${type}, vul-button--${size}, { is-plain: plain, is-round: round, is-circle: circle, is-disabled: disabled || loading, is-loading: loading } ] :disableddisabled || loading clickhandleClick span v-ifloading classvul-button__loading !-- 这里可以放一个加载图标组件暂时用文本 -- ⌛ /span span classvul-button__content slot / /span /button /template script setup langts import { withDefaults } from vue // 定义组件的 Props interface Props { type?: primary | success | warning | danger | info | default size?: large | default | small plain?: boolean round?: boolean circle?: boolean disabled?: boolean loading?: boolean } // 使用 withDefaults 提供默认值 const props withDefaults(definePropsProps(), { type: default, size: default, plain: false, round: false, circle: false, disabled: false, loading: false }) // 定义组件的事件 const emit defineEmits{ click: [event: MouseEvent] }() const handleClick (event: MouseEvent) { if (!props.disabled !props.loading) { emit(click, event) } } /script style scoped .vul-button { display: inline-flex; align-items: center; justify-content: center; line-height: 1; height: 32px; padding: 8px 16px; white-space: nowrap; cursor: pointer; border: 1px solid #dcdfe6; border-radius: 4px; background-color: #ffffff; color: #606266; font-size: 14px; transition: all 0.1s; outline: none; user-select: none; } .vul-button:hover { border-color: #c6e2ff; background-color: #ecf5ff; color: #409eff; } .vul-button--primary { background-color: #409eff; border-color: #409eff; color: #ffffff; } .vul-button--primary:hover { background-color: #66b1ff; border-color: #66b1ff; } .vul-button--small { height: 28px; padding: 6px 12px; font-size: 12px; } .vul-button--large { height: 36px; padding: 10px 20px; font-size: 16px; } .vul-button.is-round { border-radius: 20px; } .vul-button.is-circle { border-radius: 50%; width: 32px; padding: 8px; } .vul-button.is-circle.vul-button--small { width: 28px; } .vul-button.is-circle.vul-button--large { width: 36px; } .vul-button.is-disabled { cursor: not-allowed; opacity: 0.6; } .vul-button__loading { margin-right: 4px; animation: rotate 1s linear infinite; } keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } /style设计要点CSS 命名规范采用vul-(Vue UI Library) 作为前缀遵循 BEM 思想block__element--modifier避免样式污染。TypeScript 集成使用script setup langts和defineProps/defineEmits的泛型写法获得完整的类型推断。可访问性正确处理disabled和loading状态下的click事件和光标样式。3.2 组件导出与库入口创建组件的导出文件// packages/core/src/button/index.ts import Button from ./Button.vue import type { App } from vue // 为组件添加 install 方法使其可以被 Vue.use() 全局安装 Button.install (app: App) { app.component(Button.name || VulButton, Button) } export default Button export { Button }现在创建库的主入口文件负责导出所有组件// packages/core/src/index.ts import Button from ./button // 组件列表用于全局安装和按需引入 const components [Button] // 全局安装插件 const install (app: any) { components.forEach(component { app.component(component.name || component.displayName, component) }) } // 支持按需导入 export { Button, install } // 默认导出插件 export default { install, version: __VERSION__ // 构建时会替换 }3.3 配置核心包的 Vite 构建每个子包可以有独立的构建配置。在packages/core目录下创建vite.config.ts// packages/core/vite.config.ts import { defineConfig } from vite import vue from vitejs/plugin-vue import { resolve } from path import dts from vite-plugin-dts // https://vitejs.dev/config/ export default defineConfig({ plugins: [ vue(), dts({ outDir: dist, include: [src/**/*], staticImport: true, insertTypesEntry: true, }), ], build: { outDir: dist, lib: { entry: resolve(__dirname, src/index.ts), name: VueUILibrary, fileName: (format) vue-ui-library.${format}.js, }, rollupOptions: { // 确保外部化处理那些你不想打包进库的依赖 external: [vue], output: { // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量 globals: { vue: Vue, }, exports: named, }, }, sourcemap: true, }, resolve: { alias: { : resolve(__dirname, src), }, }, })关键配置解析build.lib: 指定库模式的入口和输出文件名。rollupOptions.external:至关重要。将vue外部化不打包进你的库这与你声明的peerDependencies一致。vite-plugin-dts: 自动生成.d.ts类型声明文件这是 TypeScript 项目引用你的库所必需的。在packages/core目录下运行pnpm build你将在dist目录下看到构建产物包括多种格式的 JS 文件和类型声明。4. 搭建开发环境与实时预览 (Playground)在packages里开发组件我们需要一个独立的应用来实时预览和调试。这就是play目录的作用。4.1 创建 Playground 应用cd play pnpm create vitelatest . -- --template vue-ts按照提示完成初始化后修改其vite.config.ts使其能正确解析本地包// play/vite.config.ts import { defineConfig } from vite import vue from vitejs/plugin-vue import { resolve } from path export default defineConfig({ plugins: [vue()], resolve: { alias: { vue-ui-library/core: resolve(__dirname, ../packages/core/src/index.ts), }, }, })这个别名配置让我们在 Playground 中可以直接导入正在开发的组件库源码。4.2 在 Playground 中使用组件修改play/src/App.vue!-- play/src/App.vue -- template div classplayground h1Vue UI Library Playground/h1 div classdemo-section h2Button 组件/h2 div classbutton-group VulButton clickhandleClick默认按钮/VulButton VulButton typeprimary主要按钮/VulButton VulButton typesuccess成功按钮/VulButton VulButton typewarning警告按钮/VulButton VulButton typedanger危险按钮/VulButton VulButton typeinfo信息按钮/VulButton /div div classbutton-group VulButton plain朴素按钮/VulButton VulButton typeprimary plain主要朴素/VulButton VulButton typesuccess round成功圆角/VulButton VulButton typewarning circle警/VulButton /div div classbutton-group VulButton sizelarge大型按钮/VulButton VulButton sizesmall小型按钮/VulButton VulButton loading加载中/VulButton VulButton disabled禁用按钮/VulButton /div /div /div /template script setup langts import { Button as VulButton } from vue-ui-library/core const handleClick (event: MouseEvent) { console.log(Button clicked!, event) } /script style scoped .playground { padding: 24px; } .demo-section { margin-bottom: 32px; } .button-group { margin-bottom: 16px; } .button-group * { margin-right: 12px; margin-bottom: 12px; } /style在play目录下运行pnpm dev打开浏览器你就能看到正在开发的 Button 组件并且可以实时修改packages/core/src/button/Button.vue来观察热更新效果。5. 实现按需加载与全量导入一个专业的组件库必须支持两种使用方式全量导入和按需导入。我们已经通过入口文件支持了全量导入。现在来实现按需导入这能极大优化生产环境的包体积。5.1 配置构建工具生成按需加载文件我们需要修改核心包的构建配置让每个组件都能被单独构建和引入。这通常需要一个插件但 Vite 的库模式本身支持多入口。我们可以创建一个脚本或者使用社区成熟的方案。这里我们展示一种基于 Vite 多入口配置的简化思路。首先安装一个辅助工具来生成组件入口映射# 在根目录 pnpm add -Dw fast-glob然后创建一个构建脚本scripts/build.mjs// scripts/build.mjs import { defineConfig, build } from vite import vue from vitejs/plugin-vue import { resolve } from path import { fileURLToPath } from url import { readdirSync, statSync, existsSync, mkdirSync, writeFileSync } from fs import { glob } from fast-glob const __dirname fileURLToPath(new URL(., import.meta.url)) const rootDir resolve(__dirname, ..) const componentsDir resolve(rootDir, packages/core/src) // 1. 自动查找所有组件目录 const getComponents () { const componentEntries {} const files glob.sync(*/index.ts, { cwd: componentsDir, absolute: false }) files.forEach(file { const componentName file.split(/)[0] // 入口格式button/index.ts - button componentEntries[componentName] resolve(componentsDir, file) }) return componentEntries } // 2. 为每个组件生成独立的构建配置并构建 const buildSingleComponent async (componentName, entryPath) { const outDir resolve(rootDir, packages/core/dist/${componentName}) const config defineConfig({ plugins: [vue()], build: { outDir, lib: { entry: entryPath, name: Vul${componentName.charAt(0).toUpperCase() componentName.slice(1)}, fileName: (format) index.${format}.js, formats: [es, umd] }, rollupOptions: { external: [vue], output: { globals: { vue: Vue }, exports: named } }, emptyOutDir: false, // 避免清空整个 dist } }) await build(config) console.log(✅ Built component: ${componentName}) } // 3. 主构建函数 const main async () { const components getComponents() console.log(Found components:, Object.keys(components)) // 并行构建所有组件 const buildPromises Object.entries(components).map(([name, entry]) buildSingleComponent(name, entry) ) await Promise.all(buildPromises) // 4. 生成一个用于按需引入的辅助文件 const componentNames Object.keys(components) const esmEntryContent componentNames.map(name export { default as ${name.charAt(0).toUpperCase() name.slice(1)} } from ./${name}/index.es.js ).join(\n) const cjsEntryContent componentNames.map(name module.exports.${name.charAt(0).toUpperCase() name.slice(1)} require(./${name}/index.umd.cjs).default ).join(\n) const distDir resolve(rootDir, packages/core/dist) writeFileSync(resolve(distDir, es.js), esmEntryContent) writeFileSync(resolve(distDir, lib.js), cjsEntryContent) console.log( All components built successfully!) } main().catch(console.error)这个脚本会遍历src下的每个组件目录为每个组件单独执行一次 Vite 构建并将产物输出到dist/componentName/下。最后生成es.js和lib.js作为按需导入的入口。5.2 修改 package.json 支持按需导入更新packages/core/package.json的exports字段{ exports: { .: { import: ./dist/vue-ui-library.mjs, require: ./dist/vue-ui-library.umd.cjs, types: ./dist/index.d.ts }, ./es: { import: ./dist/es.js, require: ./dist/es.js }, ./lib: { import: ./dist/lib.js, require: ./dist/lib.js }, ./*: ./* } }这样用户就可以通过以下方式按需引入了// ES Modules import { Button } from vue-ui-library/core/es // 或者 CommonJS const { Button } require(vue-ui-library/core/lib)在实际项目中用户通常会配合 unplugin-vue-components 这类自动导入插件实现真正的“无感”按需加载这需要在组件库侧提供对应的解析器。6. 集成自动化文档 (Vitepress)优秀的文档是组件库不可或缺的一部分。Vitepress 凭借其与 Vite 的深度集成和 Markdown 中心的设计成为 Vue 生态中文档站的首选。6.1 初始化文档项目在docs目录下初始化 Vitepresscd docs pnpm add -D vitepress vue # 初始化 npx vitepress init按照提示选择主题、是否启用搜索等。完成后修改docs/.vitepress/config.ts// docs/.vitepress/config.ts import { defineConfig } from vitepress import { resolve } from path export default defineConfig({ title: Vue UI Library, description: A Vue 3 UI Component Library., themeConfig: { nav: [ { text: 指南, link: /guide/ }, { text: 组件, link: /components/button } ], sidebar: { /guide/: [ { text: 介绍, items: [ { text: 快速开始, link: /guide/ }, { text: 安装, link: /guide/installation } ] } ], /components/: [ { text: 基础组件, items: [ { text: Button 按钮, link: /components/button } ] } ] } }, vite: { resolve: { alias: { vue-ui-library/core: resolve(__dirname, ../../packages/core/src/index.ts), } } } })6.2 编写组件文档页创建docs/components/button.md--- title: Button 按钮 --- # Button 按钮 常用的操作按钮。 ## 基础用法 使用 type、size、plain、round、circle 属性来定义按钮的样式。 demo src./demo/button-basic.vue / ::: details 查看代码 /components/demo/button-basic.vue ::: ## API ### Props | 属性名 | 说明 | 类型 | 可选值 | 默认值 | |--------|------|------|--------|--------| | type | 类型 | string | primary / success / warning / danger / info / default | default | | size | 尺寸 | string | large / default / small | default | | plain | 是否为朴素按钮 | boolean | — | false | | round | 是否为圆角按钮 | boolean | — | false | | circle | 是否为圆形按钮 | boolean | — | false | | disabled | 是否禁用 | boolean | — | false | | loading | 是否加载中 | boolean | — | false | ### Events | 事件名 | 说明 | 回调参数 | |--------|------|----------| | click | 点击按钮时触发 | event: MouseEvent |创建对应的演示组件docs/components/demo/button-basic.vue!-- docs/components/demo/button-basic.vue -- template div classdemo-button div stylemargin-bottom: 16px; VulButton默认按钮/VulButton VulButton typeprimary主要按钮/VulButton VulButton typesuccess成功按钮/VulButton VulButton typewarning警告按钮/VulButton VulButton typedanger危险按钮/VulButton /div div stylemargin-bottom: 16px; VulButton plain朴素按钮/VulButton VulButton typeprimary plain主要朴素/VulButton VulButton typesuccess round成功圆角/VulButton VulButton typewarning circle警/VulButton /div /div /template script setup import { Button as VulButton } from vue-ui-library/core /script在docs目录下运行pnpm docs:dev一个实时热更新的组件文档站就运行起来了。你可以看到组件的实时演示和详细的 API 文档。7. 添加单元测试 (Vitest)测试是保障组件库质量的生命线。我们使用 Vitest因为它与 Vite 配置共享速度极快。7.1 安装与配置在packages/core目录下安装 Vitest 和测试工具cd packages/core pnpm add -D vitest vue/test-utils vitest/ui happy-dom创建packages/core/vitest.config.ts// packages/core/vitest.config.ts import { defineConfig } from vitest/config import vue from vitejs/plugin-vue import { resolve } from path export default defineConfig({ plugins: [vue()], test: { environment: happy-dom, // 模拟浏览器环境 globals: true, // 启用全局 API如 describe, it, expect }, resolve: { alias: { : resolve(__dirname, src), }, }, })7.2 编写第一个组件测试创建packages/core/src/button/__tests__/Button.spec.ts// packages/core/src/button/__tests__/Button.spec.ts import { describe, it, expect } from vitest import { mount } from vue/test-utils import Button from ../Button.vue describe(Button.vue, () { it(renders default button, () { const wrapper mount(Button, { slots: { default: Click me } }) expect(wrapper.text()).toBe(Click me) expect(wrapper.classes()).toContain(vul-button) expect(wrapper.classes()).toContain(vul-button--default) }) it(emits click event when clicked, async () { const wrapper mount(Button) await wrapper.trigger(click) expect(wrapper.emitted()).toHaveProperty(click) }) it(does not emit click event when disabled, async () { const wrapper mount(Button, { props: { disabled: true } }) await wrapper.trigger(click) expect(wrapper.emitted()).not.toHaveProperty(click) }) it(applies correct type class, () { const wrapper mount(Button, { props: { type: primary } }) expect(wrapper.classes()).toContain(vul-button--primary) }) it(shows loading state, () { const wrapper mount(Button, { props: { loading: true } }) expect(wrapper.classes()).toContain(is-loading) expect(wrapper.find(.vul-button__loading).exists()).toBe(true) }) })在packages/core/package.json中添加测试脚本{ scripts: { test: vitest, test:ui: vitest --ui } }运行pnpm test执行测试或pnpm test:ui打开图形化界面查看测试结果和覆盖率。通过编写全面的测试用例可以确保组件在各种状态和交互下的行为符合预期。8. 常见问题与排查思路在搭建和使用组件库的过程中你一定会遇到各种问题。以下是一些典型问题及其解决方案问题现象可能原因排查方式解决方案构建失败提示Vue未找到1.peerDependencies未正确声明。2.rollupOptions.external未配置或配置错误。1. 检查package.json的peerDependencies。2. 检查vite.config.ts中external数组是否包含vue。1. 确保peerDependencies包含vue: ^3.4.0。2. 确保构建配置中external: [vue]。在 Playground 中组件样式丢失1. 组件样式文件未导入。2. Vite 别名配置错误导致源码路径解析失败。1. 检查组件是否在src/index.ts中正确导出。2. 检查 Playground 的vite.config.ts别名路径是否正确指向源码。1. 确保组件样式通过style scoped或单独导入。2. 确认别名路径resolve(__dirname, ../packages/core/src/index.ts)存在。TypeScript 报错找不到模块声明1. 未生成.d.ts类型声明文件。2.package.json中types字段指向错误。1. 运行构建后检查dist目录下是否有.d.ts文件。2. 检查package.json的types字段是否为./dist/index.d.ts。1. 确保vite-plugin-dts插件正确配置并运行。2. 确保主入口文件src/index.ts导出了所有类型。按需引入时 Tree Shaking 不生效1. 组件库未配置sideEffects。2. 用户项目构建工具配置问题。1. 检查库的package.json是否设置了sideEffects: false。2. 确认组件是 ES Module 格式导出。1. 在package.json中添加sideEffects: false。2. 确保构建产物包含 ES Module 格式 (format: es)。文档站中组件无法渲染1. Vitepress 未正确配置 Vue 插件。2. 文档中导入路径错误。1. 检查.vitepress/theme/index.ts是否注册了组件库。2. 检查文档 Markdown 中导入语句的路径。1. 在 Vitepress 主题入口文件中使用app.use(YourLib)。2. 使用 Vitepress 的vite配置项设置别名确保路径正确。发布到 npm 后安装报错1. 依赖未正确声明 (dependenciesvspeerDependencies)。2. 文件未包含在files字段中。1. 检查package.json的dependencies、devDependencies、peerDependencies。2. 运行npm pack查看将要发布的文件列表。1. 将vue等宿主环境依赖放入peerDependencies。2. 确保files字段包含了所有需要发布的文件如dist,README.md。9. 最佳实践与工程建议走到这一步一个组件库的雏形已经完成。但要将其用于生产或团队协作还需要遵循一些最佳实践版本管理与发布自动化使用changesets或standard-version管理版本号和生成 CHANGELOG。在 CI/CD 中自动化执行测试、构建和发布到 npm 的流程。遵循语义化版本控制 (SemVer)。样式系统设计CSS 变量使用 CSS 自定义属性定义主题色、间距、字体等便于实现动态主题和暗黑模式。设计令牌将颜色、间距、阴影等视觉元素抽象为设计令牌并在所有组件中统一引用。样式隔离坚持使用scopedCSS 或 CSS Modules避免全局样式污染。组件设计原则单一职责一个组件只做一件事。受控 vs 非受控明确组件的状态管理方式优先设计为“受控组件”。复合组件对于复杂组件如 Select、Table使用provide/inject或 Composition API 实现子组件间的通信。无障碍访问为交互组件添加必要的 ARIA 属性确保键盘导航和屏幕阅读器支持。代码质量与规范集成 ESLint Prettier 统一代码风格。使用 Husky lint-staged 在提交前自动检查和修复代码。为所有公共 API 编写详细的 JSDoc/TSDoc 注释。性能优化虚拟滚动为长列表组件如 Table、Select实现虚拟滚动。懒加载对于非首屏必需的组件或图标支持动态导入。构建优化利用 Vite/Rollup 的代码分割、Tree Shaking 能力。从零开始搭建一个 UI 组件库远不止是写几个.vue文件。它是一套完整的工程化体系涉及项目架构、构建打包、类型系统、文档化、测试和发布流程。通过本次实践你不仅学会了如何创建一个 Button 组件更重要的是掌握了构建一个可维护、可扩展的前端库的完整方法论。下一步你可以尝试添加更多基础组件Input、Select、Modal 等。实现一个完整的主题切换系统。集成图标库如 Iconify。编写 E2E 测试使用 Cypress 或 Playwright。搭建完整的 CI/CD 流水线实现自动化测试、构建和发布。这套架构和工具链是当前 Vue 3 生态下的主流选择掌握了它你就能从容应对任何定制化组件开发的需求也能更深入地理解社区中那些优秀开源库的设计精髓。建议你将这个项目作为模板收藏在未来的开发中不断迭代和丰富。
返回列表