Move the build to vite 8, and chunk the way rolldown wants #322

Closed
opened 2026-09-10 15:04:03 +00:00 by jcoffey-dev · 0 comments
Owner

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@136c754bcd

Rebuilt from: git history, session transcript.

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.