nyc review
nyc is Istanbul's command-line coverage runner for Node.js. It starts a test command, instruments eligible source files as CommonJS loads them, gathers line, branch, function, and statement counters, remaps results through source maps, and writes terminal, HTML, LCOV, or JSON reports. Its process wrapper can collect coverage from commands spawned by the test suite, which remains a useful distinction for CLI integration tests. Version 18.0.0 updates dependencies, replaces an older glob line, and raises the runtime requirement to Node 20 or Node 22 and later. The core instrumentation model is mature and CommonJS-oriented; native ESM projects usually fit V8-based coverage better.
Keep nyc where CommonJS tests, subprocess collection, and Istanbul-specific controls are already working. New ESM projects should start with c8 or their runner's own V8 coverage and avoid a 134-package compatibility layer.
We installed it
| Install | ✓ · 8.2s | 134 packages on disk · 31 MB |
| Import | ✓ | ESM import works · require() works · CommonJS package |
| Browser | n/a | could not be bundled for the browser (Node-only code, most likely) |
| Types | — | no TypeScript types found |
| Known vulns | 0 | 0 critical · 0 high · 0 moderate · 0 low (npm audit) |
Answers from our run
Does nyc install cleanly?
Yes. In a fresh container with an empty cache, npm install nyc finished in 8 seconds, leaving 134 packages and 31 MB on disk. npm audit reported no known vulnerabilities.
Can nyc run in a browser?
Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.
Does nyc work with both ESM and CommonJS?
Yes. Both import 'nyc' and require('nyc') worked in Node 22 in our run. The package is published as CommonJS.
Does nyc include TypeScript types?
No type declarations were found in our install, so TypeScript users need their own declarations.
nyc or c8: which should you use?
c8: Use it for V8-native coverage across CommonJS and ESM while keeping Istanbul-compatible report formats. Keep nyc where CommonJS tests, subprocess collection, and Istanbul-specific controls are already working.
When should you not use nyc?
Source executes as native ES modules. nyc's main path instruments files through CommonJS loading, while c8 reads V8 coverage from both module systems.
Use it if
- A CommonJS Mocha, AVA, tape, or plain Node test command needs Istanbul reports and has no coverage integration of its own.
- Integration tests spawn a Node CLI and coverage from those child processes must join the parent report.
- Separate unit and integration commands need to deposit raw results and produce one final LCOV file.
- The project already relies on Istanbul ignore comments, shared nyc presets, or established threshold behavior.
- Source executes as native ES modules. nyc's main path instruments files through CommonJS loading, while c8 reads V8 coverage from both module systems.
- Jest, tap, Vitest, or Node's test runner already supplies the coverage mode you need. Adding nyc duplicates configuration and report generation.
- A small development dependency tree matters. Our clean install left 134 packages and used 31 MB for a command-line tool.
- The project runs Node 18 or 21. Version 18.0.0 declares only Node 20 or Node 22 and later because its updated transitive graph sets that floor.
- You need a typed programmatic library or browser bundle. The package has no TypeScript declarations, and our browser build failed on Node-specific modules.
Setup reality
We installed nyc 18.0.0 in a fresh Node 22 Bookworm container. npm completed in 8.2 seconds, left 134 packages, and used 31 MB on disk. The nyc package itself is 128 KB unpacked, declares 27 direct dependencies and no peers, and requires Node 20 || >=22. npm audit reported zero known vulnerabilities. CommonJS require and ESM import both loaded the package, but it ships no TypeScript declarations.
The first report excludes every source file that tests never load. Set all: true and a narrow include if untouched modules must count as zero. Defining your own exclude array replaces the defaults from @istanbuljs/schema; it does not append to them. CLI globs need shell quotes. nyc finds its project root by walking upward to a package.json, so monorepo commands should set cwd when that implicit root is wrong.
Raw counters go to .nyc_output, reports to coverage, and transformed code to node_modules/.cache/nyc. Ignore all three outputs as appropriate and clear the instrumentation cache after Babel, TypeScript, source-map, or transform changes. For multiple commands, let the first run clean, pass --no-clean to later runs, use --silent while collecting, then call nyc report. Missing --no-clean leaves only the last command's data.
TypeScript and Babel projects need the matching @istanbuljs/nyc-config-* preset plus the compiler hook. With all: true, the hook must be required by nyc so unloaded files can be transformed. Native ESM can still return missing or partial coverage because the CommonJS hook is not on the import path. Our esbuild browser attempt failed, which matches a Node CLI that wraps processes, reads the filesystem, and instruments server-side modules.
Patterns
Run Mocha under coverage wrap-test-command
// package.json
{
"scripts": {
"test": "mocha",
"coverage": "nyc npm test"
}
}Place nyc flags before the command it launches. Do not wrap a script that already invokes nyc or instrumentation will be nested.
Count every source module configure-file-selection
{
"all": true,
"include": ["src/**/*.js"],
"exclude": ["**/*.spec.js", "**/fixtures/**"],
"reporter": ["text-summary", "lcov"]
}Save this as `.nycrc`. A custom exclude array replaces nyc's defaults, so add any default test or generated paths you still need.
Protect globs from the shell quote-coverage-globs
npx nyc --all --include 'src/**/*.js' --exclude '**/*.spec.js' npm testUnquoted patterns can expand before nyc sees them, producing different selection rules across shells and working directories.
Fail a build below the target enforce-coverage-threshold
{
"check-coverage": true,
"branches": 80,
"functions": 85,
"lines": 90,
"statements": 90,
"per-file": false
}Threshold numbers have no effect unless check-coverage is enabled. Turning on per-file applies each threshold to every selected file.
Apply the TypeScript preset report-typescript
// .nycrc
{
"extends": "@istanbuljs/nyc-config-typescript",
"all": true,
"check-coverage": true
}
// command
npx nyc mocha -r ts-node/register 'test/**/*.spec.ts'Install the preset, source-map support, and the runtime compiler. Native ESM TypeScript can bypass nyc's CommonJS instrumentation hook.
Emit text and LCOV output write-ci-reports
npx nyc \
--reporter=text-summary \
--reporter=lcov \
mocha 'test/**/*.js'The reporter option is repeatable. LCOV output includes `coverage/lcov.info` plus browsable HTML under the report directory.
Accumulate unit and integration coverage merge-test-runs
npx nyc --silent npm run test:unit
npx nyc --silent --no-clean npm run test:integration
npx nyc report --reporter=text --reporter=lcovThe later collection commands require `--no-clean`; otherwise each one removes the raw counters written by the previous run.
Use Istanbul ignore hints ignore-unreachable-code
/* istanbul ignore next */
function debugOnly() {
return process.env.DEBUG;
}
/* istanbul ignore else */
if (process.platform === 'win32') {
useWindowsPath();
} else {
usePosixPath();
}Ignore comments alter the coverage denominator. Keep a reason in review because V8 coverage tools may interpret or ignore these hints differently.
Trace spawned Node commands collect-child-processes
npx nyc --show-process-tree mocha test/cli.spec.jsnyc uses process wrapping to propagate coverage into spawned Node processes. This extra layer can expose platform-specific command and argument issues.
Pin paths to one package set-monorepo-root
npx nyc \
--cwd packages/api \
--all \
--include 'src/**/*.js' \
npm testWithout cwd, nyc walks upward for package.json. Running from a monorepo root can select files and write artifacts in the wrong package.
Disable stale transformed code clear-instrumentation-cache
npx nyc --cache=false npm testUse this after changing Babel, TypeScript, or source-map configuration when line mappings no longer match. The regular cache lives under node_modules/.cache/nyc.
Use V8 coverage for native ESM switch-esm-to-c8
npm install --save-dev c8
npx c8 --reporter=text --reporter=lcov mocha 'test/**/*.mjs'A blank nyc report on imported `.mjs` files can come from the missing CommonJS hook rather than from untested code.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| c8 | npm | Use it for V8-native coverage across CommonJS and ESM while keeping Istanbul-compatible report formats. |
| @vitest/coverage-v8 | npm | Use it when Vitest already owns test execution and coverage belongs in the same configuration. |
| babel-plugin-istanbul | npm | Use it to instrument code during compilation, including code that will run in a browser bundle. |
| jest | npm | Use its built-in coverage when choosing a full test runner rather than adding coverage to an existing one. |
More testing guides
pytest · chai · vitest · jsdom · playwright · coverage · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

