前端代碼覆蓋率遇到問(wèn)題及總結(jié)(一)

在講之前得說(shuō)下 前端覆蓋率的水真的是很深的扫皱,其實(shí)到目前為止還有很多未解之謎,由于對(duì)babel的編譯以及ast了解的不是很多叙凡。所以確實(shí)分析問(wèn)題起來(lái)很困難正驻。

前端代碼覆蓋率方案

關(guān)于前端代碼覆蓋率還不了解這塊內(nèi)容的同學(xué)們厌蔽,可以參考下一下幾篇文章牵辣,這里就不做贅述了。

基于 Istanbul 優(yōu)雅地搭建前端 JS 覆蓋率平臺(tái)

前端精準(zhǔn)測(cè)試探索:覆蓋率實(shí)時(shí)統(tǒng)計(jì)工具

了解了上述兩篇文章以后躺枕,你應(yīng)該對(duì)前端的代碼覆蓋率有一定的了解了服猪。那下來(lái)說(shuō)下具體的方案吧。下面就是我們前端代碼覆蓋率的具體方案了(PS: 畫(huà)的很潦草拐云,不太專業(yè)罢猪,大家請(qǐng)將就下)


在這里插入圖片描述

這里涉及到幾個(gè)關(guān)鍵的部分要說(shuō)明下:

  1. react/vue 項(xiàng)目打包插樁:

    由于我們公司的項(xiàng)目發(fā)布的流程是直接使用測(cè)試通過(guò)的鏡像直接上線到正式環(huán)境去,所以如果在測(cè)試環(huán)境部署的代碼直接是打包后插樁的內(nèi)容叉瘩,然后再上到正式環(huán)境話這個(gè)是個(gè)很糟糕的事情膳帕。 所以我們做了一個(gè)處理,前端在編譯打包的時(shí)候需要打出兩份內(nèi)容: 一個(gè)是插樁后的js文件(用于測(cè)試驗(yàn)證)薇缅,另外一個(gè)是未插樁的js文件(用于正式上線)危彩。插樁后的js會(huì)上傳到cdn或者說(shuō)我們自己的一個(gè)私有服務(wù)上做保留。

  2. chrome插件:

    chrome插件在這里起到了兩個(gè)作用泳桦。

    • 將我們的原本的為插樁的js文件請(qǐng)求替換成插樁后的js文件汤徽。
    • 進(jìn)行注入定時(shí)上報(bào)覆蓋率的數(shù)據(jù)的js腳本。

    不過(guò)目前 chrome插件這個(gè)方案可能要被我們棄用掉了灸撰,因?yàn)閏hrome插件本身只能局限于chrome瀏覽器上谒府,而我們現(xiàn)在更多的會(huì)有一些h5頁(yè)面的情況,這些他就不能夠滿足浮毯,所以我們會(huì)將這部分的邏輯直接轉(zhuǎn)到fiddler中完疫,由fiddler來(lái)完成這塊的工作。這樣子就能滿足移動(dòng)端的測(cè)試覆蓋率的問(wèn)題了债蓝。

  3. 覆蓋率后臺(tái)(node)

    這塊的實(shí)現(xiàn)我們沒(méi)有直接使用 istanbul-middleware 這個(gè)方案壳鹤,因?yàn)閷?duì)于一個(gè)長(zhǎng)達(dá)5年沒(méi)有維護(hù)更新的項(xiàng)目,我還是持有一定的懷疑態(tài)度(當(dāng)然可能項(xiàng)目本身很優(yōu)秀饰迹,完全沒(méi)有問(wèn)題)芳誓。所以我們把目光放到了 nyc 上,不過(guò)nyc更多是結(jié)合單元測(cè)試框架:jest使用或者說(shuō)直接通過(guò)命令行的方式進(jìn)行調(diào)用啊鸭。沒(méi)有太多有涉及到如何使用它的api的方法上锹淌。幸運(yùn)的是我們又找到了另外一個(gè)項(xiàng)目 Cypress 的 code-coverage, 這里做個(gè)小廣告, cypress是一個(gè)很優(yōu)秀的前端自動(dòng)化工具。 在這個(gè)項(xiàng)目里你可以看到它就是通過(guò)調(diào)用nyc的api進(jìn)行生成覆蓋率的測(cè)試報(bào)告的莉掂。 所以這塊我們毫不猶豫的選擇了它做了一定的二次開(kāi)發(fā)了葛圃。

