高效前端工程化:Monorepo、pnpm与Vue3集成实战指南

引言

在当今快速发展的前端开发领域,高效地管理和组织代码库成为提升开发效率的关键。随着项目规模的扩大,传统的单体仓库逐渐显露出局限性,而新兴的包管理工具如 PNPM、项目结构模式如 MonorepoTurborepo 开始受到广泛关注。将教会大家如何快速搭建 monorepo + pnpm + trborepo +vue3 + element-plus 项目架构。

pnpm:下一代包管理器

pnpm(Package Manager) 是一个快速、节省磁盘空间的 JavaScript 包管理器,它通过引入"链接"和"硬链接"的概念来优化 Node.js 项目的依赖管理。与 npmYarn 相比,pnpm 在安装依赖时,会创建依赖的唯一实例,并通过硬链接或符号链接的方式供各个项目共享,大大减少了磁盘占用和安装时间。此外,pnpm 的精确依赖解析机制能有效避免"dependency hell",保障项目的稳定性和可复现性。

Monorepo:一统天下的仓库策略

Monorepo(单一仓库)是一种将多个相关项目的源代码存储在一个单一版本控制系统仓库中的策略。这种模式下,无论是微服务架构的后端服务,还是包含多个前端应用的大型项目,都可以共处一室,共享配置、依赖和工具链。Monorepo 的优势在于简化跨项目协作、代码复用、统一版本管理和 CI/CD 流程。然而,随之而来的是对版本控制系统的高效管理需求,以及如何处理大型仓库带来的构建速度问题。

Turborepo:为Monorepo加速

Turborepo 正是针对 Monorepo 模式下构建速度慢的问题提出的一种解决方案。它通过智能缓存、并行执行和增量构建等技术,显著加快了 Monorepo 中项目的构建和测试速度。Turborepo 能够识别出哪些文件或包没有变化,从而跳过不必要的工作,仅重新构建那些受影响的部分。这种优化对于大型组织而言尤为重要,它使得即使仓库包含成百上千个子项目,开发者也能获得接近即时的反馈循环,极大提升了开发效率。

单体仓库与上述方案的对比

相比之下,传统的单体仓库是指一个项目对应一个仓库的模式,适用于小型项目或初创阶段的项目。在单体仓库中,所有源代码、配置文件和依赖都紧密耦合在一起,便于管理但难以扩展。随着项目复杂度增加,代码库的维护成本和团队间的协调成本会迅速上升。

  • 可维护性与扩展性MonorepoTurborepo 由于支持跨项目共享和高效管理,明显优于单体仓库,尤其适合中大型项目和企业级应用。
  • 开发效率pnpm 通过优化依赖管理提升安装速度;Turborepo 则通过智能构建机制,解决了 Monorepo 的构建效率问题,两者共同推动了开发效率的飞跃。
  • 协作与代码复用Monorepo 鼓励跨项目代码共享,而 Turborepo 在此基础上进一步优化了协作体验,单体仓库在这方面则显得较为局限。

架构搭建

1.创建项目

新建文件夹自定义命名,暂且为 monorepo-demo,然后用VSCode编辑器打开,新建终端,操作如下:

2.利用 pnpm init 在根目录初始化

kotlin 复制代码
PS G:\wokespace\FullStackProjects\pnpm-monorepo-demo> pnpm init
Wrote to G:\wokespace\FullStackProjects\pnpm-monorepo-demo\package.json

