git如何实现增量上线 实现前端资源增量式更新的一种思路

git如何实现增量上线 实现前端资源增量式更新的一种思路

  之前校招面试的时候遇到过一个很有趣的问题:

  “假设有一个超超超大型的Web项目,JS源代码压缩之后超过10MB(实际怎么可能会有这么大的嘛=。=),每次更新之后都需要用户重新加载JS是不可接受的,那么怎么样从工程的角度解决这种问题?”

  一开始立马想到了好几点解决方案,比如:

  1. 抽出基础的不常更新的模块作为长期缓存;

  2. 如果使用了 React 或者 Vue2.0 这样支持服务器端渲染的框架的话,就采用服务器端渲染然后再逐步分块加载 JS 的方法;

  3. 如果是 Hybrid 开发的话,可以考虑使用本地资源加载,类似“离线包”的想法(之前在腾讯实习的时候天天遇到这东西)。

  后来在面试官的引导下想到了一种“增量式更新”的解决方案,简单地说就是在版本更新的时候不需要重新加载资源,只需要加载一段很小的 diff 信息,然后合并到当前资源上,类似 git merge 的效果。

  1、用户端使用 LocalStorage 或者其它储存方案,存储一份原始代码+时间戳:

  {

  timeStamp: "20161026xxxxxx",

  data: "aaabbbccc"

  }

  2、每次加载资源的时候向服务器发送这个时间戳;

  3、服务器从接受到时间戳中识别出客户端的版本,和最新的版本做一次 diff,返回两者的 diff 信息:

  diff("aaabbbccc","aaagggccc");

  // 假设我们的diff信息这样表示:

  // [3, "-3", "+ggg", 3]

  4、客户端接收到这个 diff 信息之后,把本地资源和时间戳更新到最新,实现一次增量更新:

  mergeDiff("aaabbbccc",[3,"-3","+ggg",3]);

  //=> "aaagggccc"

  二、实践

  下面把这个方案中的核心思想实现一遍,简单地说就是实现 diff和 mergeDiff两个函数。

  今天找到了一个不错的 diff 算法:

  GitHub – kpdecker/jsdiff: A java text differencing implementation.

  我们只需要调用它的 diffChars方法来对比两个字符串的差异:

  varoldStr= 'aaabbbccc';

  varnewStr= 'aaagggccc';

  JsDiff.diffChars(oldStr,newStr);

  //=>

  //[ { count: 3, value: 'aaa' },

  // { count: 3, added: undefined, removed: true, value: 'bbb' },

  // { count: 3, added: true, removed: undefined, value: 'ggg' },

  // { count: 3, value: 'ccc' } ]

  上面的 diff 信息略有些冗余,我们可以自定义一种更简洁的表示方法来加速传输的速度:

  [3,"-3","+ggg",3]

  整数代表无变化的字符数量,“-”开头的字符串代表被移除的字符数量,“+”开头的字符串代表新加入的字符。所以我们可以写一个 minimizeDiffInfo 函数:

  functionminimizeDiffInfo(originalInfo){

  varresult= originalInfo.map(info=> {

  if(info.added){

  return'+'+ info.value;

  }

  if(info.removed){

  return'-'+ info.count;

  }

  returninfo.count;

  });

  returnJSON.stringify(result);

  }

  vardiffInfo= [

  {count: 3,value: 'aaa'},

  {count: 3,added: undefined,removed: true,value: 'bbb'},

  {count: 3,added: true,removed: undefined,value: 'ggg'},

  {count: 3,value: 'ccc'}

  ];

  minimizeDiffInfo(diffInfo);

  //=> '[3, "-3", "+ggg", 3]'

  用户端接受到精简之后的 diff 信息,生成最新的资源:

  mergeDiff('aaabbbccc','[3, "-3", "+ggg", 3]');

  //=> 'aaagggccc'

  functionmergeDiff(oldString,diffInfo){

  varnewString= '';

  vardiffInfo= JSON.parse(diffInfo);

  varp= 0;

  for(vari= 0;i< diffInfo.length;i++){

  varinfo= diffInfo[i];

  if(typeof(info)== 'number'){

  newString+= oldString.slice(p,p+ info);

  p+= info;

  continue;

  }

  if(typeof(info)== 'string'){

  if(info[0]=== '+'){

  varaddedString= info.slice(1,info.length);

  newString+= addedString;

  }

  if(info[0]=== '-'){

  varremovedCount= parseInt(info.slice(1,info.length));

  p+= removedCount;

  }

  }

  }

  returnnewString;

  }

  三、实际效果

  有兴趣的话可以直接运行这个:

  GitHub – starkwang/Incremental

  使用 create-react-app这个小工具快速生成了一个 React 项目,随便改了两行代码,然后对比了一下build之后的新旧两个版本:

  varJsDiff= require('diff');

  varfs= require('fs');

  varnewFile= fs.readFileSync('a.js','utf-8');

  varoldFile= fs.readFileSync('b.js','utf-8');

  console.log('New File Length: ',newFile.length);

  console.log('Old File Length: ',oldFile.length);

  vardiffInfo= getDiffInfo(JsDiff.diffChars(oldFile,newFile));

  console.log('diffInfo Length: ',diffInfo.length);

  console.log(diffInfo);

  varresult= mergeDiff(oldFile,diffInfo);

  console.log(result=== newFile);

  下面是结果:

  

  可以看到 build 之后的代码有 21w 多个字符(212KB),而 diff 信息却相当短小,只有151个字符,相比于重新加载新版本,缩小了1000多倍(当然我这里只改了两三行代码,小是自然的)。

  四、一些没涉及到的问题

  上面只是把核心的思路实现了一遍,实际工程中还有更多要考虑的东西:

  1、服务器不可能对每一次请求都重新计算一次 diff,所以必然要对 diff 信息做缓存;

  2、用户端持久化储存的实现方案,比如喜闻乐见的 LocalStorage、Indexed DB、Web SQL,或者使用 native app 提供的接口;

  3、容错、用户端和服务器端的一致性校对、强制刷新的实现。


阅读更多
换一批

没有更多推荐了,返回首页