問(wèn)題

理想總是很美好的千扔,我們?cè)谝粋€(gè)簡(jiǎn)單的demo項(xiàng)目上實(shí)驗(yàn)了下憎妙,基本沒(méi)啥問(wèn)題库正。但是進(jìn)入到真正的項(xiàng)目的時(shí)候,發(fā)現(xiàn)真的是困難重重厘唾。

問(wèn)題1. babel的升級(jí)

這個(gè)問(wèn)題我發(fā)現(xiàn)在其他文章里面都很少提到褥符。因?yàn)槭褂胕stanbul(最新的版本)插樁的方案的話需要在babel7的版本進(jìn)行,所以需要前端的項(xiàng)目做升級(jí)才行抚垃,而我們大部分的前端的項(xiàng)目都是停留在babel6的版本喷楣,這個(gè)升級(jí)過(guò)程就非常的痛苦,尤其前端的項(xiàng)目又用不到了各種的腳手架鹤树。(不過(guò)痛著痛著就習(xí)慣了铣焊,經(jīng)歷過(guò)幾次項(xiàng)目的babel版本升級(jí),基本上遇到的問(wèn)題也就那幾個(gè)罕伯,google基本都能夠幫忙解決了)以下就附上babel升級(jí)要做的一些修改

Babel 6 Babel 7
babel-core @babel/core
babel-plugin-transform-class-properties @babel/plugin-proposal-class-properties
babel-plugin-transform-object-rest-spread @babel/plugin-proposal-object-rest-spread
babel-plugin-syntax-dynamic-import @babel/plugin-syntax-dynamic-import
babel-plugin-transform-object-assign @babel/plugin-transform-object-assign
babel-plugin-transform-runtime @babel/plugin-transform-runtime
babel-plugin-transform-decorators-legacy @babel/plugin-proposal-decorators
babel-preset-env @babel/preset-env
babel-preset-react @babel/preset-react
babel-loader@7 babel-loader@8

當(dāng)然還有babelrc文件的修改等等曲伊,這里就不說(shuō)了。

問(wèn)題2. istanbul與babel-plugin-import 沖突

babel-plugin-import是一個(gè)antd ui庫(kù)的按需加載的插件, 因?yàn)閍ntd的使用非常的廣泛, 基本上我們的前端項(xiàng)目都會(huì)使用到這個(gè)ui庫(kù), 所以注定這個(gè)問(wèn)題會(huì)遇到了追他。問(wèn)題如下圖所示

image

相關(guān)的問(wèn)題在istanbul issue中也可以找到 Does not work with babel-plugin-import 文中提到的解決方案有兩種:

1.直接修改babel-plugin-import的源碼坟募。

2.修改自己引用ui庫(kù)的方式。

上述兩種都比較麻煩邑狸,然而我們?cè)跈C(jī)緣巧合下發(fā)現(xiàn) 可以通過(guò)在babelrc中引入 @babel/plugin-transform-modules-commonjs 也可以解決這個(gè)問(wèn)題懈糯。不過(guò)原因暫時(shí)還不清楚(前端的打包真的太深?yuàn)W了)

可以看下 基于 Istanbul 優(yōu)雅地搭建前端 JS 覆蓋率平臺(tái) 評(píng)論區(qū)的內(nèi)容

PS: 以下部分涉及到真正去實(shí)踐過(guò)程的問(wèn)題分析,如果沒(méi)有動(dòng)手做過(guò)這塊內(nèi)容的同學(xué)可以忽略

