Our customer portal is a React app written in TypeScript. Until March it was built by webpack with ts-loader, which reports type errors as build errors, so a green build also meant the code type checked. Nobody had written that down, because nobody had ever needed to.
In March we moved the build to Vite. Builds went from four minutes to fifty seconds, three people reviewed the pull request and the portal looked identical. Our package.json said "build": "vite build", copied from an internal example rather than from the Vite template.
In July customers began writing in about order confirmation pages that showed the total as £NaN. Three weeks earlier the API team had renamed total to totalPence in our shared types package and updated the call sites they could find. The confirmation page still read order.total. Under webpack that line would have stopped the build. Under Vite it went to production.
Vite does not type check. It hands each TypeScript file to esbuild, which removes the types and emits JavaScript without asking whether they were right, and the documentation says so: type checking is expected to happen in your editor or in a separate step. The official template's build script runs tsc before vite build for exactly that reason. Ours did not, so for four months the only type checking anywhere was in editors, and an editor shows errors in the files you have open. Nobody had opened the confirmation page. When we ran tsc --noEmit across the repository that afternoon it reported 214 errors, most of them harmless and three of them real bugs already in production.
The page fix was one line. Type checking is now its own CI job, tsc --noEmit on every pull request, and a required check that does not depend on the bundler, so changing the bundler cannot take it away. We worked the 214 errors down over two weeks, with the count posted on each pull request until it reached zero. And a weekly job pushes a branch containing a deliberate type error and alerts us if CI does not go red on it.
Webpack had been our type checker by accident. We replaced the bundler and the checker left with it, and nothing in a passing build could tell us something was missing.
– Sergey Shinder
