前端代码覆盖率问题及总结(二)

不管修改环境还是全局插桩打包出来的文件都没有插桩成功

同时在调研新的前端项目接入的时候跟我反馈到:打包test环境后能够正常插桩(这个是现象,因为我们的配置 istanbul的插件只是在test环境下才生效),然后再重新打包pro环境的时候,打包出来的也仍然含有插桩的内容。

问题很诡异,于是我这边也重新做了个验证,先打了pro环境的包发现没有插桩,很正常。重新切换到test环境进行打包结果竟然没有插桩。这个就真的很奇怪了。我一度怀疑是package script设置环境变量的问题

"test": "cross-env BABEL_ENV=test webpack --config ./webpack.config/webpack.prod.istanbul.js --progress",

这个是我们的package.json的test打包的脚本

尝试了修改 BABEL_ENV=testNODE_ENV=test或者说BABEL_ENV=test webpack --config多加一个 BABEL_ENV=test && webpack --config
结果还是一样。

这个时候只能使出终极大招了,我们默认babelrc插件的配置是这样子的。

"env": {
    "test": {
      "plugins": ["istanbul"]
    }
}

直接去掉env的现在,让默认打包都带上插桩

{
  "presets": [
    "@babel/preset-react",
    ...
  ],
  "plugins": [
    "react-hot-loader/babel",
    "@babel/plugin-transform-runtime",
    "@babel/plugin-transform-modules-commonjs",
    "istanbul"
    ...

就不信还插桩不成功。
结果真的是没成功。这个真的是没理由了。

突然想起来在上边的文章里面我们有提到可以在istannbul的源码里面打个日志看看是否有进入到instabul中。

babel-plugin-istanbul

export default declare(api => {
  api.assertVersion(7)

  const shouldSkip = makeShouldSkip()

  const t = api.types
  return {
    visitor: {
      Program: {
        enter (path) {
          this.__dv__ = null
          this.nycConfig = findConfig(this.opts)
          const realPath = getRealpath(this.file.opts.filename)
          // 增加一个打印
          console.log('istanbul, ', this.file.opts.filename)
          if (shouldSkip(realPath, this.nycConfig)) {
            return
          }
          ....

还是之前打日志的地方,我们重新做了一次编译。结果可想而知,日志没有打印出来。

问题分析到这里了,就要考虑下这个可能是js loader在其中有问题了。
现在我们要回到webpack中去看下针对js/jsx等 loader处理的过程

{
    test: /\.tsx?$/,
    include: /src/,
    use: [{ loader: 'cache-loader' }, { loader: 'happypack/loader?id=babel' }, { loader: 'ts-loader' }]
},

我们看到的是这样子的内容,因为loader的处理过程是从右到左,所以依次是 ts-loader, babel-loader 再来是cache-loader.
问题来了,这里多了多了一个cache-loader的处理。

所以现在最大可疑的就是它了。 然后最后跟开发讨论以及尝试去掉cache-loader以后,发现插桩真的就成功了。

至于cache-loader的作用呢,它会将编译后的内容缓存在node_modules/.cache/loader-cache中,都是以json的形式存在,所以一旦文件没有变化,cache-loader可能就直接用上一次编译的结果使用了,这也是为什么我跟我的同事都出现那么诡异问题的愿意了,主要是开第一次是否插桩了。


更新于2020.06.29

预研新项目的时候又遇到问题了,这次主要卡壳的地方并不是打包插桩上,而是在插桩文件的替换上。

在上文的时候我们有提到过,我们会将浏览器请求的未插桩的js文件替换成插桩后的js文件。然后我们遇到了这样子的问题。

在这里插入图片描述

如上图所示,当我们请求base.xxx.js的文件后,会被重定向为base_istanbul.xxx.js
但是每个js的文件都是请求失败的,chrome的错误信息提示为: (failed)net::ERR_FAILED。 这个问题我们是完全没有预料到的,因为重定向这块基本没啥大的逻辑,同时这个js的重定向在另外一个项目上是完全没有问题的。

根据这个错误提示信息, google了一番。 What can cause Chrome to give an net::ERR_FAILED on cached content against a server on localhost? 网上的答案大致都说到了跨域的问题,这个时候我们才重新把目光放回到浏览器控制台的报错信息中

在这里插入图片描述

类似于这样子。那跨域的问题就比较好解决了,我们重新改了下覆盖率后台的接口,让它允许跨域,这个问题就解决了。

但是问题结束了吗? 还没有,还记得我们刚才说过了,在另外一个项目的时候 ,我们这块是不需要允许跨域也是可以的,为什么现在到了这个项目就有这个问题呢?

所以还是要看下这个项目首页的静态资源加载是怎么写的了。


我们看到了类似于这样子的内容,但是有个字段很陌生。 crossorigin="anonymous"光看这个名字 就感觉是我们要找的内容了。

所以我们重新百度科普了下这个字段。

HTML5 新的规定,是可以允许本地获取到跨域脚本的错误信息,但有两个条件:一是跨域脚本的服务器必须通过 Access-Controll-Allow-Origin 头信息允许当前域名可以获取错误信息,二是当前域名的 script 标签也必须指明 src 属性指定的地址是支持跨域的地址,也就是 crossorigin 属性。

crossorigin属性:

anonymous:如果使用这个值的话就会在请求中的header中的带上Origin属性,但请求不会带上cookie和其他的一些认证信息。

use-credentials:这个就同时会在跨域请求中带上cookie和其他的一些认证信息。
在使用这两个值时都需要server端在response的header中带上Access-Control-Allow-Credentials属性。
可以通过server的配置文件来开启这个属性:server开启Access-Control-Allow-Credentials

所以罪魁祸首实际上就是这个crossOrigin属性了。


更新于2020.07.20

前端代码覆盖率又接入了新的项目,但是这次的前端用到了一个新的框架 easywebpack, 不过好在官方的文档是相当的详尽,所以在babel升级基本没有遇到什么大的问题。

原以为一切的会比较顺利的进行, 但是测试的同事反馈说出现几个文件的覆盖率数据明显不正确,并且那几个文件的数据都是一样的。我们来看下结果数据


在这里插入图片描述

这里的四个覆盖率数据竟然是完全一样的,我们再详细看下某个文件的详细覆盖率数据

在这里插入图片描述

很明显 从11行的地方我们发现这里的代码与覆盖率的数据其实是完全对应不上的,我们在11行处根本就不存在有IF的代码逻辑。

那现在的问题来了为什么会出现这种情况呢? 首先我们的覆盖率数据是有经过多次合并的,所以首先我们可能要怀疑是我们的覆盖率数据合并导致的问题。那我们就先验证下原生的网页上的覆盖率数据是否有问题吧

在这里插入图片描述

这里是从浏览器的控制台打印出来的结果,从上述的一些数据我们可以看出来,在11的时候,确实有一个if的语句,所以说明了一个问题 就是原本的文件覆盖率数据就已经错误了。

那问题到底出在哪里呢?这里还是需要从打包的地方去分析这个问题了。

以下是webpack的部分截图

在这里插入图片描述

这里分别了客户端渲染以及服务端渲染的打包,所以我们要重点看下loader.js里面做了啥事情。

module.exports = function() {
  this.cacheable();
  return `
    import React from 'react';
    import ReactDom from 'react-dom';
    import { AppContainer } from 'react-hot-loader';
    import Entry from '${this.resourcePath.replace(/\\/g, '\\\\')}';
    const state = window.__INITIAL_STATE__;
    const render = (App)=>{
      ReactDom.hydrate(EASY_ENV_IS_DEV ?  : , document.getElementById('app'));
    };

    if (EASY_ENV_IS_DEV && module.hot) {
      module.hot.accept('${this.resourcePath.replace(/\\/g, '\\\\')}', () => { render(Entry) });
    }
    render(Entry);
  `;
};

针对要处理的js文件,看起来这里的loader用重新进行render了一次,而且如果细心一点 我们可能注意到一个很关键的因素,这里从return方法往下的第11行 刚好是 if (EASY_ENV_IS_DEV && module.hot) {一个if语句, 所以其实我们所有错误的js的覆盖率都被这段的逻辑所影响到了。那到底我们原本的jsx的文件到底有没有被插桩呢?所以还是需要看下插桩后的文件到底长什么样。

针对campCourse.jsx的打包文件

在这里插入图片描述

我们在6w多行的时候可以看到有这样的一个方法 针对的就是该文件的覆盖率数据

然后我们再观察

在这里插入图片描述

在11w多行的时候, 也出现了同样的覆盖率数据,而且该数据与我们从浏览器读到的数据是一致的也就是错误的数据。到这里应该就能有一定的总结的,也就是说源码文件其实是有被插桩的,只是说它的数据最终被另外一个相同对象给覆盖了导致我们拿到了错误的数据。

问题到了这里就要想着怎么解决了,其实istanbul提供另一些方式来过滤掉一些你不想插桩的代码。具体可以看 ignoring-code-for-coverage

所以我们需要做的是忽略到这个文件的插桩,不过这个ignore添加在哪里还是有一定的技巧的 我们来看下最后的改动代码吧。

module.exports = function() {
  this.cacheable();
  return `/* istanbul ignore file */
    import React from 'react';
    import ReactDom from 'react-dom';
    import { AppContainer } from 'react-hot-loader';
    import Entry from '${this.resourcePath.replace(/\\/g, '\\\\')}';
    const state = window.__INITIAL_STATE__;
    const render = (App)=>{
      ReactDom.hydrate(EASY_ENV_IS_DEV ?  : , document.getElementById('app'));
    };

    if (EASY_ENV_IS_DEV && module.hot) {
      module.hot.accept('${this.resourcePath.replace(/\\/g, '\\\\')}', () => { render(Entry) });
    }
    render(Entry);
  `;
};

我们需要将/* istanbul ignore file */的 内容添加在return 语句的后面,即第一行才行,如此下来的代码都不会被插桩处理了。

你可能感兴趣的:(前端代码覆盖率问题及总结(二))