如何从零搭建一个 Vue 3 组件库?

来源:Vuejs教程作者:马来西亚程序员头衔:程序员
导读:本期聚焦于马来西亚程序员创作的《如何从零搭建一个 Vue 3 组件库?》,敬请观看详情。如果团队同时维护多个 Vue 3 项目,是否遇到过同一个按钮组件在 A 项目改完,B 项目却忘了同步的情况?把通用 UI 抽成独立组件库能解决这个问题,但从零搭建并不是简单拷贝文件。本文介绍一套基于 Vite 库模式与 pnpm 工作区的最小可用 Vue 3 组件库工程,重点说明组件打包、样式分离、按需加载和类型声明生成。接着演示用 VitePress 生成组件文档,再走完 npm 发布流程。读者可以避开 peerDependencies 设置不当、组件样式丢失等常见坑,逐步沉淀自己的设计系统。文中会给出完整目录结构和关键配置代码,帮助第一次搭建组件库的开发者快速上手。

当项目里出现多个重复的按钮、弹窗和表单组件时,把它们沉淀为独立的 Vue 3 组件库是降低维护成本的有效手段。但组件库开发与业务页面开发差异很大,不仅要考虑组件的 API 设计,还要处理构建产物、类型文件、样式导出和文档演示。本文会从初始化工程开始,逐步搭建一个可发布的 Vue 3 组件库,涵盖 Vite 库模式配置、组件开发、类型生成和文档发布。

如何从零搭建一个 Vue 3 组件库?

一、初始化 monorepo 工程与目录结构

组件库通常需要同时维护组件源码、文档站点和示例项目,采用 monorepo 结构能让这些模块共享依赖、统一管理版本。pnpm 的 workspace 协议非常适合这种场景,它通过软链接复用 node_modules,节省磁盘空间,也避免了 npm 或 yarn 常见的依赖提升问题。下面先创建根目录并初始化 package.json。

mkdir vue3-component-lib
cd vue3-component-lib
pnpm init
mkdir packages
mkdir packages/components
mkdir packages/docs

在根目录创建 pnpm-workspace.yaml,声明包含哪些子包。然后在根 package.json 中添加脚本和公共开发依赖。配置如下:

packages:
  - "packages/*"
{
  "name": "vue3-component-lib",
  "private": true,
  "scripts": {
    "dev": "pnpm --filter docs dev",
    "build": "pnpm --filter components build",
    "docs:build": "pnpm --filter docs build"
  },
  "devDependencies": {
    "typescript": "^5.4.0",
    "vite": "^5.2.0",
    "vue": "^3.4.0"
  }
}

其中 components 子包存放组件源码和构建配置,docs 子包用于 VitePress 文档。每个子包也需要自己的 package.json,在 components 包中声明组件库的名称、版本和入口文件。这种隔离方式可以让组件库独立发布,文档站点只做为开发辅助。

二、配置 Vite 库模式打包组件

Vite 从 2.x 开始内置了库模式,通过 build.lib 选项可以快速生成 ES 和 CommonJS 两种格式的产物。对于 Vue 3 组件库,需要将 Vue 及其相关依赖外部化,避免重复打包导致运行时冲突。同时要处理组件样式的导出,否则用户使用时会出现样式丢失。

在 packages/components 下创建 vite.config.ts,基本配置如下:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import dts from 'vite-plugin-dts'

export default defineConfig({
  plugins: [
    vue(),
    dts({ insertTypesEntry: true })
  ],
  build: {
    lib: {
      entry: 'src/index.ts',
      name: 'Vue3ComponentLib',
      fileName: (format) => `vue3-component-lib.${format}.js`,
      formats: ['es', 'cjs']
    },
    rollupOptions: {
      external: ['vue'],
      output: {
        globals: {
          vue: 'Vue'
        },
        assetFileNames: (assetInfo) => {
          if (assetInfo.name === 'style.css') return 'index.css'
          return assetInfo.name
        }
      }
    },
    cssCodeSplit: false
  }
})

cssCodeSplit 设置为 false 会将所有 CSS 合并到一个文件,并命名为 index.css,这能简化样式的导入。dts 插件用于生成 .d.ts 类型声明,并通过 insertTypesEntry 自动在 package.json 中插入 types 字段。接着配置 components 包的 package.json,明确入口和导出映射。

{
  "name": "vue3-component-lib",
  "version": "1.0.0",
  "main": "./dist/vue3-component-lib.cjs.js",
  "module": "./dist/vue3-component-lib.es.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/vue3-component-lib.es.js",
      "require": "./dist/vue3-component-lib.cjs.js"
    },
    "./style.css": "./dist/index.css"
  },
  "files": ["dist"],
  "peerDependencies": {
    "vue": "^3.4.0"
  },
  "sideEffects": ["**/*.css"]
}