問(wèn)題3. 為什么通過(guò)babel-loader + ts-loader 生成的覆蓋率數(shù)據(jù)(windows.coverage) 中帶有inputSouceMap,但是直接通過(guò)babel-loader 生成的覆蓋率數(shù)據(jù)就不帶有

我們先看下coverage數(shù)據(jù)的對(duì)比情況

ts-loader + babel-loader
在這里插入圖片描述
babel-loader
在這里插入圖片描述

首先針對(duì)這個(gè)問(wèn)題单雾,我們需要一步步的去看赚哗,我們首先要確定的一點(diǎn)是為什么babel-loader + ts-loader 的方式能夠出現(xiàn)inputSourceMap的內(nèi)容,而babel-loader卻沒(méi)有铁坎。 這兩者主要的差別實(shí)際上就是在多了一個(gè)ts-loader上蜂奸。所以我們首先的思路是去看下ts-loader這塊做了什么事情。

ts-loader

function makeSourceMapAndFinish(
  sourceMapText: string | undefined,
  outputText: string | undefined,
  filePath: string,
  contents: string,
  loaderContext: webpack.loader.LoaderContext,
  fileVersion: number,
  callback: webpack.loader.loaderCallback,
  instance: TSInstance
) {
  if (outputText === null || outputText === undefined) {
    setModuleMeta(loaderContext, instance, fileVersion);
    const additionalGuidance = isReferencedFile(instance, filePath)
      ? ' The most common cause for this is having errors when building referenced projects.'
      : !instance.loaderOptions.allowTsInNodeModules &&
        filePath.indexOf('node_modules') !== -1
      ? ' By default, ts-loader will not compile .ts files in node_modules.\n' +
        'You should not need to recompile .ts files there, but if you really want to, use the allowTsInNodeModules option.\n' +
        'See: https://github.com/Microsoft/TypeScript/issues/12358'
      : '';

    callback(
      new Error(
        `TypeScript emitted no output for ${filePath}.${additionalGuidance}`
      ),
      outputText,
      undefined
    );
    return;
  }

  const { sourceMap, output } = makeSourceMap(
    sourceMapText,
    outputText,
    filePath,
    contents,
    loaderContext
  );

  setModuleMeta(loaderContext, instance, fileVersion);
  callback(null, output, sourceMap);
}

這個(gè)地方是ts-loader最后處理后的回調(diào)硬萍,我們可以看到這里帶了一個(gè)sourceMap扩所。 那這個(gè)sourceMap到底是什么呢?我們嘗試用斷點(diǎn)去看看朴乖。


在這里插入圖片描述

這個(gè)確實(shí)就是我們?cè)赾overage數(shù)據(jù)里面看到的情況

所以順著這個(gè)流程 ts-loader講數(shù)據(jù)傳遞給到了babel-loader, babel-loader則將這個(gè)數(shù)據(jù)給到了istanbul祖屏。

既然講到了istanbul 我們來(lái)看下istanbul這塊是怎么去獲取inputSouceMap的吧。

babel-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)
          if (shouldSkip(realPath, this.nycConfig)) {
            return
          }
          let { inputSourceMap } = this.opts
          // 這里的條件可以看出來(lái) inputSouceMap是空并且 this.file.inputMap是有內(nèi)容的情況下 才會(huì)進(jìn)行相應(yīng)的InputSouceMap的賦值操作, 所以coverage數(shù)據(jù)中有否 inputSourceMap都是依賴file的inputMap中的內(nèi)容买羞。
          if (this.opts.useInlineSourceMaps !== false) {
            if (!inputSourceMap && this.file.inputMap) {
              inputSourceMap = this.file.inputMap.sourcemap
            }
          }
          const visitorOptions = {}
          Object.entries(schema.defaults.instrumentVisitor).forEach(([name, defaultValue]) => {
            if (name in this.nycConfig) {
              visitorOptions[name] = this.nycConfig[name]
            } else {
              visitorOptions[name] = schema.defaults.instrumentVisitor[name]
            }
          })
          this.__dv__ = programVisitor(t, realPath, {
            ...visitorOptions,
            inputSourceMap
          })
          this.__dv__.enter(path)
        },
        exit (path) {
          if (!this.__dv__) {
            return
          }
          const result = this.__dv__.exit(path)
          if (this.opts.onCover) {
            this.opts.onCover(getRealpath(this.file.opts.filename), result.fileCoverage)
          }
        }
      }
    }
  }
})

