Skip to content

CI: Build CLI step on Windows takes ~5 min due to redundant NAPI rebuild #715

@fengmk2

Description

@fengmk2

Problem

The Build CLI step in the CLI E2E test (windows-latest) job takes ~5 minutes, significantly slower than Linux (~2 min) and macOS (~1 min).

Example run: https://github.com/voidzero-dev/vite-plus/actions/runs/22795306880/job/66128870036

Step timing comparison (CLI E2E test)

Step Windows Ubuntu macOS
setup-rust 1m 5s 29s 11s
setup-node 1m 44s 25s 15s
Build with upstream 1m 12s (cache hit) 44s 16s
Build CLI 5m 12s 1m 49s 58s
Total job ~14 min ~8 min ~4 min

Root Cause Analysis

1. Redundant NAPI Rust compilation (primary bottleneck)

The Build CLI step runs pnpm build, which calls:

pnpm -F vite-plus-cli build  →  oxnode -C dev ./build.ts

build.ts runs buildNapiBinding() by default (no --skip-native flag), triggering a full cargo build --release of the NAPI binding — even though Build with upstream already compiled the same NAPI bindings (with caching via actions/cache).

The Build with upstream action correctly uses build-ts (which passes --skip-native) for the TypeScript parts and separately handles native builds with caching. But the subsequent pnpm build in Build CLI re-compiles everything.

Cargo release build of 9 workspace crates (same code, all NAPI cache hit):

Platform Time Relative
macOS (Apple Silicon) 49s
Ubuntu (x86_64) 1m 24s 1.7×
Windows (x86_64, MSVC) 3m 10s 3.9×

2. Windows MSVC is inherently slower for Rust compilation

  • MSVC linker (link.exe) is single-threaded and slower than lld/macOS linker (PDB generation overhead)
  • NTFS file I/O has higher per-operation latency than ext4/APFS for the many small files cargo generates
  • Windows Defender real-time scanning on GitHub Actions runners scans every file read/write, adding 30-50% overhead
  • Process spawningCreateProcess is ~10× slower than Unix fork/exec; cargo spawns many rustc processes

3. Slower toolchain setup on Windows

  • setup-node (pnpm install): 1m 44s vs 25s on Linux (NTFS I/O + antivirus overhead)
  • setup-rust: 1m 5s vs 29s on Linux

Proposed Solutions

1. Skip native rebuild in Build CLI step (quick fix, biggest impact)

Change the Build CLI step from:

- name: Build CLI
  run: |
    pnpm build
    pnpm bootstrap-cli:ci

To:

- name: Build CLI
  run: |
    pnpm build-ts-only   # or: pnpm -F @voidzero-dev/* -F vite-plus build && pnpm -F vite-plus-cli build-ts
    pnpm bootstrap-cli:ci

Since Build with upstream already handles the NAPI bindings with proper caching, the Build CLI step only needs to do the TypeScript build (build-ts / --skip-native).

Expected savings: ~3 min on Windows, ~1-2 min on Ubuntu

2. Disable Windows Defender real-time scanning

Add this step before any build steps on Windows:

- name: Disable Windows Defender real-time monitoring
  if: runner.os == 'Windows'
  run: Set-ExecutionPolicy Bypass -Scope Process -Force; Set-MpPreference -DisableRealtimeMonitoring $true
  shell: powershell

GitHub-hosted Windows runners allow this without admin elevation. This can reduce I/O-heavy operations (cargo, pnpm install) by 30-50%.

3. Additional optimizations (lower priority)

  • Add sccache for Windows Rust builds when NAPI cache misses
  • Consider cargo build --profile ci: a custom profile with fewer optimizations for faster compile time in CI

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Priority

None yet

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions