import { reactRouter } from "@react-router/dev/vite"; import { sentryVitePlugin } from "@sentry/vite-plugin"; import tailwindcss from "@tailwindcss/vite"; import { visualizer } from "rollup-plugin-visualizer"; import { defineConfig, type PluginOption } from "vite"; import { envOnlyMacros } from "vite-env-only"; import tsconfigPaths from "vite-tsconfig-paths"; import { resolveAssetBase } from "./app/utils/build/assetCdnBase"; import { shouldSuppressBuildWarning } from "./app/utils/build/buildWarningTriage"; import { getManualChunkName } from "./app/utils/build/manualChunks"; // Bundle visualizer (#71) — opt-in via ANALYZE_BUNDLE=true so the production // build keeps its current footprint. Outputs an interactive treemap to // apps/web/build/stats.html that includes both raw and gzip sizes per chunk. // See docs/architecture/bundle-budgets.md for the inspection workflow. const visualizerPlugin: PluginOption[] = process.env.ANALYZE_BUNDLE === "true" ? [ visualizer({ filename: "build/stats.html", template: "treemap", gzipSize: true, brotliSize: true, open: false, emitFile: false, }) as PluginOption, ] : []; // Source-map generation is opt-in so bundle-budget and local production builds // do not spend time writing artifacts that only the Sentry release path needs. // CI quality builds set TOOLCTRL_BUILD_SOURCEMAPS=1 before uploading the // client maps as an artifact for the reusable Sentry release job. const buildSourcemapsEnabled = process.env.TOOLCTRL_BUILD_SOURCEMAPS === "1"; // Source-map upload is gated on both SENTRY_AUTH_TOKEN and source-map // generation, so local builds without a token build successfully and tokened // builds do not try to upload maps unless the release lane explicitly opted in. // The plugin is added in the dedicated CI/production release path (see // docs/improvement-plan/02-observability/sentry-wiring-web.md, Step 9). const sentryPlugin: PluginOption[] = process.env.SENTRY_AUTH_TOKEN && buildSourcemapsEnabled ? [ sentryVitePlugin({ authToken: process.env.SENTRY_AUTH_TOKEN, org: process.env.SENTRY_ORG, project: process.env.SENTRY_PROJECT ?? "boilerplate-web", release: process.env.SENTRY_RELEASE ? { name: process.env.SENTRY_RELEASE } : undefined, sourcemaps: { assets: "./build/**", filesToDeleteAfterUpload: "./build/**/*.map", }, // Telemetry off — we self-host and never want to phone home. telemetry: false, }), ] : []; export default defineConfig(({ mode }) => ({ // Asset CDN base — when ASSET_CDN_URL is set the production build emits // asset URLs prefixed with the CDN origin (e.g. https://cdn.example.com/assets/app-3f4a1b.js). // Local dev intentionally ignores the var so the Vite dev server keeps serving from `/`. // See docs/improvement-plan/03-scaling-infrastructure/asset-cdn.md and docs/ops/asset-cdn.md. base: resolveAssetBase(process.env, mode), resolve: { alias: { "@toolctrl/queue/runtime": "/dev/null", }, }, server: { port: 3000 }, plugins: [envOnlyMacros(), tailwindcss(), reactRouter(), tsconfigPaths(), ...sentryPlugin, ...visualizerPlugin], ssr: { noExternal: ["minimatch"], }, optimizeDeps: { esbuildOptions: { target: "esnext", }, }, build: { target: "esnext", chunkSizeWarningLimit: 1800, // Opt-in for Sentry release lanes; bundle-budget and local production // builds keep this off for faster builds and smaller temporary output. sourcemap: buildSourcemapsEnabled, rollupOptions: { output: { manualChunks: getManualChunkName, }, onwarn(warning, defaultHandler) { if (shouldSuppressBuildWarning(warning)) { return; } defaultHandler(warning); }, }, }, }));