目录
[二、npm 包形式封装共享组件](#二、npm 包形式封装共享组件)
[1、 组件文件 src/components/MyButton.vue](#1、 组件文件 src/components/MyButton.vue)
[2、入口文件 src/index.js](#2、入口文件 src/index.js)
[3、Vite 打包配置 vite.config.js](#3、Vite 打包配置 vite.config.js)
[4. package.json 配置](#4. package.json 配置)
[三、Monorepo 架构管理多个子项目](#三、Monorepo 架构管理多个子项目)
[四、软链接 / symlink 方式本地测试复用模块](#四、软链接 / symlink 方式本地测试复用模块)
[1. 共享模块配置(shared-module)](#1. 共享模块配置(shared-module))
[2. 注册共享模块](#2. 注册共享模块)
[3. 消费项目引用(my-app)](#3. 消费项目引用(my-app))
[五、Git Submodule 管理公共资源库](#五、Git Submodule 管理公共资源库)
[1. 创建公共资源库(子模块)](#1. 创建公共资源库(子模块))
[2. 主项目添加子模块](#2. 主项目添加子模块)
[3. 提交主项目变更](#3. 提交主项目变更)
[1. 克隆含子模块的项目](#1. 克隆含子模块的项目)
[2. 业务代码中引用](#2. 业务代码中引用)
[3. 更新子模块代码](#3. 更新子模块代码)
[1. 创建共享组件库(独立 Git 仓库)](#1. 创建共享组件库(独立 Git 仓库))
[2. 创建组件入口文件](#2. 创建组件入口文件)
[3. 配置自动生成脚本](#3. 配置自动生成脚本)
[4. 主项目引用组件库](#4. 主项目引用组件库)
[5. 安装依赖(主项目)](#5. 安装依赖(主项目))
随着前端项目越来越大,功能越来越多,那么项目开的效率,代码的一致性,维护的成本,可复用性等问题都会不断冒出来,为解决这些问题项目可以使用多项目间的组件共用方案,
如:cdn/服务器 ,npm 包形式封装共享组件 ,Monorepo 架构管理多个子项目 ,软链接 / symlink 方式本地测试复用模块, ,Git Submodule 管理公共资源库 ,使用git地址依赖
一、cdn/服务器
使用CDN引入组件方案在优化加载速度方面确实优势显著,但也存在更新维护的挑战。
| 问题场景 | 传统方案缺陷 | 优化方案 | 效果提升 |
|---|---|---|---|
| 组件频繁更新 | 需手动刷新CDN/替换服务器文件 | 文件名哈希版本化 lib-v1.2.3.min.js | 用户自动获取新版本 |
| 缓存更新延迟 | 用户可能访问旧版本组件 | 动态加载 + Cache-Control: max-age=300 | 5分钟强制更新 |
| 多版本共存冲突 | 全局替换导致兼容性问题 | 灰度发布 + A/B测试 | 故障率降低 90% |
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue CDN组件共用示例</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<!--CDN 引入共享组件库 -->
<script src="https://cdn.jsdelivr.net/npm/element-plus@2.4.4/dist/index.full.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-plus@2.4.4/dist/index.css">
<style>
body {
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', Arial, sans-serif;
margin: 0;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.95);
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
padding: 30px;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 {
color: #333;
margin-bottom: 10px;
}
.component-demo {
margin: 20px 0;
padding: 20px;
border-radius: 10px;
background: #f8f9fa;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.demo-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 15px;
color: #444;
}
.button-group {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.notification-btn {
margin-right: 10px;
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<div class="header">
<h1>Vue多项目CDN组件共用方案</h1>
<p>通过CDN引入Element Plus组件库实现在多个项目中的组件复用</p>
</div>
<!-- 按钮组件演示 -->
<div class="component-demo">
<div class="demo-title">按钮组件演示</div>
<div class="button-group">
<el-button type="primary" @click="handleClick('Primary Button')">主要按钮</el-button>
<el-button type="success" @click="handleClick('Success Button')">成功按钮</el-button>
<el-button type="warning" @click="handleClick('Warning Button')">警告按钮</el-button>
<el-button type="danger" @click="handleClick('Danger Button')">危险按钮</el-button>
<el-button type="info" @click="handleClick('Info Button')">信息按钮</el-button>
</div>
</div>
<!-- 输入框组件演示 -->
<div class="component-demo">
<div class="demo-title">表单组件演示</div>
<el-form :model="form" label-width="120px">
<el-form-item label="用户名">
<el-input v-model="form.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item label="密码">
<el-input v-model="form.password" type="password" placeholder="请输入密码"></el-input>
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model="form.email" placeholder="请输入邮箱地址"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 数据表格演示 -->
<div class="component-demo">
<div class="demo-title">数据表格演示</div>
<el-table :data="tableData" style="width: 100%" stripe>
<el-table-column prop="id" label="ID" width="80"></el-table-column>
<el-table-column prop="name" label="姓名" width="180"></el-table-column>
<el-table-column prop="email" label="邮箱"></el-table-column>
<el-table-column prop="role" label="角色" width="120"></el-table-column>
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button size="small" @click="editUser(scope.row)">编辑</el-button>
<el-button size="small" type="danger" @click="deleteUser(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 通知提醒演示 -->
<div class="component-demo">
<div class="demo-title">通知提醒演示</div>
<div>
<el-button class="notification-btn" @click="showSuccess">成功消息</el-button>
<el-button class="notification-btn" type="warning" @click="showWarning">警告消息</el-button>
<el-button class="notification-btn" type="danger" @click="showError">错误消息</el-button>
<el-button class="notification-btn" type="info" @click="showInfo">信息消息</el-button>
</div>
</div>
<!-- 进度条演示 -->
<div class="component-demo">
<div class="demo-title">进度条演示</div>
<el-progress :percentage="progressPercentage" :stroke-width="15"></el-progress>
<div style="margin-top: 10px;">
<el-button @click="increaseProgress">增加进度</el-button>
<el-button @click="decreaseProgress">减少进度</el-button>
<el-button @click="resetProgress">重置进度</el-button>
</div>
</div>
</div>
</div>
<script>
const { createApp } = Vue;
const { ElMessage, ElNotification } = ElementPlus;
createApp({
data() {
return {
form: {
username: '',
password: '',
email: ''
},
tableData: [
{ id: 1, name: '张三', email: 'zhangsan@example.com', role: '管理员' },
{ id: 2, name: '李四', email: 'lisi@example.com', role: '用户' },
{ id: 3, name: '王五', email: 'wangwu@example.com', role: '用户' },
{ id: 4, name: '赵六', email: 'zhaoliu@example.com', role: '访客' }
],
progressPercentage: 30
};
},
methods: {
handleClick(buttonType) {
ElMessage.success(`点击了${buttonType}`);
},
submitForm() {
if (!this.form.username || !this.form.password) {
ElMessage.error('请填写必填项');
return;
}
ElMessage.success('表单提交成功!');
console.log('提交的数据:', this.form);
},
resetForm() {
this.form = { username: '', password: '', email: '' };
ElMessage.info('表单已重置');
},
editUser(user) {
ElMessage.warning(`编辑用户: ${user.name}`);
},
deleteUser(user) {
ElMessage.error(`删除用户: ${user.name}`);
},
showSuccess() {
ElNotification({
title: '成功',
message: '这是一条成功的消息',
type: 'success'
});
},
showWarning() {
ElNotification({
title: '警告',
message: '这是一条警告的消息',
type: 'warning'
});
},
showError() {
ElNotification({
title: '错误',
message: '这是一条错误的消息',
type: 'error'
});
},
showInfo() {
ElNotification({
title: '信息',
message: '这是一条信息的消息',
type: 'info'
});
},
increaseProgress() {
if (this.progressPercentage < 100) {
this.progressPercentage += 10;
}
},
decreaseProgress() {
if (this.progressPercentage > 0) {
this.progressPercentage -= 10;
}
},
resetProgress() {
this.progressPercentage = 0;
}
},
mounted() {
// 页面加载完成后显示欢迎消息
ElNotification({
title: '欢迎',
message: 'Vue CDN组件共用方案演示页面已加载完成',
type: 'success'
});
}
}).use(ElementPlus).mount('#app');
</script>
</body>
</html>
二、npm 包形式封装共享组件
将通用组件打包发布为私有或公共 npm 包,在各项目中安装并按需导入使用。适用于大型团队协作或多产品线场景。
实现:创建Vue项目并开发组件 --> 编写插件的install方法(用于全局注册组件)-->配置打包命令(使用Vue CLI或Vite进行库模式打包)-->配置package.json(设置入口文件、版本、依赖等)-->发布到npm
1、 组件文件 src/components/MyButton.vue
<!-- 组件文件 src/components/MyButton.vue -->
<template>
<button class="my-button" @click="$emit('click')">
<slot></slot>
</button>
</template>
<style scoped>
.my-button {
padding: 8px 16px;
background: #42b883;
color: white;
border: none;
border-radius: 4px;
}
</style>
2、入口文件 src/index.js
import MyButton from './components/MyButton.vue'
// 全局注册组件
const install = (app) => {
app.component('MyButton', MyButton)
}
// 支持按需引入
export { MyButton}
export default install
3、Vite 打包配置 vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
lib: {
entry: 'src/index.js', // 入口文件
name: 'MyComponentLib', // 全局变量名
fileName: 'my-component-lib' // 输出文件名
},
rollupOptions: {
external: ['vue'], // 排除 Vue 依赖
output: {
globals: {
vue: 'Vue' // 全局 Vue 变量名
}
}
}
}
})
4. package.json 配置
{
"name": "my-component-lib",
"version": "1.0.0",
"main": "dist/my-component-lib.umd.cjs", // CommonJS 入口
"module": "dist/my-component-lib.js", // ES Module 入口
"files": ["dist"], // 发布目录
"peerDependencies": { // 宿主环境依赖
"vue": ">=3.0.0"
},
"scripts": {
"build": "vite build" // 打包命令
}
}
5、发布/使用组件
## 1.构建组件库
npm run build # 生成 dist 目录
## 2.登录 npm 账户
npm login # 输入账号/密码/邮箱
## 3.发布到 npm
npm publish # 自动上传 dist 内容
# 使用组件库
## 安装依赖
npm install my-component-lib
## 全局注册(main.js)
import { createApp } from 'vue'
import MyComponentLib from 'my-component-lib'
createApp(App)
.use(MyComponentLib) // 注册所有组件
.mount('#app')
## 按需引入(单文件组件)
<script setup>
import { MyButton } from 'my-component-lib'
</script>
<template>
<MyButton>点击我</MyButton>
</template>
## 项目结构:
my-component-lib/
├── src/
│ ├── components/
│ │ ├── MyButton.vue # 组件代码
│ └── index.js # 组件导出入口
├── package.json # 包配置
└── vite.config.js # 打包配置
关键注意事项
-
版本管理
- 每次更新需修改
package.json中的version字段 - 遵循语义化版本规范(
major.minor.patch)
- 每次更新需修改
-
依赖声明
- 生产依赖 →
dependencies - 开发依赖 →
devDependencies - 宿主环境依赖 → **
peerDependencies**(避免重复安装)
- 生产依赖 →
-
CDN 引入支持
通过
unpkg自动提供 CDN 访问:<script src="https://unpkg.com/my-component-lib@1.0.0/dist/my-component-lib.umd.cjs"></script>
三、Monorepo 架构管理多个子项目
Monorepo 是一种管理项目代码的方式,它指的是在一个大的仓库(repo)中管理多个模块或包(package)。这样可以方便地统一管理依赖、版本、配置等,也可以提高代码的复用性和协作效率。
四、软链接 / symlink 方式本地测试复用模块
通过 npm link 命令创建符号链接,使本地模块无需发布即可被其他项目引用:
- 模块注册 :在共享模块目录执行
npm link,创建全局软链接 - 项目引用 :在消费项目执行
npm link <package-name>,链接到全局模块 - 实时同步:修改共享模块代码 → 所有链接项目立即生效
项目结构
projects/
├── shared-module/ # 共享模块
│ ├── src/
│ │ └── Button.vue
│ ├── package.json
│ └── vite.config.js
└── my-app/ # 消费项目
├── src/
│ └── App.vue
└── package.json
1. 共享模块配置(shared-module)
// package.json
{
"name": "shared-module",
"version": "1.0.0",
"main": "dist/shared-module.umd.js",
"peerDependencies": {
"vue": ".3.0"
}
}
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
lib: {
entry: 'src/Button.vue',
name: 'SharedModule'
}
}
})
<!-- src/Button.vue -->
<template>
<button class="shared-btn">
<slot />
</button>
</template>
<style scoped>
.shared-btn {
padding: 12px 24px;
background: #42b883;
color: white;
border: none;
border-radius: 8px;
}
</style>
2. 注册共享模块
cd shared-module
npm link # 创建全局软链接
3. 消费项目引用(my-app)
cd my-app
npm link shared-module # 链接到全局模块
<!-- src/App.vue -->
<script setup>
import SharedButton from 'shared-module'
</script>
<template>
<SharedButton>点击我</SharedButton>
</template>
4、实时开发流程
-
启动共享模块监听(shared-module 目录)
vite build --watch # 开启构建监听
-
启动消费项目(my-app 目录)
npm run dev
修改
Button.vue代码 → 自动触发重建 → my-app 实时更新组件
方案优势与注意点
# 优势
# 1. **零发布延迟**:修改代码立即生效,无需 `npm publish`
# 2. **跨项目复用**:同一模块可被多个项目同时链接使用
# 3. **磁盘空间优化**:软链接不复制文件,节省存储空间
# 关键注意
# 路径一致性:
# 检查软链接路径
ls -l node_modules/shared-module
# 输出:shared-module -> ../../.nvm/versions/node/v20.12.0/lib/node_modules/shared-module
# 版本管理
# 消费项目 package.json
"dependencies": {
"shared-module": "file:../shared-module" // 防止意外安装
}
#清除链接:
# 解除项目链接
npm unlink shared-module
# 注销全局模块
npm unlink -g shared-module
适用场景
- 跨项目组件调试:多个前端项目共用同一组件库
- 微前端基座开发:主应用实时调试子应用模块
- 本地SDK测试:未发布的工具库在业务项目中验证
此方案通过操作系统级符号链接实现高效模块复用,相比 Monorepo 更轻量灵活,但需注意路径解析和版本管理问题
五、Git Submodule 管理公共资源库
Git Submodule允许将一个Git仓库作为另一个Git仓库的子目录嵌入,同时保持独立的版本控制,适合管理公共库或共享资源。
核心实现原理
graph LR
A[主项目] --> B[.gitmodules]
B --> C[子模块路径]
C --> D[公共资源库]
通过 .gitmodules 文件建立主项目与子模块的映射关系,实现:
- 独立版本控制:子模块保持独立仓库的提交历史
- 引用特定提交:主项目锁定子模块的特定版本
- 跨项目复用:多个项目可引用同一公共库
完整实现步骤
1. 创建公共资源库(子模块)
# 创建公共库
mkdir shared-lib && cd shared-lib
git init
echo "# 公共工具库" > README.md
git add . && git commit -m "初始化公共库"
2. 主项目添加子模块
# 创建主项目
mkdir main-project && cd main-project
git init
# 添加子模块 (关键步骤)
git submodule add /path/to/shared-lib src/shared
此时生成 .gitmodules 文件:
[submodule "src/shared"]
path = src/shared
url = /path/to/shared-lib
3. 提交主项目变更
git add .gitmodules src/shared
git commit -m "添加shared-lib子模块"
使用示例
1. 克隆含子模块的项目
git clone /path/to/main-project
cd main-project
git submodule update --init # 初始化子模块
2. 业务代码中引用
// main-project/src/app.js
import { utils } from './shared/utils.js';
console.log(utils.formatDate(new Date()));
3. 更新子模块代码
# 进入子模块目录修改
cd src/shared
echo "export const formatDate = (date) => date.toISOString()" > utils.js
git add . && git commit -m "新增日期格式化工具"
# 返回主项目更新引用
cd ..
git add src/shared
git commit -m "更新共享库到最新版本"
关键操作命令
| 场景 | 命令 | 作用 |
|---|---|---|
| 添加子模块 | git submodule add <repo> [path] |
添加公共库到指定路径 |
| 初始化子模块 | git submodule update --init [--recursive] |
首次克隆后初始化子模块 |
| 更新子模块到最新版本 | git submodule update --remote |
拉取子模块最新提交 |
| 查看子模块状态 | git submodule status |
显示子模块提交ID和路径 |
| 批量操作子模块 | git submodule foreach 'git pull' |
所有子模块执行git pull |
典型工作流程
sequenceDiagram
participant 主项目
participant 子模块
主项目->>子模块: 引用特定提交(SHA1)
子模块->>主项目: 提供代码副本
loop 开发过程
子模块->>子模块: 独立开发提交
主项目->>主项目: 更新子模块引用
end
注意事项
-
版本锁定机制
主项目记录子模块的 具体提交ID(非分支名),确保版本一致性
# 查看锁定的提交ID git ls-tree HEAD src/shared # 输出:160000 commit a1b2c3d... src/shared -
分支管理技巧
在子模块中切换分支:
cd src/shared git checkout dev-branch cd .. git add src/shared git commit -m "切换子模块到dev分支" -
删除子模块
git submodule deinit src/shared # 解除注册 git rm src/shared # 删除目录 rm -rf .git/modules/src/shared # 清除缓存
完整示例参考:Git Submodule Demo
六、使用git地址依赖
将共享组件库作为一个独立的git仓库,然后在主项目的package.json中直接引用该git地址作为依赖
此方案类似于Git Submodule,但它是通过npm管理git依赖,而不是直接使用git submodule命令。
创建共享组件库(假设已经存在一个git仓库,这里以GitHub为例)
- 共享组件库的package.json中需要设置正确的入口文件(比如"main":"dist/shared.umd.min.js")
- 共享组件库需要打包成可被其他项目引用的库(例如使用vue-cli的build --target lib)
在主项目的package.json中添加依赖,指向共享组件库的git地址
格式:"依赖名": "git+https://github.com/用户名/仓库名.git" 或者指定分支git+https://.../仓库名.git#分支名
实现原理
graph LR
A[主项目] --> B[package.json]
B --> C{{Git 依赖声明}}
C --> D[共享组件库]
D --> E[独立仓库]
完整实现步骤
1. 创建共享组件库(独立 Git 仓库)
# 创建组件库项目
vue create shared-components
cd shared-components
# 配置打包脚本(package.json)
{
"name": "shared-components",
"version": "1.0.0",
"scripts": {
"lib": "vue-cli-service build --target lib --name compoment-demo src/index.js",
"push": "node script.js && npm run lib && git add . && git commit -m 'update' && git push"
},
"main": "dist/compoment-demo.umd.min.js"
}
2. 创建组件入口文件
// src/index.js
import Button from './components/Button.vue'
import Input from './components/Input.vue'
export { Button, Input }
3. 配置自动生成脚本
// script.js
const fs = require('fs')
// 自动扫描components目录生成入口文件
const components = fs.readdirSync('./src/components')
.filter(file => file.endsWith('.vue'))
.map(file => `export { default as ${file.split('.')[0]} } from './components/${file}'`)
fs.writeFileSync('./src/index.js', components.join('\n'))
4. 主项目引用组件库
// 主项目 package.json
{
"dependencies": {
"shared-components": "git+https://github.com/yourname/shared-components.git"
}
}
5. 安装依赖(主项目)
# 标准安装
npm install git+https://github.com/yourname/shared-components.git
# 若安装失败,使用替代方案
git config --global url."https://hub.fastgit.xyz/".insteadOf "ssh://git@github.com/"
npm install
组件使用
<template>
<div>
<SharedButton>主要按钮</SharedButton>
<SharedInput v-model="text" placeholder="输入内容" />
</div>
</template>
<script>
import { Button as SharedButton, Input as SharedInput } from 'shared-components'
export default {
components: { SharedButton, SharedInput },
data() {
return { text: '' }
}
}
</script>
更新流程
# 修改组件后自动打包推送
npm run push
# 更新组件库版本
npm update shared-components
# 或指定特定版本
npm install shared-components@1.2.0
方案优势对比
| 特性 | Git 依赖方案 | Git Submodule |
|---|---|---|
| 依赖管理 | npm 自动管理版本 | 需手动更新子模块引用 |
| 构建流程 | 组件库预构建,主项目直接使用 | 需主项目参与构建 |
| 跨项目一致性 | 版本锁定保证一致性 | 依赖特定提交ID |
| 安装复杂度 | npm install 一键完成 | 需额外 git submodule init |