{
  "name": "pnpm-monorepo-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
PS G:\wokespace\FullStackProjects\pnpm-monorepo-demo>

打开 package.json 文件,"private": true 加这个为防止我们意外地将私有项目发布。

3. 根据pnpm中的文档在根目录创建 pnpm-workspace.yaml 文件

根据自己项目需求创建合适的目录结构,示例如下:

4.在 packages 文件下创建存放 公共的UI组件 (ui) 和公共的工具函数 (utils) 两个项目

  • 新建 uiutils 文件夹,并利用 pnpm init 进行初始化。同时在各自的 package.json 文件中 新增属性 "private": true, 其中 name 属性值,可以自定义合适的名称。

ui 项目的名称这里自定义为 @repo/uiutils 项目的名称这里自定义为 @repo/utils

  • ui 项目下自定义新建 components 文件夹,用来存放公共的UI组件,暂时新建两个组件 FormatMoney.vueSlider.vue;然后同层级下新建 index.js 文件来导出组件提供外部访问。
  1. 利用 element-plus UI组件来开发公共UI组件,因此先安装依赖
css 复制代码
pnpm i vue element-plus
  1. 编写 FormatMoney.vue, Slider.vue, index.js 文件

FormatMoney.vue

ini 复制代码
<template>
  <el-form
    ref="formRef"
    style="max-width: 600px"
    :model="numberValidateForm"
    label-width="auto"
    class="demo-ruleForm"
  >
    <el-form-item
      label="金额"
      prop="money"
      :rules="[
        { required: true, message: '金额不能为空' },
        { type: 'number', message: '金额是数字类型' },
      ]"
    >
      <el-input
        v-model.number="numberValidateForm.money"
        type="text"
        autocomplete="off"
      />
    </el-form-item>
    <el-form-item
      label="格式化后的金额"
      prop="amount" 
    >
      <el-input
        v-model="numberValidateForm.amount"
        type="text"
        autocomplete="off"
        readonly
      />
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="submitForm(formRef)">格式化</el-button>
      <el-button @click="resetForm(formRef)">重置</el-button>
    </el-form-item>
  </el-form>
</template>

<script setup>
import "element-plus/dist/index.css";
import { ElForm, ElFormItem, ElInput, ElButton } from "element-plus";
import { reactive, ref } from 'vue'
import { formatMoney } from "repo-utils";

console.log('formatMoney', formatMoney(2342113241, '$'));

const formRef = ref()

const numberValidateForm = reactive({
  money: '',
  amount: ''
})

const submitForm = (formEl) => {
  if (!formEl) return
  formEl.validate((valid) => {
    if (valid) {
      numberValidateForm.amount = formatMoney(numberValidateForm.money, '$');
      console.log('submit!')
    } else {
      console.log('error submit!')
    }
  })
}

const resetForm = (formEl) => {
  if (!formEl) return
  formEl.resetFields()
}
</script>

Slider.vue

xml 复制代码
<script setup>
import "element-plus/dist/index.css";
import { ElSlider } from "element-plus";
import { ref } from 'vue'

const value1 = ref(0)
</script>

<template>
  <div class="slider-demo-block">
    <span class="demonstration">默认值</span>
    <el-slider v-model="value1" />
  </div>
</template>

<style scoped>
.slider-demo-block {
  max-width: 600px;
  display: flex;
  align-items: center;
}
.slider-demo-block .el-slider {
  margin-top: 0;
  margin-left: 12px;
}
.slider-demo-block .demonstration {
  font-size: 14px;
  color: black;
  width: 120px;
  padding: 10px;
}
</style>

index.js

javascript 复制代码
import FormatMoney from './components/FormatMoney.vue'
import Slider from './components/Slider.vue'

export {
  FormatMoney,
  Slider
}
  1. UI 项目最后的目录结构如下:
  • utils 项目下新建两个文件 fun.js (存放各种工具方法)index.js (提供对外访问的方法)

fun.js

typescript 复制代码
// 弹窗提示
export const tips = (message, title = "提示") => {
  window.alert(`${title}: ${message}`)
}

// 加运算
export const addOperation = (a, b) => {
  window.alert(`1加2的结果是${a + b}`);
}

// 格式化金额
export const formatMoney = (money, symbol = "", decimals = 2) => {
  return (Math.round((parseFloat(money) + Number.EPSILON) * Math.pow(10, decimals)) / Math.pow(10, decimals)).toFixed(
    decimals
  )
  .replace(/\B(?=(\d{3})+\b)/g, ",")
  .replace(/^/, `${symbol}`)
};

