偷天换日:让PC项目秒变移动端的魔法

160 阅读3分钟

当PC端项目需要迁移到移动端时,你是否还在手动复制粘贴代码?今天分享一个我们团队自研的Vite插件,帮你实现跨仓库、跨技术栈的代码高效复用!

背景:从PC到移动端的迁移之痛

最近我们团队遇到了一个典型的企业级需求:将一个成熟的PC端医疗管理系统(MDM)迁移到移动端。听起来简单,但实际上却面临诸多挑战:

面临的挑战

  1. 技术栈差异:PC端使用Element Plus,移动端需要Vant
  2. 仓库隔离:PC端和移动端在不同的Git仓库
  3. 代码复用:希望复用80%的业务逻辑,但UI组件完全不同
  4. 维护同步:业务逻辑更新需要在两端同步

传统的解决方案要么是完全重写(成本高),要么是复制粘贴(维护噩梦),或者是Monorepo (代码和依赖放一起)。我们需要的是一种既能复用核心逻辑,代码在不同仓库维护,又能灵活定制UI的解决方案。

解决方案:vite-plugin-code-reuse

基于这个需求,我们开发了 vite-plugin-code-reuse 插件,它的核心思想是:多仓库代码复用、智能替换、无缝集成

插件核心能力

// 插件的核心配置结构
interface PluginConfig {
  repositories: RepositoryConfig[];     // 代码仓库配置
  repoImportMappings: RepoImportMappings[]; // 路径映射,主要是规避不同仓库路径别名冲突问题
  importReplacements: ImportReplacementConfig[]; // 导入替换
}

实战案例:医疗管理系统迁移

让我通过实际案例展示这个插件的效果。

项目背景

  • PC端项目xlian-web-mdm(Vue 3 + Element Plus + TypeScript)
  • 移动端项目:新建的H5项目(Vue 3 + Vant + TypeScript)
  • 目标:复用PC端80%的业务逻辑,100%替换UI组件

第一步:安装配置插件

npm install vite-plugin-code-reuse --save-dev
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import codeReusePlugin from 'vite-plugin-code-reuse'
import path from 'path'

export default defineConfig({
  plugins: [
    vue(),
    codeReusePlugin({
      // 1. 引入PC端代码仓库
      repositories: [
        {
          url: 'http://gitlab.xinglianhealth.com/web/mdm/xlian-web-mdm.git',
          branch: 'master',
          dir: 'mdm-pc'  // 本地存放目录
        }
      ],
      
      // 2. 修正路径别名映射,pc和移动都配置了“@”别名,需要各自引用正确的路径
      repoImportMappings: [
        {
          alias: '@',
          repoDir: 'mdm-pc',
          // PC端中的“@”别名还是引用pc端目录下的代码,但是需要排除路由、状态管理等路径,引用移动端的,因为路由和store不能存在两个
          ignorePatterns: ['src/router', 'src/store', 'src/axios']
        }
      ],
      
      // 3. 智能导入替换,pc端的部分组件无法复用时,可以做替换
      importReplacements: [
        // 场景1:组件替换(Element Plus → Vant)
        {
          pattern: /\/views\/MasterData\/components\/UploadFile\.vue$/,
          file: 'src/components/MbUpload.vue' // 移动端专用上传组件
        },
        {
          pattern: /\/views\/MasterData\/components\/TextInput\.vue$/,
          file: 'src/components/MbTextInput.vue' // 移动端输入框
        },
        
        // 场景2:复杂表单组件替换
        {
          pattern: /\/views\/MasterData\/components\/MasterForm\.vue$/,
          file: 'src/components/MbForm.vue' // 移动端表单封装
        },
        
        // 场景3:PC端特有功能在移动端隐藏
        {
          pattern: /\/views\/MasterData\/components\/HeaderFilter\/HeaderFilter\.vue$/,
          code: '<template></template>' // 移动端不需要复杂的表头筛选
        },
        
        // 场景4:UI库替换
        {
          pattern: /^@?element-plus/,
          resolve: path.resolve('src/components/ElementPlus.ts') // 重定向到兼容层
        }
      ]
    })
  ]
})

第二步:UI组件替换

// src/components/ElementPlus.ts - UI库兼容层
import { Button, Input, Select } from './ui-adaptor'

// 创建Element Plus到Vant的映射
export {
  ElButton: Button,
  ElInput: Input,
  ElSelect: Select,
  // ... 更多映射
}

// src/components/ui-adaptor.ts - UI库适配层,以Button组件为例

<template><van-button v-bind="$attrs" /></template>
<script>
const props = defineProps({
// 对element-plus Button Props做适配,比如size不支持"medium",可以处理成其他的,相同的属性不用处理
})
</script>

第三步:复用业务逻辑组件

<!-- src/views/MasterData/PatientList.vue -->
<template>
  <!-- 复用PC端的模板结构,但使用移动端组件 -->
  <div class="patient-list">
    <!-- 搜索框(PC端是ElInput,自动替换为Vant Field) -->
    <van-field 
      v-model="searchKey"
      placeholder="搜索患者"
      @input="handleSearch"
    />
    
    <!-- 患者列表 -->
    <van-list 
      v-model:loading="loading"
      :finished="finished"
      @load="loadPatients"
    >
      <patient-item 
        v-for="patient in patients"
        :key="patient.id"
        :patient="patient"
        @click="handlePatientClick"
      />
    </van-list>
  </div>
