携程CRN源码详解之拆包(三)——增量编译

1、增量编译

    增量编译在这里指的是:打过一次js包后,后面再打包就会基于上次的打包基础打包。这里的基础指的是moduleId的map。crn-cli使用的是数字来代表module,rn_common包生成的文件都是0.js 1.js 2.js 3.js.... 业务包生成的文件都是666666.js 666667.js ..... ,而这些数字在rn中并不是固定的,比如在一次打包中rn_common包的288.js文件代表的是Button.js,这个过程中我们在RN框架代码中新增或者删除一个module,下次打包的时候rn_common包时288.js代表的是Alert.js。在业务包中是以id来require 模块的,这就会导致本来引用的是Button,却变成了Alert,这个时候就需要把所有的业务包全部重新打包,这对于携程有上百个业务包的情况是非常不友好的。增量编译能让之前已经打包的模块id保持不变,因此crn-cli必须支持增量编译。

  这里要吐槽以下,这个理论上将不能算增量编译,因为js代码本来就没有编译一说,应该叫做增量打包。我话讲完,

携程CRN源码详解之拆包(三)——增量编译_第1张图片

2、原理很简单

    如果需要增量打包,那就把每次打包新增的module和id对应关系保存到一个文件,下次打包的时候再根据这个文件取出id复用。

//baseMapping.json

[
  {
    "id": 0,
    "path": "/crn_common_entry.js"
  },
  {
    "id": 1,
    "path": "/node_modules/@babel/runtime/helpers/interopRequireWildcard.js"
  },
  {
    "id": 2,
    "path": "/node_modules/@babel/runtime/helpers/interopRequireDefault.js"
  },
  {
    "id": 3,
    "path": "/node_modules/@babel/runtime/helpers/classCallCheck.js"
  },
... ... ...

这里的文件就是rn-common包下的map文件,对应着模块路径和id

看看处理moduleId的源码

//crn-createModuleIdFactory.js
function createModuleIdFactory() {
  if(mappingContainer.length == 0){
    var baseMappingPath = path.join(process.cwd(), "bundle_output","baseMapping.json")
    if(fs.existsSync(baseMappingPath)){//1<--
      mappingContainer = require(baseMappingPath);
      nextModuleId.rn = mappingContainer[mappingContainer.length-1].id;
    }
  }
  if(mappingContainerBU.length == 0 && !global.CRN_BUILD_COMMON){
    var buMappingPath = path.join(process.cwd(), "bundle_output","buMapping.json")
    if(fs.existsSync(buMappingPath)){
      mappingContainerBU = require(buMappingPath);
      nextModuleId.business = mappingContainerBU[mappingContainerBU.length-1].id;
    }
  }

  return path => { 
    var oldModule = [];
    path = path.replace(process.cwd(), "");
    if (mappingContainer && mappingContainer.length > 0) {
      oldModule = mappingContainer.filter(element => {//2<--
        return path === element.path;
      });
    }

    var oldBUModule = [];
    if (mappingContainerBU && mappingContainerBU.length > 0) {
      oldBUModule = mappingContainerBU.filter(element => {
        return path === element.path;
      });
    }

    if (oldModule && oldModule.length > 0) {
      return oldModule[0].id;//3<--
    } else if (oldBUModule && oldBUModule.length > 0) {
      return oldBUModule[0].id;
    } else {
      var nID = generateId();//4<--
      var enterModule = {
        id: nID,
        path: path
      };

      if (!global.CRN_BUILD_COMMON) {
        if (... ...) {
          mappingContainerBU.push(enterModule);
        }
      } else {
        mappingContainer.push(enterModule);//5<--
      }

      return nID;
    }
  };
}

    这里个函数中一起处理common包和buz包的moduleId,两者逻辑一摸一样,就挑一个讲即可,我把步骤分成了5步:

1、读取map文件,并取出这个文件里最后的id(也是最大的ID),如果有新的模块那这个模块的id=maxId+1

2、根据传入的模块路径找id

3、如果在map文件中找到了id,就使用这个旧id

4、如果没找到就最大id+1

5、把这个模块id信息保存下来,打包完后会保存mappingContainer到map文件

3、携程的做法

    查看携程app打包后的业务包,许多业务包的ID都是666666开头的,而携程官方也说了90%共用一个js运行环境,业务包id重合也就意味着一个业务的模块在其他业务下无法被使用,id覆盖后之前的业务状态也可能会被覆盖,因此如果crn的业务包之间需要交互的情况下会变得有一点别扭。而且,如果在页面退出后业务代码还在后台运行的话,这种情况会非常危险,很可能爆出找不到module或找错module的情况。如果只是使用这个开源工程的话是没有业务包间协作的功能的,所以我猜测携程自己应该写了业务包交互的中间件。而如果使用react-native-multibundler开源项目就不会有这个问题,这个还是要看业务,如果业务之间不耦合交互也是可有无,去哪儿的QRN使用的是和react-native-multibundler一样的文件名当moduleId的做法,期待QRN的开源。

4、后续

   到现在已经把crn-cli了解了差不多一半了

  • 打包支持框架和业务代码拆分
  • 支持框架代码后台预加载
  • 打包支持增量编译(同一模块,两次打包模块ID不变)
  • iOS&Android统一一套打包产物
  • 首屏加载性能统计
  • LazyRequire

  

 

你可能感兴趣的:(react,naitve)