Vue 3 + Vite + Router + Pinia + Element Plus + Monorepo + qiankun 构建企业级中后台前端框架


Vue 3 + Vite + Monorepo + Qiankun 微前端搭建指南

目录

  1. 环境准备
  2. [初始化 Monorepo 项目](#初始化 Monorepo 项目 "#2-%E5%88%9D%E5%A7%8B%E5%8C%96-monorepo-%E9%A1%B9%E7%9B%AE")
  3. 安装公共依赖
  4. [配置代码规范和 TypeScript](#配置代码规范和 TypeScript "#4-%E9%85%8D%E7%BD%AE%E4%BB%A3%E7%A0%81%E8%A7%84%E8%8C%83%E5%92%8C-typescript")
  5. 创建共享库
    • [5.1 公共工具库 (packages/utils)](#5.1 公共工具库 (packages/utils) "#51-%E5%85%AC%E5%85%B1%E5%B7%A5%E5%85%B7%E5%BA%93-packagesutils")
    • [5.2 公共 Hooks 库 (packages/hooks)](#5.2 公共 Hooks 库 (packages/hooks) "#52-%E5%85%AC%E5%85%B1-hooks-%E5%BA%93-packageshooks")
    • [5.3 公共 UI 组件库 (packages/components)](#5.3 公共 UI 组件库 (packages/components) "#53-%E5%85%AC%E5%85%B1-ui-%E7%BB%84%E4%BB%B6%E5%BA%93-packagescomponents")
    • [5.4 公共请求封装 (packages/request)](#5.4 公共请求封装 (packages/request) "#54-%E5%85%AC%E5%85%B1%E8%AF%B7%E6%B1%82%E5%B0%81%E8%A3%85-packagesrequest")
  6. [创建主应用 (apps/main)](#创建主应用 (apps/main) "#6-%E5%88%9B%E5%BB%BA%E4%B8%BB%E5%BA%94%E7%94%A8-appsmain")
  7. [创建微应用 (apps/app1)](#创建微应用 (apps/app1) "#7-%E5%88%9B%E5%BB%BA%E5%BE%AE%E5%BA%94%E7%94%A8-appsapp1")
  8. 配置主应用和微应用的路由
  9. 测试和运行
  10. 构建和部署

一、技术选型考量

  1. 核心技术栈确定
  • Vue 3:作为核心框架,其组合式 API、更好的 TypeScript 支持以及优异的性能,为中后台项目的复杂逻辑处理和组件复用提供了强大基础。
  • Vite:替代传统的 Webpack 构建工具,以其极速的冷启动、按需编译和热更新能力,显著提升开发效率,尤其适合大型项目的开发流程。
  • Vue Router 4:与 Vue 3 深度适配,提供了更灵活的路由配置方式,支持动态路由、嵌套路由等功能,满足中后台系统复杂的页面跳转需求。
  • Pinia:作为 Vuex 的替代方案,Pinia 具有更简洁的 API 设计、更好的 TypeScript 兼容性,同时支持多 Store 架构,便于状态的模块化管理。
  • Element Plus:基于 Vue 3 的 UI 组件库,提供了丰富的中后台常用组件,如表格、表单、弹窗等,能够快速搭建美观、易用的界面。
  • Monorepo:采用单一代码仓库管理多个相关项目,实现代码共享、依赖统一管理,简化团队协作流程,尤其适合多项目、多模块的中后台系统。
  • qiankun:微前端框架,能够将多个独立的前端应用整合为一个整体,实现应用间的无缝切换、通信和资源共享,满足中后台系统的业务拆分和整合需求。
  1. 技术栈优势互补 这些技术的组合形成了强大的优势互补。Vue 3 和 Vite 保证了项目的性能和开发体验;Router 和 Pinia 实现了页面路由和状态的高效管理;Element Plus 加速了 UI 开发;Monorepo 优化了项目的组织和协作方式;qiankun 则为系统的微前端架构提供了支持,使得各业务模块可以独立开发、部署和维护,同时又能有机地结合在一起。

整体架构概览

我们将创建以下目录结构:

bash 复制代码
vue3-monorepo-qiankun/
├── apps/                # 应用目录
│   ├── main/            # 主应用 (qiankun 容器)
│   └── app1/            # 微应用 1
├── packages/            # 共享库目录
│   ├── components/      # 共享组件库
│   ├── hooks/           # 共享 Hooks 库
│   └── utils/           # 共享工具库
├── package.json         # 根项目配置
├── pnpm-workspace.yaml  # pnpm 工作区配置
└── tsconfig.json        # 全局 TypeScript 配置

第一步:环境准备

确保你已经安装了 pnpmnode (推荐 v16+)。

bash 复制代码
npm install -g pnpm

第二步:初始化 Monorepo 项目

  1. 创建并进入项目根目录

    bash 复制代码
    mkdir -p vue3-monorepo-qiankun && cd vue3-monorepo-qiankun
  2. 初始化 package.json

    bash 复制代码
    pnpm init -y
  3. 创建 pnpm-workspace.yaml 文件

    bash 复制代码
    cat > pnpm-workspace.yaml << EOF
    packages:
      - 'apps/*'
      - 'packages/*'
    EOF
  4. 创建 .npmrc 文件 (推荐) 这个文件用于配置 pnpm 的行为,让它更像传统的 node_modules 结构,方便某些工具识别。

    bash 复制代码
    cat > .npmrc << EOF
    shamefully-hoist=true
    EOF

第三步:安装公共依赖

这是本方案的核心。我们将所有共享的运行时依赖和开发依赖都安装在根目录。

  1. 安装公共运行时依赖 这些是主应用和微应用都需要用到的库,如 vue, element-plus 等。

    bash 复制代码
    pnpm add -w vue vue-router pinia element-plus @element-plus/icons-vue axios qiankun

    说明 : -w--workspace-root 是关键,它告诉 pnpm 把依赖安装到工作区的根目录,而不是当前目录(虽然这里我们就在根目录)。

  2. 安装公共开发依赖 这些是构建、 lint、测试等工具,如 vite, typescript, eslint 等。

    bash 复制代码
    pnpm add -Dw @vitejs/plugin-vue @vitejs/plugin-vue-jsx vite typescript vue-tsc @types/node eslint prettier eslint-config-prettier eslint-plugin-prettier eslint-plugin-vue vue-eslint-parser @typescript-eslint/eslint-plugin @typescript-eslint/parser vite-plugin-qiankun

执行完毕后,你会发现根目录的 package.json 中已经包含了所有这些依赖,并且根目录下出现了一个 node_modules 文件夹。


第四步: 配置代码规范和 TypeScript

  1. 创建 tsconfig.json 这个文件为整个工作区提供基础的 TypeScript 配置,子项目可以继承它。

    bash 复制代码
    cat > tsconfig.json << EOF
    {
      "compilerOptions": {
        "target": "ESNext",
        "useDefineForClassFields": true,
        "module": "ESNext",
        "moduleResolution": "Node",
        "strict": true,
        "jsx": "preserve",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "esModuleInterop": true,
        "lib": ["ESNext", "DOM"],
        "skipLibCheck": true,
        "noEmit": true,
        "baseUrl": ".",
        "paths": {
          "@/*": ["src/*"]
        }
      },
      "include": ["**/*.ts", "**/*.tsx", "**/*.vue"],
      "exclude": ["node_modules", "**/dist"]
    }
    EOF
  2. 创建 ESLint 和 Prettier 配置 在根目录创建这些配置文件,可以让所有子项目共享同一套代码规范。

    bash 复制代码
    # 创建 .eslintrc.js
    cat > .eslintrc.js << EOF
    module.exports = {
      root: true,
      env: {
        browser: true,
        es2021: true,
        node: true,
      },
      extends: [
        'eslint:recommended',
        'plugin:vue/vue3-essential',
        'plugin:@typescript-eslint/recommended',
        'plugin:prettier/recommended',
      ],
      parser: 'vue-eslint-parser',
      parserOptions: {
        ecmaVersion: 'latest',
        parser: '@typescript-eslint/parser',
        sourceType: 'module',
      },
      plugins: ['vue', '@typescript-eslint', 'prettier'],
      rules: {
        'prettier/prettier': 'error',
        'vue/no-unused-vars': 'error',
        '@typescript-eslint/no-unused-vars': 'error',
        '@typescript-eslint/explicit-module-boundary-types': 'off',
      },
    };
    EOF
    
    # 创建 .prettierrc
    cat > .prettierrc << EOF
    {
      "printWidth": 100,
      "tabWidth": 2,
      "useTabs": false,
      "semi": true,
      "singleQuote": true,
      "quoteProps": "as-needed",
      "trailingComma": "es5",
      "bracketSpacing": true,
      "arrowParens": "avoid",
      "endOfLine": "auto",
      "vueIndentScriptAndStyle": false
    }
    EOF
    
    # 创建 .eslintignore 和 .prettierignore
    cat > .eslintignore << EOF
    node_modules/
    dist/
    *.d.ts
    EOF
    cp .eslintignore .prettierignore
  3. 更新根目录 package.jsonscripts 添加一些方便在根目录运行的脚本,比如全局 lint。

    json 复制代码
    {
      "scripts": {
        "dev": "pnpm -r dev",
        "build": "pnpm -r build",
        "lint": "eslint . --ext .vue,.js,.ts",
        "format": "prettier --write .",
        "clean": "pnpm -r --delete node_modules && rm -rf node_modules"
      }
    }

第五步:创建共享库 (packages)

共享库将直接使用根目录的依赖,它们自己的 package.json 只需要声明依赖即可。

5.1 公共工具库 (packages/utils)

  1. 创建目录并初始化

    bash 复制代码
    mkdir -p packages/utils/src && cd packages/utils
    pnpm init -y
  2. 修改 package.json 关键是 dependencies 字段,我们声明需要 vue,但 pnpm 会自动从根目录查找。

    json 复制代码
    {
      "name": "@your-org/utils",
      "version": "1.0.0",
      "type": "module",
      "main": "src/index.ts",
      "types": "src/index.ts",
      "scripts": {
        "lint": "eslint . --ext .ts"
      },
      "dependencies": {
        "vue": "^3.4.21"
      }
    }
  3. 创建 tsconfig.json 继承根目录的配置。

    json 复制代码
    {
      "extends": "../../tsconfig.json",
      "compilerOptions": {
        "composite": true
      },
      "include": ["src/**/*.ts", "src/**/*.d.ts"]
    }
  4. 编写工具函数 创建 src/format.ts 并在 src/index.ts 中导出

    • src/format.ts

      typescript 复制代码
      export function formatDate(date: Date, fmt = 'YYYY-MM-DD HH:mm:ss') {
        const o = {
          'M+': date.getMonth() + 1,
          'D+': date.getDate(),
          'H+': date.getHours(),
          'm+': date.getMinutes(),
          's+': date.getSeconds(),
          'q+': Math.floor((date.getMonth() + 3) / 3),
          S: date.getMilliseconds()
        };
        if (/(Y+)/.test(fmt)) {
          fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
        }
        for (const k in o) {
          if (new RegExp('(' + k + ')').test(fmt)) {
            fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k as keyof typeof o]) : (('00' + o[k as keyof typeof o]).substr(('' + o[k as keyof typeof o]).length)));
          }
        }
        return fmt;
      }
    • src/index.ts

      typescript 复制代码
      export * from './format';

5.2 公共 Hooks 库 (packages/hooks)

  1. 创建目录并初始化

    bash 复制代码
    cd ../../ && mkdir -p packages/hooks/src && cd packages/hooks
    pnpm init -y
  2. 修改 package.json

    json 复制代码
    {
      "name": "@your-org/hooks",
      "version": "1.0.0",
      "type": "module",
      "main": "src/index.ts",
      "types": "src/index.ts",
      "scripts": {
        "lint": "eslint . --ext .ts,.vue"
      },
      "dependencies": {
        "vue": "^3.4.21"
      }
    }
  3. 创建 tsconfig.json

    json 复制代码
    {
      "extends": "../../tsconfig.json",
      "compilerOptions": {
        "composite": true
      },
      "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"]
    }
  4. 编写 Hooks

    • src/useStorage.ts

      typescript 复制代码
      import { ref, watch, type Ref } from 'vue';
      
      export function useStorage<T>(key: string, defaultValue: T): Ref<T> {
        const storedValue = localStorage.getItem(key);
        const value = ref<T>(storedValue ? JSON.parse(storedValue) : defaultValue);
      
        watch(value, (newVal) => {
          localStorage.setItem(key, JSON.stringify(newVal));
        }, { deep: true });
      
        return value;
      }
    • src/index.ts

      typescript 复制代码
      export * from './useStorage';

5.3 公共 UI 组件库 (packages/components)

  1. 创建目录并初始化

    bash 复制代码
    cd ../../ && mkdir -p packages/components/src && cd packages/components
    pnpm init -y
  2. 修改 package.json

    json 复制代码
    {
      "name": "@your-org/components",
      "version": "1.0.0",
      "type": "module",
      "main": "src/index.ts",
      "types": "src/index.ts",
      "scripts": {
        "lint": "eslint . --ext .ts,.vue"
      },
      "dependencies": {
        "vue": "^3.4.21",
        "element-plus": "^2.7.2"
      }
    }
  3. 创建 tsconfig.json

    json 复制代码
    {
      "extends": "../../tsconfig.json",
      "compilerOptions": {
        "composite": true
      },
      "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"]
    }
  4. 编写 UI 组件

    • src/CommonButton/CommonButton.vue

      vue 复制代码
      <template>
        <el-button :type="type" :loading="loading" @click="onClick">
          <slot></slot>
        </el-button>
      </template>
      
      <script setup lang="ts">
      import { defineProps, emit } from 'vue';
      
      const props = defineProps<{
        type?: 'primary' | 'success' | 'warning' | 'danger' | 'info';
        loading?: boolean;
      }>();
      
      const emit = defineEmits<{
        (e: 'click'): void;
      }>();
      
      const onClick = () => {
        emit('click');
      };
      </script>
    • src/index.ts

      typescript 复制代码
      export { default as CommonButton } from './CommonButton/CommonButton.vue';

5.4 公共请求封装 (packages/request)

  1. 创建目录并初始化

    bash 复制代码
    cd ../../ && mkdir -p packages/request/src && cd packages/request
    pnpm init -y
  2. 修改 package.json

    json 复制代码
    {
      "name": "@your-org/request",
      "version": "1.0.0",
      "type": "module",
      "main": "src/index.ts",
      "types": "src/index.ts",
      "scripts": {
        "lint": "eslint . --ext .ts"
      },
      "dependencies": {
        "axios": "^1.6.8",
        "vue": "^3.4.21"
      }
    }
  3. 创建 tsconfig.json

    json 复制代码
    {
      "extends": "../../tsconfig.json",
      "compilerOptions": {
        "composite": true
      },
      "include": ["src/**/*.ts", "src/**/*.d.ts"]
    }
  4. 编写请求封装

    • src/index.ts

      typescript 复制代码
      import axios from 'axios';
      
      const service = axios.create({
        baseURL: import.meta.env.VITE_API_BASE_URL,
        timeout: 5000
      });
      
      // 请求拦截器
      service.interceptors.request.use(
        (config) => {
          // 可以添加 token 等逻辑
          return config;
        },
        (error) => {
          return Promise.reject(error);
        }
      );
      
      // 响应拦截器
      service.interceptors.response.use(
        (response) => {
          return response.data;
        },
        (error) => {
          return Promise.reject(error);
        }
      );
      
      export default service;

第六步:创建主应用 (apps/main)

主应用是一个标准的 Vite 应用,但它的依赖也将由根目录提供。

  1. 创建目录并初始化 我们使用 --template,但之后会修改依赖。

    bash 复制代码
    cd ../../ && mkdir -p apps/main && cd apps/main
    pnpm create vite@latest . --template vue-ts
    rm -rf node_modules pnpm-lock.yaml

    执行后,根据提示操作,然后删除 apps/main 目录下的 node_modules 和 pnpm-lock.yaml 文件,因为我们将使用根目录的依赖。

  2. 修改 package.json 删除 dependenciesdevDependencies 下的所有依赖,然后根据需要重新声明它们。pnpm 会自动从根目录链接。

    json 复制代码
    {
      "name": "@your-org/main",
      "private": true,
      "version": "0.0.0",
      "type": "module",
      "scripts": {
        "dev": "vite",
        "build": "vue-tsc && vite build",
        "lint": "eslint . --ext .vue,.js,.ts",
        "preview": "vite preview"
      },
      "dependencies": {
        "@element-plus/icons-vue": "^2.3.1",
        "@your-org/components": "workspace:^*",
        "@your-org/hooks": "workspace:^*",
        "@your-org/utils": "workspace:^*",
        "@your-org/request": "workspace:^*",
        "element-plus": "^2.7.2",
        "pinia": "^2.1.7",
        "qiankun": "^2.10.16",
        "vue": "^3.4.21",
        "vue-router": "^4.3.0"
      },
      "devDependencies": {
        "@vitejs/plugin-vue": "^5.0.4",
        "@vitejs/plugin-vue-jsx": "^3.1.0",
        "vite": "^5.2.11",
        "vite-plugin-qiankun": "^1.0.11",
        "vue-tsc": "^1.8.27"
      }
    }

    注意 : "@your-org/utils": "workspace:^*" 是引用工作区内部包的标准方式。

  3. 修改 vite.config.ts 主应用作为 qiankun 容器,需要配置 vite-plugin-qiankun。

    typescript 复制代码
    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import vueJsx from '@vitejs/plugin-vue-jsx'
    import qiankun from 'vite-plugin-qiankun'
    import path from 'path'
    
    export default defineConfig({
      plugins: [
        vue(),
        vueJsx(),
        qiankun('main-app', { useDevMode: true })
      ],
      resolve: {
        alias: {
          '@': path.resolve(__dirname, 'src')
        },
        preserveSymlinks: true,
        modules: [
          path.resolve(__dirname, 'node_modules'),
          path.resolve(__dirname, '../../node_modules')
        ]
      },
      server: {
        port: 8080,
        open: true,
        cors: true
      }
    })
  4. 修改 tsconfig.json 继承根目录的配置

    json 复制代码
    {
      "extends": "../../tsconfig.json",
      "compilerOptions": {
        "composite": true
         "baseUrl": ".", // 基础路径
         "paths": {
           "@/*": ["src/*"] // 映射 @/ 到 src/
         }
      },
      "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
    }
  5. 编写主应用代码

    • src/router/index.ts

      typescript 复制代码
      import { createRouter, createWebHistory } from 'vue-router'
      import Home from '@/views/Home/index.vue'
      import MicroApp from '@/views/MicroApp/index.vue'
      
      const routes = [
        { path: '/', component: Home },
        { path: '/app1', component: MicroApp }
      ]
      
      const router = createRouter({
        history: createWebHistory(),
        routes
      })
      
      export default router
    • src/main.ts

      typescript 复制代码
      import { createApp } from 'vue'
      import App from './App.vue'
      import router from './router'
      import { createPinia } from 'pinia'
      import ElementPlus from 'element-plus'
      import 'element-plus/dist/index.css'
      import * as ElementPlusIconsVue from '@element-plus/icons-vue'
      import { CommonButton } from '@your-org/components'
      
      const app = createApp(App)
      
      for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
        app.component(key, component)
      }
      
      app.component('CommonButton', CommonButton)
      app.use(createPinia())
      app.use(router)
      app.use(ElementPlus)
      
      app.mount('#app')
      
      // 暴露全局变量给微应用
      (window as any).Vue = app.config.globalProperties.constructor
      (window as any).ElementPlus = ElementPlus
      (window as any).Pinia = app.config.globalProperties.$pinia
      (window as any).VueRouter = router
    • src/views/MicroApp/index.vue

      vue 复制代码
      <template>
        <div id="micro-app-container"></div>
      </template>
      
      <script setup lang="ts">
      import { onMounted, onUnmounted } from 'vue';
      import { loadMicroApp, unmountMicroApp } from 'qiankun';
      
      let microApp: any = null;
      
      onMounted(() => {
        microApp = loadMicroApp({
          name: 'app1',
          entry: '//localhost:8081',
          container: '#micro-app-container',
          props: { token: 'main-app-token' }
        })
      });
      
      onUnmounted(() => {
        unmountMicroApp('app1');
      });
      </script>

第七步:创建微应用 (apps/app1)

微应用的配置与主应用类似,但有一个关键区别:当它被 qiankun 加载时,需要通过 externals 排除已由主应用提供的依赖

  1. 创建目录并初始化 同样,删除自动生成的 node_modulespnpm-lock.yaml

    bash 复制代码
    cd ../../ && mkdir -p apps/app1 && cd apps/app1
    pnpm create vite@latest . --template vue-ts
    rm -rf node_modules pnpm-lock.yaml
  2. 修改 package.json 与主应用类似,声明依赖

    json 复制代码
    {
      "name": "@your-org/app1",
      "private": true,
      "version": "0.0.0",
      "type": "module",
      "scripts": {
        "dev": "vite",
        "dev:qiankun": "vite --mode qiankun",
        "build": "vue-tsc && vite build",
        "build:qiankun": "vue-tsc && vite build --mode qiankun",
        "lint": "eslint . --ext .vue,.js,.ts",
        "preview": "vite preview"
      },
      "dependencies": {
        "@element-plus/icons-vue": "^2.3.1",
        "@your-org/components": "workspace:^*",
        "@your-org/hooks": "workspace:^*",
        "@your-org/utils": "workspace:^*",
        "@your-org/request": "workspace:^*",
        "element-plus": "^2.7.2",
        "pinia": "^2.1.7",
        "vue": "^3.4.21",
        "vue-router": "^4.3.0"
      },
      "devDependencies": {
        "@vitejs/plugin-vue": "^5.0.4",
        "@vitejs/plugin-vue-jsx": "^3.1.0",
        "vite": "^5.2.11",
        "vite-plugin-qiankun": "^1.0.11",
        "vue-tsc": "^1.8.27"
      }
    }
  3. 修改 vite.config.ts 这里是核心,我们需要根据运行模式来动态配置 build.rollupOptions.externals

    typescript 复制代码
    import { defineConfig, loadEnv } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import vueJsx from '@vitejs/plugin-vue-jsx'
    import qiankun from 'vite-plugin-qiankun'
    import path from 'path'
    
    export default defineConfig(({ mode }) => {
      const env = loadEnv(mode, process.cwd())
      const isQiankunMode = env.VITE_QIANKUN === 'true'
    
      return {
        plugins: [
          vue(),
          vueJsx(),
          qiankun('app1', { useDevMode: !isQiankunMode })
        ],
        resolve: {
          alias: {
            '@': path.resolve(__dirname, 'src')
          },
          preserveSymlinks: true,
          modules: [
            path.resolve(__dirname, 'node_modules'),
            path.resolve(__dirname, '../../node_modules')
          ]
        },
        server: {
          port: 8081,
          open: true,
          cors: true
        },
        base: isQiankunMode ? '/app1/' : '/',
        build: {
          rollupOptions: {
            external: isQiankunMode ? [
              'vue', 'vue-router', 'pinia', 'element-plus', 'axios',
              '@element-plus/icons-vue', '@your-org/utils', '@your-org/hooks', '@your-org/components', '@your-org/request'
            ] : [],
            output: {
              globals: isQiankunMode ? {
                vue: 'Vue',
                'vue-router': 'VueRouter',
                pinia: 'Pinia',
                'element-plus': 'ElementPlus',
                axios: 'axios',
                '@element-plus/icons-vue': 'ElementPlusIconsVue',
                '@your-org/utils': 'YourOrgUtils',
                '@your-org/hooks': 'YourOrgHooks',
                '@your-org/components': 'YourOrgComponents',
                '@your-org/request': 'YourOrgRequest'
              } : {}
            }
          }
        }
      }
    })
  4. 创建 apps/app1/.env.qiankun 创建 apps/app1/.env.qiankun 文件,用于 qiankun 模式

    bash 复制代码
    cat > .env.qiankun << EOF
    NODE_ENV=development
    VITE_QIANKUN=true
    EOF
  5. 修改 tsconfig.json 同样继承根目录配置

    json 复制代码
    {
      "extends": "../../tsconfig.json",
      "compilerOptions": {
        "composite": true,
        "baseUrl": ".", // 基础路径
         "paths": {
           "@/*": ["src/*"] // 映射 @/ 到 src/
         }
      },
      "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
    }
  6. 编写微应用代码 微应用的入口文件 src/main.ts 需要遵循 qiankun 的协议,导出 bootstrap, mount, unmount 三个生命周期函数。

    • src/router/index.ts

      typescript 复制代码
      import { createRouter, createWebHistory } from 'vue-router'
      import Home from '@/views/Home/index.vue'
      import List from '@/views/List/index.vue'
      
      const routes = [
        { path: '/', redirect: '/home' },
        { path: '/home', component: Home },
        { path: '/list', component: List }
      ]
      
      const router = createRouter({
        history: createWebHistory(import.meta.env.BASE_URL),
        routes
      })
      
      export default router
    • src/main.ts

      typescript 复制代码
      import { createApp } from 'vue'
      import App from './App.vue'
      import router from './router'
      import { createPinia } from 'pinia'
      import ElementPlus from 'element-plus'
      import 'element-plus/dist/index.css'
      import * as ElementPlusIconsVue from '@element-plus/icons-vue'
      import { CommonButton } from '@your-org/components'
      import request from '@your-org/request'
      
      let app: any = null
      
      function render(props: any = {}) {
        const { container } = props
        app = createApp(App)
      
        for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
          app.component(key, component)
        }
      
        app.config.globalProperties.$request = request
        app.component('CommonButton', CommonButton)
        app.use(createPinia())
        app.use(router)
        app.use(ElementPlus)
      
        app.mount(container ? container.querySelector('#app') : '#app')
      }
      
      if (!(window as any).__POWERED_BY_QIANKUN__) {
        render()
      }
      
      export async function bootstrap() {
        console.log('微应用 app1 启动')
      }
      
      export async function mount(props: any) {
        console.log('微应用 app1 挂载', props)
        render(props)
      }
      
      export async function unmount() {
        console.log('微应用 app1 卸载')
        app.unmount()
        app = null
      }

    重要 : 主应用需要在全局 (window) 上挂载微应用 externals 中声明的那些库,例如 window.Vue = Vue,这样微应用在运行时才能找到它们。这部分逻辑通常放在主应用的 src/main.ts 中。


第八步:配置主应用和微应用的路由

  • 主应用路由:负责加载微应用(如 /app1 路径)。
  • 微应用路由:内部路由相对独立(如 /home/list),会被 qiankun 自动拼接为 /app1/home/app1/list

第九步:测试和运行

  1. 安装所有依赖

    bash 复制代码
    cd ../../..
    pnpm install
  2. 安装所有依赖 这会根据所有 package.json 的声明,从根目录统一安装和链接。

    bash 复制代码
    pnpm install
  3. 启动项目

    bash 复制代码
    # 同时启动所有应用
    pnpm dev
    
    # 或者单独启动
    pnpm --filter @your-org/main dev
    pnpm --filter @your-org/app1 dev:qiankun
  4. 访问地址


第十步. 构建和部署

  1. 构建项目

    bash 复制代码
    pnpm build
  2. 部署配置(Nginx 示例)

    nginx 复制代码
    server {
      listen 80;
      server_name your-domain.com;
      root /path/to/vue3-monorepo-qiankun/apps/main/dist;
    
      location / {
        try_files $uri $uri/ /index.html;
      }
    
      location /app1 {
        alias /path/to/vue3-monorepo-qiankun/apps/app1/dist;
        try_files $uri $uri/ /app1/index.html;
      }
    }

总结

通过以上步骤,你已经成功搭建了一个完整的 Vue 3 + Vite + Monorepo + Qiankun 微前端项目,包括公共工具库、Hooks 库、UI 组件库和请求封装。所有公共依赖都提取到了根目录进行统一管理,实现了代码复用和版本统一。

相关推荐
倚栏听风雨37 分钟前
详解 TypeScript 中,async 和 await
前端
小皮虾1 小时前
告别服务器!小程序纯前端“图片转 PDF”工具,隐私安全又高效
前端·javascript·微信小程序
ohyeah1 小时前
我的变量去哪了?JS 作用域入门指南
前端·javascript
倚栏听风雨1 小时前
TypeScript 中,Promise
前端
影i1 小时前
Vue 3 踩坑实录:如何优雅地把“上古”第三方插件关进 Iframe 小黑屋
前端
小明记账簿_微信小程序1 小时前
vue项目中使用echarts做词云图
前端
浪浪山_大橙子1 小时前
Trae SOLO 生成 TensorFlow.js 手势抓取物品太牛了 程序员可以退下了
前端·javascript
出征1 小时前
Pnpm的进化进程
前端
屿小夏1 小时前
openGauss020-openGauss 向量数据库深度解析:从存储到AI的全栈优化
前端