index.js

javascript 复制代码
export * from "./fun.js";

5. 利用 vite 工具在 apps 文件下创建各种子项目,暂且创建 docsweb 两个项目,操作如下:

lua 复制代码
pnpm create vite 

执行上面的命令,按照提示一步步根据自己需求创建两个项目,最终目录结构如下:

6.回到根目录,在根目录下全局安装 @repo/ui@repo/utils ,这样在任何子应用或者子包都可以相互使用。 如果要安装到根项目中(即全局项目中)那么可以在指令后面加上 -w

bash 复制代码
pnpm i -w @reop/ui @repo/utils

安装完毕后,可以在 package.json 文件中看到如下信息:

7.在子项目 docsweb 中使用

  • 进入 web 项目,将 App.vue 文件内容修改如下:
xml 复制代码
<script setup>
import { tips } from "repo-utils";
import { FormatMoney } from "repo-ui";
</script>

<template>
  <h1>web项目</h1>
  <FormatMoney></FormatMoney>
</template>

<style scoped>
h1 {
  margin-bottom: 50px;
}
</style>
  • 进入 docs 项目,将 App.vue 文件内容修改如下:
xml 复制代码
<script setup>
import { tips } from "repo-utils";
import { FormatMoney, Slider } from "repo-ui";
tips("我是docs项目");
</script>

<template>
  <h1>docs项目</h1>
  <Slider></Slider>
</template>
<style scoped>
h1 {
  margin-bottom: 50px;
}
</style>

分别启动项目,运行效果如下:

使用 Turborepo 构建打包

1.全局安装 Turborepo

css 复制代码
pnpm i -g turbo

# 检测是否安装成功
λ turbo --version
1.13.3

2.根目录新建文件 turbo.json, 默认内容如下:

