pnpm+monorepo实现前端公共函数、组件库

发布于:2025-03-05 ⋅ 阅读:(16) ⋅ 点赞:(0)

一、前言

1. pnpm

pnpm 是前端包管理工具之一,常见的还有npm、yarn等,但pnpm由于安装速度快、磁盘占用低、内置支持monorepo等优势,所以更推荐使用。

1-1 包管理工具核心特点对比

特性 pnpm npm yarn
安装速度 🚀 快(基于硬链接和缓存) ⏳ 较慢(全量复制) 🚀 快(全量复制+缓存优化)
磁盘占用 📉 低(共享 node_modules 依赖) 📈 高(每个项目完整安装) 📈 高(和 npm 类似)
包管理机制 硬链接+全局存储 直接安装到 node_modules 直接安装到 node_modules
严格性 🔒 严格模式(默认不可创建隐式依赖) 🔓 允许隐式依赖 🔓 允许隐式依赖
Monorepo 支持 ✅ 内置(pnpm workspaces ❌ 需要 npm workspaces yarn workspaces
离线安装 ✅ 支持 ❌ 不支持 ✅ 支持
自动并行安装 ✅ 默认并行安装 ❌ 串行安装(npm 6)✅ 并行安装(npm 7+ ✅ 并行安装
Hoisting(提升依赖) 🚫 默认不提升(更符合依赖层级) ✅ 允许 ✅ 允许
Lockfile pnpm-lock.yaml package-lock.json yarn.lock

1-2 简单命令对比

操作 pnpm npm yarn
安装依赖 pnpm install npm install yarn install
添加依赖 pnpm add <pkg> npm install <pkg> yarn add <pkg>
移除依赖 pnpm remove <pkg> npm uninstall <pkg> yarn remove <pkg>
运行脚本 pnpm run <script> npm run <script> yarn <script>
全局安装 pnpm add -g <pkg> npm install -g <pkg> yarn global add <pkg>
初始化项目 pnpm init npm init yarn init

2. monorepo

2-1 概念

所有模块在同一仓库下,代码共享和协作更方便,无需发布到 npm 才能使用。

2-2 优势

  • 代码共享更容易

    • 所有模块在同一仓库下,代码共享和协作更方便,无需发布到 npm 才能使用。
  • 一致的依赖管理

    • 可以使用 pnpm/yarn/npm workspaces 统一管理依赖,避免每个模块单独安装,减少重复安装的依赖包。
  • 更容易的跨模块修改

    • 在此之前,多采用 Multi-repo 模式,即多个项目分别单独管理,这会导致一些问题,如修改 A 项目 API 需要:
      1. 更新 A 项目代码
      2. 发布新版本到 npm
      3. 更新 B 项目的依赖
    • 在 Monorepo 中,只需在同一个仓库内修改 A 和 B,避免复杂的版本管理。
  • 简化 CI/CD 流程

    • 只需配置一个 CI/CD 流程,即可管理多个模块的构建、测试和发布。
  • 原子性提交

    • 允许一次 PR 修改多个模块,保证所有变更一次性生效,避免版本不兼容问题。

2-3 缺点

  • 首次配置繁琐
  • Git 仓库规模较大,所有项目都在一个仓库中,随着时间推移,Git 历史可能变得庞大。
  • 不方便对项目做权限划分

2-4 Monorepo vs Multi-repo

对比项 Monorepo(单仓库) Multi-repo(多仓库)
代码存储 单个 Git 仓库 每个模块独立 Git 仓库
依赖管理 共享 node_modules,统一管理 每个模块单独管理
版本管理 可以使用 workspace:* 每个模块单独发布版本
跨模块修改 统一 PR 提交,改动同步 需要更新多个仓库,发布新版本
CI/CD 统一 CI/CD 流程 每个仓库需要独立 CI/CD
适用场景 内部共享库、组件库、前端+后端一体化 需要独立发布、独立管理的项目

二、实现流程

1. 构建项目结构

根目录下执行 pnpm init 初始化项目

使用pnpm进行work-space,根据 pnpm官方文档,项目根目录下创建 pnpm-workspace.yaml,基本内容如下:

packages:
  # 指定根目录直接子目录中的包
  - 'my-app'
  # packages/ 直接子目录中的所有包 - 所有前端项目存储的目录
  - 'packages/*'
  # components/ 子目录中的所有包 - 给所有项目使用的公共组件
  - 'components/**'
  # utils/ 子目录中的所有包 - 给所有项目使用的公共工具库
  - 'utils/**'
  # 排除测试目录中的包
  - '!**/test/**'

根目录下创建 .npmrc,用于确保后续下载依赖时,优先从本地查找,而非远程npm。

# 解决pnpm add时优先在本地查找依赖
link-workspace-packages=true 

根据 pnpm-workspace.yaml 中声明的目录创建结构,此时项目目录如下:

在这里插入图片描述

2. 测试本地依赖

通过 utils、packages/web 测试本地依赖

2-1 utils

utils目录 下,执行pnpm init,此时出现 package.json,修改name为 @xxx/utils 格式,并加入 "private": true,

{
  "name": "@tbc/utils",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {}
}

utils目录下创建 text.js

const testFn1 = () => { console.log("fn1"); }
const testFn2 = () => { console.log("fn2"); }

export default {
  testFn1,
  testFn2
}

utils目录下创建 index.js注意: 文件名要与 package.json 中的 main 字段一致,默认为 index.js

在这里插入图片描述
index.js 中整合所有方法并导出

export * from "./test"

2-2 packages/web

packages目录下,创建项目 web 模拟真实项目,执行npm create vitepnpm i
执行pnpm add @tbc/utils安装 utils目录的依赖

在这里插入图片描述
npm run dev启动项目,并在 App.vue 中使用 utils下 的公共方法

import { testFn1 } from '@tbc/utils'
console.log(testFn1(),"===");

查看控制台打印
在这里插入图片描述
此时直接修改 utils目录 下的公共方法(加入return)
在这里插入图片描述
web项目中无需更新依赖,自动使用最新公共方法,查看打印
在这里插入图片描述

3. 组件库

3-1 封装简易组件

组件库与utils同理,在components目录下创建组件库项目,如npm create vite。修改 package.json 的name字段为 @tbc/ui

组件库项目根目录下,创建 dir 目录,用于存储组件。
dir/MyButton.vue

<template>
  <button style="background-color: antiquewhite;width: 150px;border-radius: 8px;">自定义组件</button>
</template>

组件库项目根目录下,创建 index.js 文件,用于对外暴露组件
index.js

import 'element-plus/dist/index.css'; 
import MyButton from "./dir/MyButton.vue"

export { MyButton }

3-2 其他项目中使用组件库

packages目录下其他项目中通过 pnpm add @tbc/ui 安装依赖,依赖详情为 "workspace:^"

使用组件库

<template>
  <MyButton>自定义组件库</MyButton>
</template>

<script setup>
import { MyButton } from "@tbc/ui"
</script>

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到