Save npm dev dependencies with --save-dev and keep them under NODE_ENV=production

FA
16d ago
+92-3

Split out of #7054. This is the bug fix that turned out to be behind most of the "npm deploys are slow" number in that PR.

Problem

_install_frontend_packages adds dev dependencies with <pm> add -d. bun reads -d as --dev; npm reads it as --loglevel info. On npm projects every framework devDependency (vite, postcss, @react-router/dev, …) was therefore saved into dependencies. On the next run sync_root_package_json_to_web re-rendered them under devDependencies, saw the manifest as changed, invalidated the install cache, and the install step ran npm remove + npm install + 2× npm add on every single export, even with nothing changed.

Changes

  • Pass --save-dev on npm (bun keeps -d).
  • Pass --include=dev on every npm install/add/remove, so NODE_ENV=production cannot prune the build tooling out of node_modules.
  • Tests for both package managers, for the restored-lockfile install, and for later add/remove operations under NODE_ENV=production.

Measurement

Unchanged app, repeat reflex export, npm:

mainthis PR
package-manager commands per export4 (remove, install, add, add)0
compile phase (includes install)7.75 s0.47 s
total export15.2 s7.6 s (−50%)

bun is unaffected (6.9 s on main, no package-manager commands on repeat exports either way).

Measurement setup

Five-page app from the blank template (Radix Themes + Tailwind v4 plugins), Linux x86_64, 4 vCPU, Python 3.14, Node 22.22, npm 10.9, bun 1.4.2. Each run is a fresh interpreter calling reflex.utils.export.export() the same way reflex export does; durations are the phase timings the export already reports to telemetry (compile includes the dependency install). Medians of 3 runs unless noted. Vite dominates every export (~6.5 s); these numbers isolate the part this PR touches.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Checklist

  • Tests added for the change; the full tests/units suite passes locally except the 14 reflex_bench/drivers/test_browser.py cases that need a Playwright browser launch, which fail identically on untouched main in this sandbox
  • ruff check, ruff format --check, codespell, and pyright reflex tests pass locally
  • News fragment added for every package touched

https://claude.ai/code/session_018nWB9r2HcC5tkT72UxWgGj


Generated by Claude Code