如上述所說(shuō)的現(xiàn)在對(duì)istanbul來(lái)說(shuō)最關(guān)鍵的字段是inputMap袁勺。 那我們來(lái)看下babel-loader或者說(shuō)babel里面是否有對(duì)inputMap做一個(gè)賦值的動(dòng)作,分別在這兩個(gè)倉(cāng)庫(kù)中查了下這個(gè)關(guān)鍵字畜普,發(fā)現(xiàn)在babel中知道了期丰。


在這里插入圖片描述

關(guān)鍵的信息應(yīng)該就是在normalize-file中了。我們看看這塊的有一個(gè)邏輯

normalize-file

export default function* normalizeFile(
  pluginPasses: PluginPasses,
  options: Object,
  code: string,
  ast: ?(BabelNodeFile | BabelNodeProgram),
): Handler<File> {
  code = `${code || ""}`;

  if (ast) {
    if (ast.type === "Program") {
      ast = t.file(ast, [], []);
    } else if (ast.type !== "File") {
      throw new Error("AST root must be a Program or File node");
    }
    ast = cloneDeep(ast);
  } else {
    ast = yield* parser(pluginPasses, options, code);
  }

  let inputMap = null;
  if (options.inputSourceMap !== false) {
    // If an explicit object is passed in, it overrides the processing of
    // source maps that may be in the file itself.
    // 已經(jīng)通過(guò)ts-loader處理以后 inputSouceMap是一個(gè)object對(duì)象了,所以直接做賦值了钝荡。
    if (typeof options.inputSourceMap === "object") {
      inputMap = convertSourceMap.fromObject(options.inputSourceMap);
    }

    
    // 這下邊的部分邏輯都是在判斷ast內(nèi)容里面是否有包含soumap的字符串的信息街立,但是實(shí)際上如果是單獨(dú)babel-loader處理的是不存在的。

    if (!inputMap) {
      const lastComment = extractComments(INLINE_SOURCEMAP_REGEX, ast);
      if (lastComment) {
        try {
          inputMap = convertSourceMap.fromComment(lastComment);
        } catch (err) {
          debug("discarding unknown inline input sourcemap", err);
        }
      }
    }
    
    if (!inputMap) {
      const lastComment = extractComments(EXTERNAL_SOURCEMAP_REGEX, ast);
      if (typeof options.filename === "string" && lastComment) {
        try {
          // when `lastComment` is non-null, EXTERNAL_SOURCEMAP_REGEX must have matches
          const match: [string, string] = (EXTERNAL_SOURCEMAP_REGEX.exec(
            lastComment,
          ): any);
          const inputMapContent: Buffer = fs.readFileSync(
            path.resolve(path.dirname(options.filename), match[1]),
          );
          if (inputMapContent.length > LARGE_INPUT_SOURCEMAP_THRESHOLD) {
            debug("skip merging input map > 1 MB");
          } else {
            inputMap = convertSourceMap.fromJSON(inputMapContent);
          }
        } catch (err) {
          debug("discarding unknown file input sourcemap", err);
        }
      } else if (lastComment) {
        debug("discarding un-loadable file input sourcemap");
      }
    }
  }

  // 這里的返回值就是我們看到的一個(gè)File的對(duì)象實(shí)例埠通,里面就包含有inputMap.
  return new File(options, {
    code,
    ast,
    inputMap,
  });
}
在這里插入圖片描述