</template>

<script setup lang="ts">
// 这里直接复用PC端的业务逻辑!
import { usePatientList } from '@/mdm-pc/src/views/MasterData/composables/usePatientList'

// 完全复用PC端的逻辑,包括:
// 1. 数据获取逻辑
// 2. 搜索过滤逻辑  
// 3. 分页逻辑
// 4. 事件处理逻辑
const {
  patients,
  loading,
  finished,
  searchKey,
  loadPatients,
  handleSearch,
  handlePatientClick
} = usePatientList()
</script>

<style scoped>
/* 移动端特有的样式 */
.patient-list {
  padding: 12px;
}
</style>

插件的核心实现原理

1. 代码仓库管理

vite build start开始时,自动拉取PC端仓库代码,如无更新不会重复拉取,默认只拉取一个commit,速度极快。

{
  url: 'http://gitlab.xinglianhealth.com/web/mdm/xlian-web-mdm.git', //仓库地址
  branch: 'master', // 仓库分支
  dir: 'mdm-pc'  // 本地存放目录
}

image.png


### 2. 智能导入替换,支持三种替换方式,任意选择

```typescript
interface ImportReplacementConfig {
    /**
     * 匹配模式(正则表达式字符串)
     */
    pattern: RegExp;
    /**
     * 替换方式:代码字符串
     */
    code?: string;
    /**
     * 替换方式:文件路径
     */
    file?: string;
    /**
     * 替换方式:重定向路径
     */
    resolve?: string;
}

3. 别名路径冲突映射修正,将被引用的仓库内部别名指向仓库内部,规避和外层别名的冲突

interface RepoImportMappings {
    /**
     * 路径别名
     */
    alias: string;
    /**
     * 冲突的仓库目录
     */
    repoDir: string;
    /**
     * 忽略列表(路径匹配时跳过),使用外出仓库别名解析
     */
    ignorePatterns?: string[];
}

实际效果对比

迁移前(传统方式)

├── 移动端项目
│   ├── src
│   │   ├── views
│   │   │   ├── PatientList.vue    ← 需要重写
│   │   │   ├── DoctorList.vue     ← 需要重写
│   │   │   └── ... (20+个页面)
│   │   └── components
│   │       ├── MbForm.vue         ← 需要重写
│   │       └── ... (50+个组件)
└── 工时:3-4人月

迁移后(使用插件)

├── 移动端项目
│   ├── mdm-pc/                    ← 自动引入的PC端代码
│   ├── src
│   │   ├── views
│   │   │   ├── PatientList.vue    ← 直接引用PC端PatientList组件,只对样式做适配覆盖
│   │   │   ├── DoctorList.vue     ← 直接引用PC端DoctorList组件,只对样式做适配覆
│   │   │   └── ... (复用80%)
│   │   └── components
│   │       ├── MbUploader.vue     ← 无法复用的组件,用移动端专用文件组件做替换
│   │       └── ... (只需写30%的组件)
└── 工时:1-1.5人月(节省60%+)

高级应用场景

场景1:多项目共享工具库

// 多个项目共享工具函数
codeReusePlugin({
  repositories: [
    {
      url: 'git@github.com:company/shared-utils.git',
      branch: 'main',
      dir: 'shared-utils'
    },
    ...
  ],
  importReplacements: [
    {
      pattern: /^@shared\/utils/,
      resolve: path.resolve('shared-utils/src')
    },
    ...
  ]
})

场景2:主题系统切换

// 根据环境切换主题
codeReusePlugin({
  importReplacements: [
    {
      pattern: /\/themes\/default/,
      file: process.env.NODE_ENV === 'mobile' 
        ? 'src/themes/mobile.vue'
        : 'src/themes/pc.vue'
    }
  ]
})

场景3:A/B测试版本

// A/B测试不同版本的组件
codeReusePlugin({
  importReplacements: [
    {
      pattern: /\/components\/CheckoutButton\.vue$/,
      file: Math.random() > 0.5 
        ? 'src/components/CheckoutButtonVariantA.vue'
        : 'src/components/CheckoutButtonVariantB.vue'
    }
  ]
})

总结

vite-plugin-code-reuse 插件为我们解决了跨项目代码复用的核心痛点:

🎯 核心价值

  1. 大幅提升开发效率:节省60%+的开发时间
  2. 保证代码一致性:业务逻辑完全一致,减少BUG
  3. 降低维护成本:一处修改,多处生效
  4. 灵活定制:UI层完全可定制,不影响核心逻辑

🚀 适用场景

  • PC端到移动端的项目迁移
  • 多项目共享组件库
  • 微前端架构中的模块复用
  • A/B测试和实验性功能

如果你也面临类似的多项目代码复用问题,不妨试试这个插件。项目已在GitHub开源,欢迎Star和贡献代码!

GitHub地址vite-plugin-code-reuse

让代码复用变得简单,让开发效率飞起来! 🚀