Files
ihasmail-inbuxa/web/vite.config.ts
T
jcoffey-dev bb25355c23 AGPL source offer and name cleanup
Every build writes the exact source it was built from, uncommitted work and
new files included, as source.tar.gz next to the app, named after that tree.
Docker builds, which have no git, pack the build context and name it by a
hash of its files. The sign-in page and Settings > About link to it instead of
a repository that can drift.

What users, operators and packagers see no longer names the upstream server:
- interface text, in all nine catalogues, with a token-session line for
  Security;
- server messages;
- the settings, now MAIL_SERVER_URL, MAIL_SERVERS_FILE, ADMIN_URL and
  MAIL_SERVER_FOLLOW_ADVERTISED_URLS, and mail-servers.example.json;
- the Tenants notice, which is gone;
- the README, CONTRIBUTING and SECURITY.

ihasmail's own FEATURES, KNOWN-ISSUES and ROADMAP stay with public ihasmail,
and INBUXA.md is folded into the README.
2026-09-19 00:13:12 -07:00

128 lines
4.9 KiB
TypeScript

import { defineConfig, type Plugin } from "vitest/config";
import react from "@vitejs/plugin-react";
import { fileURLToPath, URL } from "node:url";
import { resolveVersion } from "../scripts/version.mjs";
import { baseUrlOf } from "../scripts/basePath.mjs";
import { sourceIdentity, writeSourceArchive } from "../scripts/source-archive.mjs";
// Resolved here, at build time: the browser has no git to ask, and neither does
// the Docker build, which is handed the answer as IHASMAIL_VERSION instead.
const version = resolveVersion();
/*
* ihasmail-inbuxa: the AGPL's offer for this build. The whole project's source
* (web and server), exactly as built, goes into dist/source.tar.gz, and its
* identity into the app so the download link can name it. See
* scripts/source-archive.mjs.
*/
const projectRoot = fileURLToPath(new URL("..", import.meta.url));
const source = sourceIdentity(projectRoot);
function sourceArchive(): Plugin {
return {
name: "inbuxa-source-archive",
apply: "build",
closeBundle() {
writeSourceArchive(projectRoot, fileURLToPath(new URL("./dist/source.tar.gz", import.meta.url)), "ihasmail-inbuxa", source);
},
};
}
/*
* Where the app is mounted. Unlike everything else ihasmail is told, this one
* cannot wait until the process starts: the hashed asset URLs are written into
* index.html when the bundle is built, so a build that does not know its prefix
* emits `/assets/...` and the shell 404s under `/mail/`. So `BASE_PATH` is read
* at build time here as well as at run time in the server, and the Dockerfile
* carries one value into both.
*
* Vite wants the directory form with the trailing slash, and hands it back to
* the app as `import.meta.env.BASE_URL` -- which is where `lib/basePath.ts`
* gets it, so the browser never has to be told separately.
*/
const base = baseUrlOf(process.env.BASE_PATH);
/*
* Every file the build made, written into the app page for the service worker.
*
* The page names only what it loads at start; the composer, settings, viewers
* and the rest arrive when first used, and after each deploy that first use
* went back to the server. With the whole list in the page, the worker can
* fetch them in the background once a new version is seen, and knows to keep
* them. Language catalogs are listed apart: a reader wants one of them, which
* is cached when it is first loaded.
*
* An inert JSON block rather than prefetch links, which the browser would
* fetch on every load.
*/
function assetList(): Plugin {
return {
name: "ihasmail-asset-list",
apply: "build",
transformIndexHtml: {
order: "post",
handler(html, ctx) {
if (!ctx.bundle) return html;
const precache: string[] = [];
const onDemand: string[] = [];
for (const file of Object.values(ctx.bundle)) {
if (!file.fileName.startsWith("assets/") || file.fileName.endsWith(".map")) continue;
const catalog = file.type === "chunk" && file.moduleIds.length > 0 && file.moduleIds.every((id) => /[\\/]src[\\/]locales[\\/][^\\/]+\.ts$/.test(id));
(catalog ? onDemand : precache).push(`${base}${file.fileName}`);
}
const json = JSON.stringify({ precache: precache.sort(), onDemand: onDemand.sort() });
return html.replace("</body>", ` <script type="application/json" id="ihasmail-assets">${json}</script>\n </body>`);
},
},
};
}
export default defineConfig({
base,
plugins: [react(), assetList(), sourceArchive()],
define: { __IHASMAIL_VERSION__: JSON.stringify(version), __SOURCE_ID__: JSON.stringify(source.id) },
resolve: {
alias: { "@": fileURLToPath(new URL("./src", import.meta.url)) },
},
server: {
port: 5173,
proxy: {
// Under a prefix the dev server serves the app from `base`, so the app's
// API calls arrive here prefixed too. Forwarded whole, prefix included:
// the dev server behind this reads the same BASE_PATH and expects it.
[`${base}api`]: {
target: "http://127.0.0.1:8080",
changeOrigin: false,
},
},
},
build: {
target: "es2022",
sourcemap: false,
rolldownOptions: {
output: {
/*
* Rolldown, which vite 8 bundles with, dropped the object form of
* `manualChunks` -- naming a chunk and listing the packages in it --
* and takes groups matched against module paths instead. Same two
* chunks out the other end; `icons` is listed first because groups are
* tried in order and the first match wins.
*/
codeSplitting: {
groups: [
{ name: "icons", test: /node_modules[\\/]lucide-react[\\/]/ },
{
name: "vendor",
test: /node_modules[\\/](wouter|zustand|dompurify|@tanstack[\\/]react-virtual)[\\/]/,
},
],
},
},
},
},
test: {
environment: "jsdom",
include: ["src/**/*.test.ts", "src/**/*.test.tsx"],
},
});