所以如果單獨(dú)用babel-loader的情況 是沒(méi)有辦法拿到inputSouceMap的

以上就是大概解釋了為什么ts-loader+babel-loader是由inputSourceMap 然后單獨(dú)的babel-loader是沒(méi)有的赎离。

問(wèn)題4. 通過(guò)ts-loader + babel-loader 生成的覆蓋率數(shù)據(jù)與bable-loader 單獨(dú)處理生成的數(shù)據(jù) 在statement等字段上數(shù)據(jù)有一定的差異,這個(gè)差異導(dǎo)致報(bào)告中部分語(yǔ)句覆蓋會(huì)有所區(qū)別端辱。
ts-loader + bable-loader
在這里插入圖片描述
在這里插入圖片描述
bable-loader
在這里插入圖片描述

在這里插入圖片描述

至少?gòu)倪@個(gè)截圖來(lái)了ts-loader + babel-loader的結(jié)果更正確點(diǎn)才對(duì)梁剔。

所以我們現(xiàn)在需要確認(rèn)的一點(diǎn)是為什么coverage中的statement會(huì)有差別。

其實(shí)這里很容易有一個(gè)猜測(cè)的 ts-loader處理后的內(nèi)容其實(shí)已經(jīng)不是真正的源碼內(nèi)容了舞蔽,已經(jīng)變化了才對(duì)荣病。所以我們還是需要再去看下 normalize-file

因?yàn)槲覀冏⒁獾剿膮?shù)里面其實(shí)就包含有ast以及相應(yīng)的code。 所以一樣的我們繼續(xù)斷點(diǎn)到這個(gè)地方看下數(shù)據(jù)的情況

ts-loader + babel-loader 的code 及 ast


在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

從上圖的幾個(gè)對(duì)比其實(shí)就已經(jīng)能夠知道為什么coverage數(shù)據(jù)的statement的數(shù)組的個(gè)數(shù)等都有區(qū)別了渗柿。

但是可能又有人會(huì)好奇的問(wèn)題到众雷,那為什么單獨(dú)使用ts-loader編譯。import的語(yǔ)句都沒(méi)有被計(jì)算進(jìn)去呢做祝,從ats來(lái)看砾省,import的語(yǔ)句命名也是被翻譯過(guò)來(lái)為 ImportDeclaration才對(duì),

這塊呢又要說(shuō)到istanbul中的code instrument這塊去了,由于我對(duì)這塊的理解不深混槐,只是通過(guò)斷點(diǎn)的方式做了一些初步的判斷做的一些猜想编兄。

其實(shí)如果我們有些人細(xì)心的話就能夠發(fā)現(xiàn) 原本是import的語(yǔ)句。
比如說(shuō)

import * as React from "react";

經(jīng)過(guò)ts-loader轉(zhuǎn)換后声登,代碼已經(jīng)變成了

var React = require('react');

其實(shí)是被從es6轉(zhuǎn)換成了commonjs了狠鸳。 所以它的ats的轉(zhuǎn)換也從 ImportDeclaration變成了 VariableDeclaration

所以從這個(gè)過(guò)程可以看出來(lái) VariableDeclaration被識(shí)別了出來(lái),但是ImportDeclaration貌似不被intrument所認(rèn)可悯嗓,是這樣子嗎件舵? 我們又要看下代碼了。

visitor.js

