Vue 3源码极客之:`Vue`的`Vite`:如何利用`Vite`进行`Monorepo`项目开发。

哈喽,大家好!我是你们的老朋友,今天咱们来聊聊一个让前端开发效率起飞的话题:Vue 3 + Vite + Monorepo,这仨凑一块儿,绝对能擦出不一样的火花。

开场白:为什么是它们?

  • Vue 3: 新一代Vue,性能更好,Composition API更灵活,类型推导更棒。
  • Vite: 快,真快!基于浏览器原生ESM,开发体验爽到飞起,再也不用忍受Webpack那漫长的冷启动了。
  • Monorepo: 项目结构更清晰,代码复用更容易,依赖管理更优雅,简直是大型项目的福音。

这三者结合,解决的就是大型项目开发中的效率问题、代码复用问题和项目管理问题。

第一部分:Monorepo基础概念与实践

  1. 什么是Monorepo?

    简单来说,Monorepo就是把多个项目或模块放在同一个代码仓库里管理。与之对应的是Multi-repo,每个项目一个仓库。

    特性 Monorepo Multi-repo
    代码组织 所有项目在一个仓库 每个项目一个仓库
    依赖管理 统一管理,方便共享和复用 各自管理,重复依赖可能存在
    构建部署 统一构建流程,方便整体发布 各自构建流程,独立发布
    代码复用 模块共享更简单,减少重复代码 模块共享需要额外工具或流程
  2. Monorepo的优势

    • 代码复用: 组件库、工具函数等可以轻松在不同项目间共享。
    • 依赖管理: 统一管理依赖版本,避免版本冲突和重复安装。
    • 原子性变更: 修改一个底层库,可以一次性更新所有依赖它的项目。
    • 协作效率: 团队成员更容易了解整个项目的结构和依赖关系。
  3. Monorepo的挑战

    • 仓库体积: 整个仓库会比较大,需要考虑Git的性能。
    • 构建复杂性: 需要工具来管理项目间的依赖关系和构建顺序。
    • 权限管理: 需要更精细的权限控制,防止误操作。
  4. Monorepo工具选择

    市面上有很多Monorepo工具,比如Lerna、Yarn Workspaces、pnpm Workspaces、Nx等等。这里我们选择pnpm Workspaces,因为它速度快、磁盘占用小、支持幽灵依赖管理,非常适合Vue 3 + Vite的项目。

第二部分:使用pnpm Workspaces搭建Monorepo项目

  1. 初始化项目

    首先,创建一个空目录,作为Monorepo的根目录:

    mkdir vue3-vite-monorepo
    cd vue3-vite-monorepo

    然后,初始化pnpm:

    pnpm init

    这会生成一个package.json文件。

  2. 配置pnpm-workspace.yaml

    在根目录下创建一个pnpm-workspace.yaml文件,用来告诉pnpm哪些目录是workspace:

    packages:
      - 'packages/*'
      - 'apps/*'

    这里我们约定:

    • packages/ 目录存放共享的npm包。
    • apps/ 目录存放独立的Vue应用。
  3. 创建共享模块 (packages/)

    packages/目录下创建一个名为ui-library的目录,作为我们的组件库:

    mkdir -p packages/ui-library
    cd packages/ui-library
    pnpm init

    packages/ui-library/package.json中添加一些基本信息:

    {
      "name": "@vue3-vite-monorepo/ui-library",
      "version": "0.1.0",
      "description": "A Vue 3 component library",
      "main": "dist/ui-library.js",
      "module": "dist/ui-library.es.js",
      "types": "dist/ui-library.d.ts",
      "files": [
        "dist"
      ],
      "scripts": {
        "build": "vite build",
        "dev": "vite"
      },
      "devDependencies": {
        "vue": "^3.0.0",
        "vite": "^4.0.0",
        "@vitejs/plugin-vue": "^4.0.0",
        "typescript": "^4.0.0",
        "@vue/compiler-sfc": "^3.0.0",
        "vue-tsc": "^1.0.0"
      },
      "peerDependencies": {
        "vue": "^3.0.0"
      }
    }
    • name: 一定要带scope,例如 @vue3-vite-monorepo/ui-library,这样可以避免和其他npm包冲突。
    • main, module, types: 指定构建后的文件路径。
    • peerDependencies: 声明对vue的依赖,避免重复安装。
    • devDependencies: 开发依赖,包含vitevue@vitejs/plugin-vue等。

    安装依赖:

    pnpm install

    创建一个简单的组件: packages/ui-library/src/components/MyButton.vue

    <template>
      <button class="my-button">{{ label }}</button>
    </template>
    
    <script setup lang="ts">
    import { defineProps } from 'vue';
    
    defineProps({
      label: {
        type: String,
        required: true,
        default: 'Click Me'
      }
    });
    </script>
    
    <style scoped>
    .my-button {
      background-color: #4CAF50;
      border: none;
      color: white;
      padding: 10px 20px;
      text-align: center;
      text-decoration: none;
      display: inline-block;
      font-size: 16px;
      cursor: pointer;
    }
    </style>

    创建一个入口文件:packages/ui-library/src/index.ts

    import MyButton from './components/MyButton.vue';
    
    export {
      MyButton
    };

    配置vite.config.ts: packages/ui-library/vite.config.ts

    import { defineConfig } from 'vite';
    import vue from '@vitejs/plugin-vue';
    import { resolve } from 'path';
    
    export default defineConfig({
      plugins: [vue()],
      build: {
        lib: {
          entry: resolve(__dirname, 'src/index.ts'),
          name: 'UiLibrary',
          fileName: (format) => `ui-library.${format}.js`,
        },
        rollupOptions: {
          external: ['vue'],
          output: {
            globals: {
              vue: 'Vue',
            },
          },
        },
      },
    });
    • lib: 配置库模式构建。
    • entry: 指定入口文件。
    • name: 指定库的名称,会在全局暴露。
    • fileName: 指定输出文件名。
    • rollupOptions.external: 指定外部依赖,这里我们指定vue,避免重复打包。
    • rollupOptions.output.globals: 指定全局变量,这里我们指定vue的全局变量为Vue

    最后,构建组件库:

    pnpm build

    这会在packages/ui-library/dist/目录下生成构建后的文件。

  4. 创建Vue应用 (apps/)

    apps/目录下创建一个名为my-app的目录:

    mkdir -p apps/my-app
    cd apps/my-app

    使用Vite初始化Vue项目:

    pnpm create vite . --template vue-ts

    这会创建一个标准的Vue + TypeScript项目。

    安装依赖:

    pnpm install
  5. 在Vue应用中使用组件库

    现在,我们需要在my-app中使用ui-library。 由于pnpm Workspaces的特性,我们可以直接通过@vue3-vite-monorepo/ui-library来引用,而无需发布到npm仓库。

    修改apps/my-app/src/App.vue:

    <template>
      <img alt="Vue logo" src="./assets/logo.png" />
      <HelloWorld msg="Hello Vue 3 + TypeScript + Vite" />
      <MyButton label="Monorepo Button" />
    </template>
    
    <script setup lang="ts">
    import HelloWorld from './components/HelloWorld.vue'
    import { MyButton } from '@vue3-vite-monorepo/ui-library' // 引入组件库
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>

    启动Vue应用:

    pnpm dev

    你应该能看到MyButton组件成功显示在页面上。

