Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

All notable changes to this project will be documented in this file.

## [1.5.2] - 2026-07-10

### Changed

- **Switch from `transformWithEsbuild` to `transformWithOxc`** β€” Zero-coverage generation now uses Vite's OXC-based transform (bundled inside Vite via Rolldown) instead of esbuild. OXC is called with `jsx: { runtime: 'automatic' }` to force JSX transformation regardless of the project's `tsconfig.json` (Next.js projects typically set `"jsx": "preserve"` which OXC respects when auto-discovering tsconfig, producing far fewer statements). OXC does not constant-fold `process.env.NODE_ENV` without an explicit `define`, so all branches are preserved. This eliminates the need for `esbuild` as a direct dependency.

### Fixed

- **Remove `esbuild` direct dependency** β€” No longer needed now that `transformWithOxc` handles TypeScript/TSX compilation. `esbuild` was added in v1.5.1 as a workaround for Vite 8 removing its bundled esbuild; the switch to OXC resolves this cleanly with no extra dependency.

## [1.5.1] - 2026-07-10

### Fixed
Expand Down
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,6 @@ npm install nextcov --save-dev
npm install @playwright/test --save-dev
```

> **Note:** `esbuild` is included as a direct dependency of nextcov (required since Vite 8 no longer bundles it). It will be installed automatically β€” no manual installation needed.

## Quick Setup with `nextcov init`

The fastest way to get started is with the `init` command:
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nextcov",
"version": "1.5.1",
"version": "1.5.2",
"description": "Collect test coverage for Playwright E2E tests in Next.js applications",
"author": "Steve Zhang",
"license": "MIT",
Expand Down Expand Up @@ -63,7 +63,6 @@
"ast-v8-to-istanbul": "^1.0.4",
"chalk": "^4.1.2",
"convert-source-map": "^2.0.0",
"esbuild": ">=0.21.0",
"glob": "^11.1.0",
"istanbul-lib-coverage": "^3.2.0",
"istanbul-lib-report": "^3.0.1",
Expand Down
35 changes: 18 additions & 17 deletions src/converter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import { existsSync } from 'node:fs'
import { resolve, sep } from 'node:path'
import { parseAstAsync, transformWithEsbuild } from 'vite'
import { parseAstAsync, transformWithOxc } from 'vite'
import _astV8ToIstanbul from 'ast-v8-to-istanbul'
import libCoverage from 'istanbul-lib-coverage'
import libSourceMaps from 'istanbul-lib-source-maps'
Expand Down Expand Up @@ -955,14 +955,14 @@ export class CoverageConverter {
/**
* Create empty coverage entry for an uncovered file.
*
* Uses the same esbuild pipeline as Vitest: transformWithEsbuild compiles
* TypeScript/TSX β†’ JavaScript with a source map, then parseAstAsync parses
* the compiled JS, and astV8ToIstanbul maps positions back to the original
* source via the source map.
* Uses Vite's OXC transform: transformWithOxc compiles TypeScript/TSX β†’
* JavaScript with a source map, then parseAstAsync parses the compiled JS,
* and astV8ToIstanbul maps positions back to the original source via the
* source map.
*
* Because the positions come from esbuild (identical to unit/component
* coverage), a subsequent `nextcov merge` rebase always finds exact
* line:col matches β€” no fallback, no misattribution.
* Because the positions come from OXC (matching Vitest's pipeline),
* a subsequent `nextcov merge` rebase always finds exact line:col matches β€”
* no fallback, no misattribution.
*/
private async createEmptyCoverage(
filePath: string,
Expand All @@ -971,18 +971,19 @@ export class CoverageConverter {
try {
const fileUrl = toFileUrl(filePath, this.projectRoot)

// Transform TypeScript/TSX β†’ JS using esbuild (same as Vitest's pipeline).
// Transform TypeScript/TSX β†’ JS using OXC (bundled in Vite via Rolldown).
// sourcemap:true gives us the position mapping back to the original source.
//
// IMPORTANT: Use platform:'neutral' so esbuild does NOT constant-fold
// process.env.NODE_ENV branches. With the default platform ('browser'),
// esbuild sees NODE_ENV='production' in the env and eliminates the
// 'else' arm of `NODE_ENV === 'production' ? A : B`, producing fewer
// branches in the zero-coverage map than Vitest (which uses platform:'browser'
// but runs with NODE_ENV='test', so no constant-folding occurs there).
const { code: compiledCode, map } = await transformWithEsbuild(code, filePath, {
// We explicitly set jsx.runtime:'automatic' to force JSX transformation
// regardless of the project's tsconfig.json. Next.js projects typically
// have "jsx":"preserve" in tsconfig, which OXC respects when auto-discovering
// it β€” leaving JSX untransformed and producing far fewer statements than
// Vitest's pipeline (which overrides the tsconfig via its own OXC config).
// OXC does not constant-fold process.env.NODE_ENV without an explicit
// define, so all branches are preserved β€” matching Vitest's behavior.
const { code: compiledCode, map } = await transformWithOxc(code, filePath, {
sourcemap: true,
platform: 'neutral',
jsx: { runtime: 'automatic' },
})

// Parse the compiled JS with Vite's fast Rollup-based parser
Expand Down
Loading