bash 复制代码
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env.*local"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "!.next/cache/**", "dist/**"]
    },
    "lint": {
      "dependsOn": ["^lint"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

3.在根目录下修改 package.json 文件,添加执行命令脚本, 具体如下:

4. 进入各个子项目或者子包中,修改 .gitignore 文件,增加如下内容:

lua 复制代码
.turbo
build/
dist/
.next/

5.根目录执行 pnpm devpnpm build,会对子项目全量启动或打包,具体如下:

  • 全量启动项目
css 复制代码
λ pnpm dev

> monorepo-demo@1.0.0 dev G:\wokespace\FullStackProjects\pnpm-monorepo-demo
> turbo dev

• Packages in scope: docs, repo-ui, repo-utils, web
• Running dev in 4 packages
• Remote caching disabled
docs:dev: cache bypass, force executing 0906b5c91c3b269b
web:dev: cache bypass, force executing b446edc8270ef0f2
docs:dev:
docs:dev: > docs@0.0.0 dev G:\wokespace\FullStackProjects\monorepo-demo\apps\docs
docs:dev: > vite
docs:dev:
web:dev:
web:dev: > web@0.0.0 dev G:\wokespace\FullStackProjects\monorepo-demo\apps\web
web:dev: > vite
web:dev:
docs:dev: Port 5173 is in use, trying another one...
web:dev:
web:dev:   VITE v5.2.8  ready in 20313 ms
web:dev:
web:dev:   ➜  Local:   http://localhost:5173/
web:dev:   ➜  Network: use --host to expose
web:dev:   ➜  Vue DevTools: Open http://localhost:5173/__devtools__/ as a separate window
web:dev:   ➜  Vue DevTools: Press Alt(⌥)+Shift(⇧)+D in App to toggle the Vue DevTools
web:dev:
docs:dev:
docs:dev:   VITE v5.2.8  ready in 20368 ms
docs:dev:
docs:dev:   ➜  Local:   http://localhost:5174/
docs:dev:   ➜  Network: use --host to expose
docs:dev:   ➜  Vue DevTools: Open http://localhost:5174/__devtools__/ as a separate window
docs:dev:   ➜  Vue DevTools: Press Alt(⌥)+Shift(⇧)+D in App to toggle the Vue DevTools
docs:dev:
  • 全量打包子项目
css 复制代码
λ pnpm build

> monorepo-demo@1.0.0 build G:\wokespace\FullStackProjects\monorepo-demo
> turbo build

• Packages in scope: docs, repo-ui, repo-utils, web
• Running build in 4 packages
• Remote caching disabled
web:build: cache miss, executing 5987b2c98bceeb10
docs:build: cache miss, executing aff8ae65ab7f527e
web:build:
docs:build:
web:build: > web@0.0.0 build G:\wokespace\FullStackProjects\monorepo-demo\apps\web
web:build: > vite build
web:build:
docs:build: > docs@0.0.0 build G:\wokespace\FullStackProjects\monorepo-demo\apps\docs
docs:build: > vite build
docs:build:
web:build: vite v5.2.8 building for production...
docs:build: vite v5.2.8 building for production...
web:build: transforming...
docs:build: transforming...
web:build: ✓ 1435 modules transformed.
docs:build: ✓ 1435 modules transformed.
docs:build: rendering chunks...
web:build: rendering chunks...
docs:build: computing gzip size...
web:build: computing gzip size...
docs:build: dist/index.html                       0.43 kB │ gzip:  0.29 kB
web:build: dist/index.html                       0.43 kB │ gzip:  0.29 kB
docs:build: dist/assets/AboutView-C6Dx7pxG.css    0.09 kB │ gzip:  0.10 kB
web:build: dist/assets/AboutView-C6Dx7pxG.css    0.09 kB │ gzip:  0.10 kB
web:build: dist/assets/index-B_6bWB-a.css      328.68 kB │ gzip: 45.17 kB
web:build: dist/assets/AboutView-9L8e1QJt.js     0.23 kB │ gzip:  0.20 kB
docs:build: dist/assets/index-DM4ReSuC.css      328.68 kB │ gzip: 45.17 kB
web:build: dist/assets/index-BUYK55Bj.js       175.04 kB │ gzip: 65.02 kB
docs:build: dist/assets/AboutView-CgU-TZmY.js     0.23 kB │ gzip:  0.20 kB
docs:build: dist/assets/index-wHPYhhNI.js       195.12 kB │ gzip: 72.35 kB
web:build: ✓ built in 14.67s
docs:build: ✓ built in 14.67s

 Tasks:    2 successful, 2 total
Cached:    0 cached, 2 total
  Time:    25.729s

关于 Turborepo 具体教程,请参考官网文档进行查阅,目前正在翻译官网文档,后续会开放浏览地址供阅览。

写在最后

至此一步步完成了利用 pnpm, monorepo, turborepo 等技术搭建多项目多包统一仓库管理的架构,解决了项目规模扩大后的管理难题,提高了开发效率和团队协作水平。选择合适的工具和策略,对提升项目成功率至关重要。

源码放到github仓库上,点击进行查看

相关推荐
开心工作室_kaic17 分钟前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿36 分钟前
webWorker基本用法
前端·javascript·vue.js
cy玩具1 小时前
点击评论详情,跳到评论页面,携带对象参数写法:
前端
qq_390161772 小时前
防抖函数--应用场景及示例
前端·javascript
John.liu_Test2 小时前
js下载excel示例demo
前端·javascript·excel
Yaml42 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事2 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
哟哟耶耶2 小时前
js-将JavaScript对象或值转换为JSON字符串 JSON.stringify(this.SelectDataListCourse)
前端·javascript·json
getaxiosluo2 小时前
react jsx基本语法,脚手架,父子传参,refs等详解
前端·vue.js·react.js·前端框架·hook·jsx
理想不理想v2 小时前
vue种ref跟reactive的区别?
前端·javascript·vue.js·webpack·前端框架·node.js·ecmascript