exports 字段让现代打包工具能正确解析类型、ES 和 CJS 产物,./style.css 子路径方便用户按需引入样式。sideEffects 标记 CSS 文件具有副作用,防止 tree shaking 把样式摇掉。

三、开发第一个组件并生成类型声明

以一个最常用的 Button 组件为例,说明组件库中的组件应该怎样编写。组件库组件与业务组件最大的区别在于 API 的稳定性和透传支持,因此需要仔细定义 props、emits 和插槽。下面使用 script setup 语法实现一个简洁的按钮。

<template>
  <button
    class="v-button"
    :class="[`v-button--${type}`, { 'is-disabled': disabled }]"
    :disabled="disabled"
    @click="handleClick"
  >
    <slot></slot>
  </button>
</template>

<script setup lang="ts">
import { defineProps, defineEmits } from 'vue'

type ButtonType = 'default' | 'primary' | 'danger'

interface Props {
  type?: ButtonType
  disabled?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  type: 'default',
  disabled: false
})

const emit = defineEmits<{
  (e: 'click', event: MouseEvent): void
}>()

function handleClick(event: MouseEvent) {
  if (!props.disabled) {
    emit('click', event)
  }
}
</script>

<style scoped>
.v-button {
  padding: 8px 16px;
  border: 1px solid #ccc;
  border-radius: 4px;
  cursor: pointer;
}
.v-button--primary {
  background-color: #409eff;
  color: white;
  border-color: #409eff;
}
.is-disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
</style>

这个 Button 组件使用了 withDefaults 为 props 提供默认值,并在点击时判断 disabled 状态,避免禁用时触发事件。scoped 样式只会作用于当前组件,打包后这些样式会通过 Vite 的 CSS 处理机制提取到独立的 index.css 中。

然后需要创建组件库的入口文件 src/index.ts,统一导出所有组件和类型声明。这有利于用户按需引入或全量引入。

import Button from './components/Button.vue'
import type { App } from 'vue'

export { Button }

export default {
  install(app: App) {
    app.component(Button.name as string, Button)
  }
}

入口文件同时支持具名导出和默认导出,默认导出实现了 Vue 插件的 install 方法,这样用户可以通过 app.use(ComponentLib) 全局注册。为了生成完整的类型声明,项目中安装了 vite-plugin-dts,它会在构建时分析源码并输出 .d.ts 文件。生成的文件位于 dist 目录下,package.json 中的 types 字段会指向 dist/index.d.ts。

四、用 VitePress 生成文档并发布到 npm

文档是组件库能否被团队采用的关键。VitePress 基于 Vite 构建,天然支持 Vue 3 组件,可以直接在 Markdown 文件中演示组件。先在 packages/docs 目录初始化 VitePress,并创建文档页面。

docs 目录下创建 .vitepress/config.js 配置导航和侧边栏:

import { defineConfig } from 'vitepress'

export default defineConfig({
  title: 'Vue3 Component Lib',
  description: 'A minimal Vue 3 component library',
  themeConfig: {
    nav: [
      { text: '指南', link: '/guide/' },
      { text: '组件', link: '/components/' }
    ],
    sidebar: {
      '/guide/': [
        { text: '快速开始', link: '/guide/getting-started' }
      ],
      '/components/': [
        { text: 'Button 按钮', link: '/components/button' }
      ]
    }
  }
})

然后在 Markdown 文件中使用组件,需要先导入组件库。给出一个示例 markdown 文件内容:

<script setup>
import { Button } from 'vue3-component-lib'
import 'vue3-component-lib/style.css'
</script>

# Button 按钮

<Button type="primary">主要按钮</Button>

在 VitePress 的 Markdown 中可以直接使用 Vue 组件,但需要引入组件库的样式文件。文档站点的构建配置中通过别名将组件库指向源码,方便调试。之后可以运行 pnpm --filter docs dev 启动本地文档预览。

最后是发布到 npm。在发布前需要确认 components 包的 package.json 中已经声明了 peerDependencies 和 files 字段,确保只发布 dist 目录。执行 npm login 后,在 components 目录运行 pnpm build 和 npm publish 即可。如果包名带有作用域,需要添加 --access public 参数。

常见坑点有两个:一是样式丢失,多数原因是 sideEffects 字段未配置或用户没有手动导入 style.css,可以在文档中明确告知导入方式,或者提供自动按需引入的插件。二是 Vue 版本冲突,如果组件库将 Vue 打包进产物,会导致多个 Vue 实例共存,所以必须在 rollupOptions.external 中外部化 vue,同时在 peerDependencies 中声明正确的版本范围。

通过以上步骤,一个基础但完整的 Vue 3 组件库就搭建完成了。后续可以继续扩展组件、完善测试、加入 CI/CD 自动发布,但核心的构建链路已经清晰。

Vue 3组件库前端工程化修改时间:2026-10-06 01:28:28

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/1006/66233.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。