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

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

同时在调研新的前端项目接入的时候跟我反馈到:打包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? 网上的答案大致都说到了跨域的问题,这个时候我们才重新把目光放回到浏览器控制台的报错信息中

在这里插入图片描述

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

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

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

<script type="text/javascript" src="https://xxxx.xxx.com/xxx-web-live/statics/redux.dca712d2fa02c53d36087cd0ffe6917e.js" crossorigin="anonymous"></script>

我们看到了类似于这样子的内容,但是有个字段很陌生。 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 ? <AppContainer><App {...state} /></AppContainer> : <App {...state} />, 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 ? <AppContainer><App {...state} /></AppContainer> : <App {...state} />, 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 语句的后面,即第一行才行,如此下来的代码都不会被插桩处理了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容