[toc]

# 1. 开始

何为孪生项目?故名思义,就是两个项目长得很像,比如项目A和项目A+,项目A+从项目A衍生而来,二者有些功能差异,但差异度很小,低于10%。

除了业务上的这种场景,还有其他情况会产生孪生项目,比如开源项目,如果对内和对外的开源程度不同,或者功能有差异,也会形成孪生项目。

这里不去讨论产品层面的逻辑,也不去讨论拆分为两个项目是否合理,仅讨论如何更高效的开发此类的项目。

# 2. 刀耕火种

最原始的方式就是在项目A上开发后,将代码搬运到项目A+上,此种方式开发时十分痛苦,痛苦点如下:

  • 开发效率低,一模一样的代码需要写两遍
  • 存在遗漏风险,开发周期稍长时很可能发生
  • 存在错误风险,有可能粘贴错位置
  • 存在冲突风险,有可能与他人改动冲突

同步代码的频率也很高,包括以下场景:

  • 需求迭代
  • bug修复
  • 重构、优化代码

另外,业务自身就比较复杂,这种开发方式更加剧了项目的复杂性,不利于后期维护。

# 3. 工具化

既然原始方式弊端如此多,如何改进呢?

# 3.1. 思路及实现

可以用工具自动化的同步代码,这里的实现思路如下:

  • 条件编译解决差异部分
  • 以页面为单位,将每个差异部分控制在几行内,也就是封装组件、方法
  • 用脚本同步代码,从页面开始,递归分析依赖,一起同步

这里面有几个前提:

  1. 项目A和项目A+的差异点不能太多,如果异大于同,可以另起炉灶,用新路由,公共部分封装中间层
  2. 公共组件、方法需保证一致,包括:
    • 三方库,比如press-ui
    • common
    • component

对于 store、全局mixinlocal-component下的公共组件,比如header,也建议保持一致,如果确实无法保持一致,也可以解决,可以用工具ignore字段排除,或者让同步文件的粒度变细一点。

另外,这种工具是利用了uni-app扩展平台 (opens new window)的能力,国际化也是这么做的。

# 3.2. 使用方式

在项目根目录下增加文件light-cli.config.js,内容如下:

module.exports = {
  'sync-repo': {
    target: '../pro',
    ignore: [
      'src/common/**/*',
      'src/component/**/*',
    ],
    files: [
      'src/project/user/views/message-center/message-center-index.vue',
      'src/project/user/views/message-center/message-center-detail.vue',
    ],
  },
};

然后执行:

npx light-cli

注意files不一定是页面,如果想同步某个底层文件也可以,工具足够灵活,可以支持任意粒度的文件。

工具地址在这里 (opens new window)

# 3.3. 可能引起的问题

此外,使用扩展条件编译发现一个坑点,在三方库ts文件中用条件编译,包裹import语句时,在小程序平台,顶部的#ifdef会被去掉。

比如下面这段代码,#ifdef H5会被去掉,导致编译出错。

// handler.ts
import Vue from 'vue';
import { dialogProps } from './computed';

// #ifdef H5
import VueDialog from './press-dialog.vue';
// #endif

# 4. 效果

使用这种方式可以大大提高生产力,提升开发效率。并且,工具比人更能减少出错的可能,减少bug。

更重要的是,关注点会更加聚焦,只关心“主”项目,“从”项目只需要看下每次脚本跑的差异即可,这里主从关系并不是固定的,二者可互换。