七爪源码:如何执行依赖分析并推断修改代码的影响

执行依赖分析并计算修改代码对在线页面的影响。

今天我们将讨论如何进行依赖分析,并统计修改后的代码对在线页面的影响。

您可能有多人合作开发项目的经验。在这个过程中,难免会修改一些常用的代码。如果项目工作的多人之间信息不完全同步,就会出现协调不一致的问题,最终可能导致在线问题。

我将用一个特定的场景来说明这个问题。学生 A 正在开发一个需求。他发现,在这个需求中,有一个组件可以分离成一个公共组件,既可以自己使用,也可以提供给其他同学,提高开发效率。所以学生 A 封装了公共组件 C。

B同学在开发需求的时候,发现C组件可以在需求中复用,所以直接使用C组件。至此,A同学和B同学的需求已经分别上线。经过QA同学的测试,没有问题。突然有一天,A同学发现要调整组件,增加了一个参数。他负责的页面也进行了改编,自测没有问题,就交给QA测试A同学的页面,上线没有问题。刚上线,B同学的页面就出现了问题。原因是A同学修改的公共组件C没有和B同学同步,B同学的页面没有适配,导致上线问题。

我这里用一张图来说明这个问题:

至此,相信大家应该明白B同学页面出事的原因了,就是A同学修改了公开代码,而因为信息不同步,B同学不知道公开修改 组件,并没有进行适配,最终导致了上线事故。 那么如何使用技术手段来评估修改后的代码对页面的影响呢? 接下来,我将通过一个Demo来讲解。 现在有一个通用组件Common,分别被Page01和Page02引用。 代码如下:



// Common.js
export default function Common() {
  return <div> Common Component </div>
}
// Page01.js
import Common from './Common';
export default function Page01() {
  return <div> 
    <Common />
    Page01
  </div>
}
// Page02.js
import Common from './Common';
export default function Page02() {
  return <div> 
    <Common />
    Page02
  </div>
}

通过阅读代码应该明白,如果修改了Common组件,会同时影响Page01和Page02页面。如何通过技术手段分析Common组件的影响?在解决这个问题之前,让我们学习如何分析组件的依赖关系。


如何分析依赖关系?

直观上,通过AST解析代码,找到模块的引用路径,然后在项目中分析路径关系,找到对应的文件,就可以找到import语句。接下来,递归查找所有模块依赖路径。如果我们从0开始开发这样的工具,要考虑的因素会很多,要做好也不是一件容易的事。所以,我们可以站在巨人的肩膀上,用 WebPack 做到这一点。

在 WebPack 的插件机制中,我们有很多编译好的生命周期可以使用。 ContextModuleFactory Hooks主要做模块解析的工作,其中afterResolve是模块解析的最后阶段,在这个阶段你可以得到一些依赖。核心代码如下:

module.exports = class DemoPlugin {
  constructor(options) {
  }
  apply(compiler) {
    compiler.hooks.normalModuleFactory.tap('DepAnalysisPlugin', (nmf) => {
      nmf.hooks.afterResolve.tap('DepAnalysisPlugin', (result) => {
          const resourceResolveData = result.createData.resourceResolveData || {};
          const curFile = resourceResolveData.path;
          const parentFile = resourceResolveData.context.issuer;
          // ...
        }
      )
    });
  }
}

每次解析一个依赖,在afterResolve中,我们可以存储一次依赖,等文件中的所有依赖都解析完后,我们就可以把所有的依赖放到一个map上。 以之前的Demo为例,最终的地图是这样的:

{
  path: 'src/App.js',
  deps: [
    {
      path: 'src/components/Page01.js',
      deps: [
        {
          path: 'src/components/Common.js',
          deps: []
        }
      ]
    },
    {
      path: 'src/components/Page02.js',
      deps: [
        {
          path: 'src/components/Common.js',
          deps: []
        }
      ]
    }
  ]
}

至此,依赖关系就搞清楚了,接下来我们就需要分析哪些页面受到了Common组件的影响。


如何建立页面和文件之间的链接?

建立页面和文件的链接就是建立路由和文件的链接,所以我们只要找到路由和文件的对应关系就可以了。 但是在实际的业务开发中,大家的写法和路由方式千差万别。 如果从代码分析的角度来解决这个问题,可能并不容易解决。 有没有更合理的解决方案?

答案是肯定的。 根据我多年的经验,路由和文件的关系需要一个规范来约束,对于公司级的项目更准确,更适用。 这里我们临时制定一个配置文件,格式如下代码所示:

const path = require('path'); 
exports.default = [
  {
    page: 'Page01',
    entry: path.join(__dirname, './src/components/Page01.js')
  },
  {
    page: 'Page02',
    entry: path.join(__dirname, './src/components/Page02.js')
  }
]

至此,我们已经确定了页面与入口文件的对应关系。

如何确定哪些页面受到修改文件的影响?

前面我们已经拿到了整个依赖关系图,结合定义的页面配置,我们可以将页面和依赖关系结合起来。 具体的组合算法可以通过遍历依赖图来实现。 您可以通过 DFS(深度优先搜索)或 BFS(广度优先搜索)算法来实现它。

如果现在修改Common组件,那么我们可以通过最终分析结果是page 01和page 02得到所有的影响范围,然后把影响范围交给QA同学进行函数回归,就可以避免我提到的 一开始。 修改公共依赖,QA不清楚影响范围,导致漏测,进而导致线上事故。


结论

通过编写自定义的 WebPack 插件,我们可以在 normalModuleFactory 的 afterResolve 生命周期中建立组件依赖,然后根据路由配置规范推导出常用组件的受影响页面范围,最后交给 QA 完成全面的功能回归。 这可以在很大程度上避免由于 QA 不知道新功能的影响而错过测试的风险。

感谢您的阅读。


关注七爪网,获取更多APP/小程序/网站源码资源!