const codeVisitor = {
    ArrowFunctionExpression: entries(convertArrowExpression, coverFunction),
    AssignmentPattern: entries(coverAssignmentPattern),
    BlockStatement: entries(), // ignore processing only
    ExportDefaultDeclaration: entries(), // ignore processing only
    ExportNamedDeclaration: entries(), // ignore processing only
    ClassMethod: entries(coverFunction),
    ClassDeclaration: entries(parenthesizedExpressionProp('superClass')),
    ClassProperty: entries(coverClassPropDeclarator),
    ClassPrivateProperty: entries(coverClassPropDeclarator),
    ObjectMethod: entries(coverFunction),
    ExpressionStatement: entries(coverStatement),
    BreakStatement: entries(coverStatement),
    ContinueStatement: entries(coverStatement),
    DebuggerStatement: entries(coverStatement),
    ReturnStatement: entries(coverStatement),
    ThrowStatement: entries(coverStatement),
    TryStatement: entries(coverStatement),
    VariableDeclaration: entries(), // ignore processing only
    VariableDeclarator: entries(coverVariableDeclarator),
    IfStatement: entries(
        blockProp('consequent'),
        blockProp('alternate'),
        coverStatement,
        coverIfBranches
    ),
    ForStatement: entries(blockProp('body'), coverStatement),
    ForInStatement: entries(blockProp('body'), coverStatement),
    ForOfStatement: entries(blockProp('body'), coverStatement),
    WhileStatement: entries(blockProp('body'), coverStatement),
    DoWhileStatement: entries(blockProp('body'), coverStatement),
    SwitchStatement: entries(createSwitchBranch, coverStatement),
    SwitchCase: entries(coverSwitchCase),
    WithStatement: entries(blockProp('body'), coverStatement),
    FunctionDeclaration: entries(coverFunction),
    FunctionExpression: entries(coverFunction),
    LabeledStatement: entries(coverStatement),
    ConditionalExpression: entries(coverTernary),
    LogicalExpression: entries(coverLogicalExpression)
};

codeVisitor中定義了各個(gè)表達(dá)式的處理,但是里面確實(shí)就不包括 ImportDeclaration

所以這里就應(yīng)該是解釋了為什么import語(yǔ)句沒(méi)有顯示被覆蓋率的原因了

問(wèn)題5. istanbul的插樁為什么不能夠?qū)ode_modules中的代碼進(jìn)行插樁脯厨?

其實(shí)不是說(shuō)不能主要是這里遇到了一些坑铅祸, 我們首先先看下官方的文檔的說(shuō)明

Including files within node_modules

