Supersedes #319 (@vitejs/plugin-react 4.7.0 → 6.1.1), which fails CI on its own. This carries the plugin bump with the vite major it needs, so #319 should close when this lands.
The prerequisite was vite 8, not vite 7
@vitejs/[email protected] peers on vite: ^8.0.0 and nothing lower — its other peers (oxc-transform-react, @rolldown/plugin-babel, babel-plugin-react-compiler) are all optional. That is why #319 died at startup with ERR_PACKAGE_PATH_NOT_EXPORTED: './internal' is not defined by "exports" in vite/package.json: it was loading a vite 6 that has no such subpath.
So the build moves 6.4.3 → 8.3.0 (current latest; vite 7 is previous). [email protected] already peers ^6 || ^7 || ^8, so it needed nothing.
What vite 8 changed for us
It bundles with rolldown instead of rollup, which accounts for the config work:
manualChunks lost its object form. Naming a chunk and listing the packages in it is not supported; rolldown takes codeSplitting.groups, each with a name and a test matched against module paths. The same two chunks come out — vendor and icons — and icons is listed first because groups are tried in order and the first match wins.
rollupOptions is now a deprecated alias for rolldownOptions, so it is spelled the new way. Purely cosmetic: the chunk hashes were byte-identical before and after the rename.
Verified the chunking still does what it did: vendor-BybD4nxw.js (60.9 kB) still carries dompurify — I grepped the built chunk for 3.4.15 — and icons-L_xczE6n.js (38.2 kB) is still lucide on its own. There is one new rolldown-runtime chunk, which is rolldown's own helper. Build time drops from ~1.6s to ~220ms.
The lockfile is regenerated, not patched
Worth knowing, since it is most of the diff. vitest depends on vite itself, and an incremental npm install was content to leave 6.4.3 hoisted at the root for vitest while web built against 8.3.0 — two vite majors in one tree, with tests transforming through a different bundler than the build. npm dedupe would not collapse it. A clean install resolves to a single vite 8.3.0, which is why the lock is rebuilt from scratch.
Node minimum
vite 8 requires ^20.19.0 || >=22.12.0, above the >=20.10 that package.json engines and the README both promised. Both now say 20.19, since building from source on 20.10 genuinely no longer works. CI, the Dockerfile and the published image are all on Node 22 and were never affected.
One thing surfaced, not fixed
Rolldown warns that two modules are imported both statically and dynamically, so the dynamic import cannot move them into their own chunk:
src/store/sieve.ts is dynamically imported by src/store/mail.ts but also statically imported by
src/App.tsx, src/views/mail/FilterFromMessage.tsx, src/views/settings/FiltersSettings.tsx
src/lib/notify.ts is dynamically imported by src/App.tsx, src/store/mail.ts but also statically
imported by src/App.tsx, src/views/settings/NotificationsSettings.tsx
That is a property of the source rather than of the bundler — store/sieve.ts has three static importers against the one dynamic import at store/mail.ts:1429 — and rolldown is only saying out loud what rollup did quietly. Left alone here; it belongs in its own change if the split is worth having.
Verification
npm run typecheck, npm test (1153 web tests across 113 files, 168 server tests) and npm run build all pass, npm audit is clean, and I ran npm ci from the regenerated lock plus a full docker build locally — the image builds on node:22-alpine as CI will.
Supersedes #319 (`@vitejs/plugin-react` 4.7.0 → 6.1.1), which fails CI on its own. This carries the plugin bump with the vite major it needs, so #319 should close when this lands.
## The prerequisite was vite 8, not vite 7
`@vitejs/[email protected]` peers on `vite: ^8.0.0` and nothing lower — its other peers (`oxc-transform-react`, `@rolldown/plugin-babel`, `babel-plugin-react-compiler`) are all optional. That is why #319 died at startup with `ERR_PACKAGE_PATH_NOT_EXPORTED: './internal' is not defined by "exports" in vite/package.json`: it was loading a vite 6 that has no such subpath.
So the build moves 6.4.3 → 8.3.0 (current `latest`; vite 7 is `previous`). `[email protected]` already peers `^6 || ^7 || ^8`, so it needed nothing.
## What vite 8 changed for us
It bundles with **rolldown** instead of rollup, which accounts for the config work:
- **`manualChunks` lost its object form.** Naming a chunk and listing the packages in it is not supported; rolldown takes `codeSplitting.groups`, each with a `name` and a `test` matched against module paths. The same two chunks come out — `vendor` and `icons` — and `icons` is listed first because groups are tried in order and the first match wins.
- **`rollupOptions` is now a deprecated alias** for `rolldownOptions`, so it is spelled the new way. Purely cosmetic: the chunk hashes were byte-identical before and after the rename.
Verified the chunking still does what it did: `vendor-BybD4nxw.js` (60.9 kB) still carries dompurify — I grepped the built chunk for `3.4.15` — and `icons-L_xczE6n.js` (38.2 kB) is still lucide on its own. There is one new `rolldown-runtime` chunk, which is rolldown's own helper. Build time drops from ~1.6s to ~220ms.
## The lockfile is regenerated, not patched
Worth knowing, since it is most of the diff. `vitest` depends on vite itself, and an incremental `npm install` was content to leave 6.4.3 hoisted at the root for vitest while `web` built against 8.3.0 — two vite majors in one tree, with tests transforming through a different bundler than the build. `npm dedupe` would not collapse it. A clean install resolves to a single vite 8.3.0, which is why the lock is rebuilt from scratch.
## Node minimum
vite 8 requires `^20.19.0 || >=22.12.0`, above the `>=20.10` that `package.json` engines and the README both promised. Both now say 20.19, since building from source on 20.10 genuinely no longer works. CI, the Dockerfile and the published image are all on Node 22 and were never affected.
## One thing surfaced, not fixed
Rolldown warns that two modules are imported both statically and dynamically, so the dynamic import cannot move them into their own chunk:
```
src/store/sieve.ts is dynamically imported by src/store/mail.ts but also statically imported by
src/App.tsx, src/views/mail/FilterFromMessage.tsx, src/views/settings/FiltersSettings.tsx
src/lib/notify.ts is dynamically imported by src/App.tsx, src/store/mail.ts but also statically
imported by src/App.tsx, src/views/settings/NotificationsSettings.tsx
```
That is a property of the source rather than of the bundler — `store/sieve.ts` has three static importers against the one dynamic import at `store/mail.ts:1429` — and rolldown is only saying out loud what rollup did quietly. Left alone here; it belongs in its own change if the split is worth having.
## Verification
`npm run typecheck`, `npm test` (1153 web tests across 113 files, 168 server tests) and `npm run build` all pass, `npm audit` is clean, and I ran `npm ci` from the regenerated lock plus a full `docker build` locally — the image builds on `node:22-alpine` as CI will.
**Merged** 2026-09-10 as coffey-labs/ihasmail@136c754bcd48
<sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Supersedes #319 (
@vitejs/plugin-react4.7.0 → 6.1.1), which fails CI on its own. This carries the plugin bump with the vite major it needs, so #319 should close when this lands.The prerequisite was vite 8, not vite 7
@vitejs/[email protected]peers onvite: ^8.0.0and nothing lower — its other peers (oxc-transform-react,@rolldown/plugin-babel,babel-plugin-react-compiler) are all optional. That is why #319 died at startup withERR_PACKAGE_PATH_NOT_EXPORTED: './internal' is not defined by "exports" in vite/package.json: it was loading a vite 6 that has no such subpath.So the build moves 6.4.3 → 8.3.0 (current
latest; vite 7 isprevious).[email protected]already peers^6 || ^7 || ^8, so it needed nothing.What vite 8 changed for us
It bundles with rolldown instead of rollup, which accounts for the config work:
manualChunkslost its object form. Naming a chunk and listing the packages in it is not supported; rolldown takescodeSplitting.groups, each with anameand atestmatched against module paths. The same two chunks come out —vendorandicons— andiconsis listed first because groups are tried in order and the first match wins.rollupOptionsis now a deprecated alias forrolldownOptions, so it is spelled the new way. Purely cosmetic: the chunk hashes were byte-identical before and after the rename.Verified the chunking still does what it did:
vendor-BybD4nxw.js(60.9 kB) still carries dompurify — I grepped the built chunk for3.4.15— andicons-L_xczE6n.js(38.2 kB) is still lucide on its own. There is one newrolldown-runtimechunk, which is rolldown's own helper. Build time drops from ~1.6s to ~220ms.The lockfile is regenerated, not patched
Worth knowing, since it is most of the diff.
vitestdepends on vite itself, and an incrementalnpm installwas content to leave 6.4.3 hoisted at the root for vitest whilewebbuilt against 8.3.0 — two vite majors in one tree, with tests transforming through a different bundler than the build.npm dedupewould not collapse it. A clean install resolves to a single vite 8.3.0, which is why the lock is rebuilt from scratch.Node minimum
vite 8 requires
^20.19.0 || >=22.12.0, above the>=20.10thatpackage.jsonengines and the README both promised. Both now say 20.19, since building from source on 20.10 genuinely no longer works. CI, the Dockerfile and the published image are all on Node 22 and were never affected.One thing surfaced, not fixed
Rolldown warns that two modules are imported both statically and dynamically, so the dynamic import cannot move them into their own chunk:
That is a property of the source rather than of the bundler —
store/sieve.tshas three static importers against the one dynamic import atstore/mail.ts:1429— and rolldown is only saying out loud what rollup did quietly. Left alone here; it belongs in its own change if the split is worth having.Verification
npm run typecheck,npm test(1153 web tests across 113 files, 168 server tests) andnpm run buildall pass,npm auditis clean, and I rannpm cifrom the regenerated lock plus a fulldocker buildlocally — the image builds onnode:22-alpineas CI will.Merged 2026-09-10 as coffey-labs/ihasmail@136c754bcd
Rebuilt from: git history, session transcript.