babel-plugin-istanbul is a Babel plugin that automatically instruments JavaScript code with Istanbul coverage tracking during compilation. It enables code coverage analysis for ES6+ code by transforming the source code to include coverage counters, making it compatible with testing frameworks like Mocha and Karma.
npm install --save-dev babel-plugin-istanbul// ESM import (for programmatic usage)
import babelPluginIstanbul from "babel-plugin-istanbul";
// CommonJS (for programmatic usage)
const babelPluginIstanbul = require("babel-plugin-istanbul");Add the plugin to your .babelrc or babel.config.js in test mode:
{
"env": {
"test": {
"plugins": ["istanbul"]
}
}
}With options:
{
"env": {
"test": {
"plugins": [
["istanbul", {
"exclude": ["**/*.spec.js", "test/**"],
"useInlineSourceMaps": false
}]
]
}
}
}import babel from "@babel/core";
import babelPluginIstanbul from "babel-plugin-istanbul";
const result = babel.transform(sourceCode, {
filename: "example.js",
plugins: [
[babelPluginIstanbul, {
include: ["src/**/*.js"],
exclude: ["src/**/*.test.js"],
onCover: (filePath, fileCoverage) => {
console.log(`Instrumented: ${filePath}`);
}
}]
]
});The plugin operates as a Babel AST visitor that:
The main export that creates the Babel plugin for code instrumentation.
/**
* Creates a Babel plugin that instruments code with Istanbul coverage
* @param {Object} api - Babel API object
* @returns {Object} Babel plugin object with visitor pattern
*/
function babelPluginIstanbul(api: BabelAPI): BabelPlugin;
interface BabelPlugin {
visitor: {
Program: {
enter(path: NodePath): void;
exit(path: NodePath): void;
};
};
}
interface BabelAPI {
types: Object;
assertVersion(version: number): void;
}Configuration object passed as the second parameter to the plugin:
interface PluginOptions {
/** Array of glob patterns for files to include */
include?: string[];
/** Array of glob patterns for files to exclude */
exclude?: string[];
/** Whether to exclude node_modules (default: true) */
excludeNodeModules?: boolean;
/** File extensions to consider for instrumentation */
extension?: string[];
/** Global variable name for coverage data (default: '__coverage__') */
coverageVariable?: string;
/** Scope for coverage variable ('window', 'global', etc.) */
coverageGlobalScope?: string;
/** Whether to use function to determine global scope (default: true) */
coverageGlobalScopeFunc?: boolean;
/** Whether to use inline source maps (default: true) */
useInlineSourceMaps?: boolean;
/** Explicitly provided source map object */
inputSourceMap?: Object;
/** Array of class method names to ignore in instrumentation */
ignoreClassMethods?: string[];
/** Current working directory for config resolution */
cwd?: string;
/** Path to .nycrc configuration file */
nycrcPath?: string;
/** Callback executed when a file is instrumented */
onCover?: (filePath: string, fileCoverage: Object) => void;
}The onCover callback provides access to coverage data for each instrumented file:
/**
* Callback executed when a file is instrumented
* @param {string} filePath - Absolute path to the instrumented file
* @param {Object} fileCoverage - Istanbul file coverage object
*/
function onCover(filePath: string, fileCoverage: FileCoverage): void;
interface FileCoverage {
/** Mapping of statement locations */
statementMap: { [key: string]: Location };
/** Mapping of function locations */
fnMap: { [key: string]: FunctionLocation };
/** Mapping of branch locations */
branchMap: { [key: string]: BranchLocation };
/** Statement execution counts */
s: { [key: string]: number };
/** Function execution counts */
f: { [key: string]: number };
/** Branch execution counts */
b: { [key: string]: number[] };
}
interface Location {
start: { line: number; column: number };
end: { line: number; column: number };
}
interface FunctionLocation extends Location {
name: string;
decl: Location;
loc: Location;
}
interface BranchLocation {
loc: Location;
type: string;
locations: Location[];
}Works automatically with karma-coverage when Babel transpilation is already configured:
// karma.conf.js
module.exports = function(config) {
config.set({
preprocessors: {
'src/**/*.js': ['babel'] // Don't add 'coverage' preprocessor
},
coverageReporter: {
type: 'html',
dir: 'coverage/'
}
});
};Configure NYC to not instrument code (plugin handles instrumentation):
{
"nyc": {
"sourceMap": false,
"instrument": false,
"include": ["src/**/*.js"],
"exclude": ["src/**/*.test.js"]
}
}The plugin resolves configuration from multiple sources in order of precedence:
NYC_CONFIG environment variable.nycrc, .nycrc.json, or package.json nyc section/**
* Resolves final configuration from multiple sources
* @param {Object} opts - Plugin options
* @returns {Object} Resolved configuration object
*/
function findConfig(opts: PluginOptions): ResolvedConfig;
interface ResolvedConfig extends PluginOptions {
cwd: string;
include: string[];
exclude: string[];
excludeNodeModules: boolean;
extension: string[];
coverageVariable: string;
useInlineSourceMaps: boolean;
}The plugin handles source maps to maintain coverage accuracy across build steps:
/**
* Source map options for coverage instrumentation
*/
interface SourceMapOptions {
/** Whether to use inline source maps from the source code */
useInlineSourceMaps?: boolean;
/** Explicitly provided source map object */
inputSourceMap?: {
version: number;
sources: string[];
names: string[];
mappings: string;
sourcesContent?: string[];
};
}// babel.config.js
module.exports = {
env: {
test: {
plugins: [
["istanbul", {
coverageVariable: "__MY_COVERAGE__"
}]
]
}
}
};// .babelrc
{
"env": {
"test": {
"plugins": [
["istanbul", {
"include": ["src/**/*.js"],
"exclude": [
"src/**/*.test.js",
"src/**/*.spec.js",
"src/test-utils/**"
]
}]
]
}
}
}import babel from "@babel/core";
import babelPluginIstanbul from "babel-plugin-istanbul";
const coverageData = new Map();
babel.transform(code, {
plugins: [
[babelPluginIstanbul, {
onCover: (filePath, fileCoverage) => {
coverageData.set(filePath, fileCoverage);
console.log(`Instrumented ${filePath} with ${Object.keys(fileCoverage.statementMap).length} statements`);
}
}]
]
});// babel.config.js
module.exports = {
env: {
test: {
plugins: [
["istanbul", {
ignoreClassMethods: ["render", "componentDidMount"]
}]
]
}
}
};// For browser testing (with window global)
{
"plugins": [
["istanbul", {
"coverageGlobalScope": "window"
}]
]
}
// For Node.js testing (with global object)
{
"plugins": [
["istanbul", {
"coverageGlobalScope": "global"
}]
]
}The plugin handles several error scenarios gracefully:
Common error patterns:
// Configuration loading error
throw new Error("Failed to load nyc configuration: Invalid JSON in .nycrc");
// File filtering (no error, just skipped)
if (shouldSkip(filePath, nycConfig)) {
return; // File not instrumented
}