We always add **/node_modules/** to the exclude list, even if not >specified in the config.
You can override this by setting --exclude-node-modules=false.

For example, "excludeNodeModules: false" in the following nyc config will prevent node_modules from being added to the exclude rules.
The set of include rules then restrict nyc to only consider instrumenting files found under the lib/ and node_modules/@my-org/ directories.
The exclude rules then prevent nyc instrumenting anything in a test folder and the file node_modules/@my-org/something/unwanted.js.

{
  "all": true,
  "include": [
    "lib/**",
    "node_modules/@my-org/**"
  ],
  "exclude": [
    "node_modules/@my-org/something/unwanted.js",
    "**/test/**"
  ],
  "excludeNodeModules": false
}

根據(jù)上述的信息, 我們?cè)趐ackage.json中做相應(yīng)的修改。重新進(jìn)行打包后,coverage的數(shù)據(jù)中并沒(méi)有出現(xiàn)我們想要的node_modules的數(shù)據(jù)

帶著疑問(wèn), 我們需要重新思考下:首先 node_modules的內(nèi)容被babel編譯了嗎合武?如果是編譯了那istanul對(duì)這個(gè)對(duì)這個(gè)文件做插樁了嗎临梗? 我們需要先確定這兩點(diǎn)。

首先我們先確認(rèn)我們的babel的配置是正確的稼跳,即確實(shí)有指定node_modules也加入到編譯中盟庞。

webpack.config

  {
    test: [/\.js$/, /\.tsx?$/],
    use: ['babel-loader'],
    include: [/src/]
  },
  {
    test: [/\.js$/, /\.tsx?$/],
    use: ['babel-loader'],
    include: [ /node_modules\/@cvte\/seewoedu-video\/dist\//]
  },

從這里看至少是對(duì)的,但是怎么確定文件確實(shí)是被babel以及istanbul處理到呢汤善?

我們還是要從源碼入手做一個(gè)控制臺(tái)的打印來(lái)看看什猖。

babel-loader

async function loader(source, inputSourceMap, overrides) {
  const filename = this.resourcePath;
  // 增加一個(gè)打印
   console.log("babel loader", filename);
  let loaderOptions = loaderUtils.getOptions(this) || {};

  validateOptions(schema, loaderOptions, {
    name: "Babel loader",
  });
...

我們知道webpack打包會(huì)經(jīng)過(guò)babel-loader 所以我們先在這里打印下看下是否確實(shí)經(jīng)過(guò)了處理票彪。

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)
          // 增加一個(gè)打印
          console.log('istanbul, ', this.file.opts.filename)
          if (shouldSkip(realPath, this.nycConfig)) {
            return
          }
          ....

我們重新看下打包過(guò)程的打印信息


在這里插入圖片描述

從上述的信息來(lái)看, 我們的源碼進(jìn)入了babel-loader, 并且也被istanbul處理了,但是node_modules確只是被babel-loader處理不狮,但是并沒(méi)有到istanbul中抹镊。

所以這里肯定是哪里的配置不正確導(dǎo)致的。

找了很多istanbul的配置都沒(méi)有什么效果荤傲,直到搜索到了這個(gè)issue的回答 babel 7 can't compile in node_modules

http://babeljs.io/docs/en/config-files#6x-vs-7x-babelrc-loading 這里有了比較清晰的答案了。

Given that, it may be more desirable to rename the .babelrc to be a project-wide "babel.config.json". As mentioned in the project-wide section above, this may then require explicitly setting "configFile" since Babel will not find the config file if the working directory isn't correct.

所以我們只需要將babelrc 文件修改為babel-config.json即可颈渊。
我們重新來(lái)嘗試下看下打包的打印

在這里插入圖片描述

從這里看確實(shí)node_modules的處理已經(jīng)進(jìn)入到了istanbul處理的范圍內(nèi)了遂黍。

總結(jié)

以上就是我們?cè)谡{(diào)研跟實(shí)施代碼覆蓋率的時(shí)候遇到的一些問(wèn)題跟分析的過(guò)程。由于前端代碼覆蓋率這塊還剛起步俊嗽,如果還有其他問(wèn)題 我會(huì)繼續(xù)更新這篇文章雾家,解決其他同學(xué)在前端代碼覆蓋率上遇到的問(wèn)題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末绍豁,一起剝皮案震驚了整個(gè)濱河市芯咧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌竹揍,老刑警劉巖敬飒,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異芬位,居然都是意外死亡无拗,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)昧碉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)英染,“玉大人,你說(shuō)我怎么就攤上這事被饿∷目担” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵狭握,是天一觀的道長(zhǎng)闪金。 經(jīng)常有香客問(wèn)我,道長(zhǎng)论颅,這世上最難降的妖魔是什么毕泌? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮嗅辣,結(jié)果婚禮上撼泛,老公的妹妹穿的比我還像新娘。我一直安慰自己澡谭,他們只是感情好愿题,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布损俭。 她就那樣靜靜地躺著,像睡著了一般潘酗。 火紅的嫁衣襯著肌膚如雪杆兵。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,604評(píng)論 1 305
  • 那天仔夺,我揣著相機(jī)與錄音琐脏,去河邊找鬼。 笑死缸兔,一個(gè)胖子當(dāng)著我的面吹牛日裙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播惰蜜,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼昂拂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了抛猖?” 一聲冷哼從身側(cè)響起格侯,我...
    開(kāi)封第一講書(shū)人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎财著,沒(méi)想到半個(gè)月后联四,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡撑教,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年碎连,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驮履。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鱼辙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出玫镐,到底是詐尸還是另有隱情倒戏,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布恐似,位于F島的核電站杜跷,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏矫夷。R本人自食惡果不足惜葛闷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望双藕。 院中可真熱鬧淑趾,春花似錦、人聲如沸忧陪。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至延蟹,卻和暖如春评矩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背阱飘。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工斥杜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人沥匈。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓蔗喂,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親咐熙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355