第三部分:Monorepo高级技巧与最佳实践

  1. 依赖提升 (Hoisting)

    pnpm会自动将公共依赖提升到根目录,减少重复安装,节省磁盘空间。 例如,如果ui-librarymy-app都依赖lodash,那么lodash只会安装一份在根目录的node_modules/下。

  2. 依赖隔离 (Isolation)

    pnpm使用node_modules/.pnpm目录来存储依赖,实现了真正的依赖隔离,避免了幽灵依赖问题。 每个项目只能访问自己声明的依赖,不能访问未声明的依赖。

  3. 使用ESLint和Prettier进行代码规范

    在Monorepo中,保持代码风格一致非常重要。 可以在根目录下配置ESLint和Prettier,并使用pnpm的filter功能,只对修改过的项目进行代码检查。

    // 在根目录的package.json中
    {
      "scripts": {
        "lint": "eslint --ext .js,.ts,.vue .",
        "format": "prettier --write ."
      }
    }
    # 只对apps/my-app目录进行eslint检查
    pnpm lint --filter my-app
  4. 使用Changesets进行版本管理

    Changesets是一个非常优秀的Monorepo版本管理工具,它可以自动生成changelog,自动更新依赖版本,自动发布npm包。

    首先,安装Changesets:

    pnpm add @changesets/cli -D -w

    初始化Changesets:

    pnpm changeset init

    这会在根目录下创建一个.changeset目录。

    当需要发布新版本时,运行:

    pnpm changeset

    这会提示你选择需要更新的项目,并填写更新说明。 Changesets会将这些信息保存到.changeset目录下的markdown文件中。

    然后,运行:

    pnpm version

    Changesets会根据.changeset目录下的markdown文件,自动更新每个项目的package.json中的版本号,并生成changelog。

    最后,运行:

    pnpm publish

    Changesets会自动发布更新后的npm包。

  5. 持续集成 (CI/CD)

    Monorepo的CI/CD流程需要特别注意。 可以使用GitHub Actions或其他CI/CD工具,根据代码变更情况,只构建和部署受影响的项目。

    例如,可以使用nx affected命令来找出受影响的项目:

    nx affected --target=build --base=main --head=HEAD

    这会列出所有需要构建的项目。

第四部分:Vue 3 + Vite + Monorepo的优势总结

优势 描述
代码复用与共享 组件库、工具函数等可以轻松在不同项目间共享,避免重复代码,提高开发效率。
依赖管理 统一管理依赖版本,避免版本冲突和重复安装,节省磁盘空间。
构建速度 Vite的快速冷启动和热更新,大大提升了开发体验。即使在大型Monorepo项目中,也能保持流畅的开发体验。
项目结构清晰 Monorepo的结构更清晰,更容易了解整个项目的结构和依赖关系,方便团队协作。
版本管理 Changesets等工具可以方便地进行版本管理,自动生成changelog,自动更新依赖版本,自动发布npm包。
CI/CD 可以灵活地配置CI/CD流程,根据代码变更情况,只构建和部署受影响的项目,提高构建和部署效率。

总结与展望

Vue 3 + Vite + Monorepo是一个强大的组合,可以显著提升大型前端项目的开发效率和可维护性。 虽然配置和管理Monorepo需要一定的学习成本,但带来的好处远大于付出的成本。

当然,Monorepo并不是银弹,需要根据项目的实际情况选择合适的工具和架构。 希望今天的分享能帮助大家更好地理解和应用Vue 3 + Vite + Monorepo,打造更高效、更优雅的前端项目。

祝大家编码愉快!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注