124 lines
5.5 KiB
TypeScript
124 lines
5.5 KiB
TypeScript
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 { fileURLToPath } from "node:url";
|
|
|
|
import { resolveAssetBase } from "./app/utils/build/assetCdnBase";
|
|
import { shouldSuppressBuildWarning } from "./app/utils/build/buildWarningTriage";
|
|
import { getChunkFileName, getManualChunkName } from "./app/utils/build/manualChunks";
|
|
import { getWebBuildDirectory, getWebTestBuildSurfaceFallback, requireWebBuildSurface } from "./build-surface";
|
|
|
|
const webBuildSurface = requireWebBuildSurface(process.env, getWebTestBuildSurfaceFallback());
|
|
const webBuildDirectory = getWebBuildDirectory(webBuildSurface);
|
|
const rootClientSurfaceModule = fileURLToPath(new URL(`./app/root-${webBuildSurface}-surface.tsx`, import.meta.url));
|
|
const entryRuntimeBootstrapModule = fileURLToPath(new URL(`./app/entry-runtime-bootstrap-${webBuildSurface}.server.ts`, import.meta.url));
|
|
|
|
// Bundle visualizer (#71) — opt-in via ANALYZE_BUNDLE=true so the production
|
|
// build keeps its current footprint. Outputs an interactive treemap to
|
|
// the selected web build directory and 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: `${webBuildDirectory}/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 HIVEGRID_BUILD_SOURCEMAPS=1 before uploading the
|
|
// client maps as an artifact for the reusable Sentry release job.
|
|
const buildSourcemapsEnabled = process.env.HIVEGRID_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 ?? "hivegrid-web",
|
|
release: process.env.SENTRY_RELEASE ? { name: process.env.SENTRY_RELEASE } : undefined,
|
|
sourcemaps: {
|
|
assets: `./${webBuildDirectory}/**`,
|
|
filesToDeleteAfterUpload: `./${webBuildDirectory}/**/*.map`,
|
|
},
|
|
// Telemetry off — we self-host and never want to phone home.
|
|
telemetry: false,
|
|
}),
|
|
]
|
|
: [];
|
|
|
|
export default defineConfig(({ mode }) => ({
|
|
define: {
|
|
// Compile the selected runtime surface into every route module. Direct
|
|
// comparisons against this value let Rollup remove the opposite surface,
|
|
// including server-only lazy chunks, from the physical build artifact.
|
|
"import.meta.env.VITE_HIVEGRID_BUILD_SURFACE": JSON.stringify(webBuildSurface),
|
|
},
|
|
// 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),
|
|
// The Customer Portal has a deliberately tiny, separately staged public
|
|
// tree. Never let Vite copy Core service-worker, embeds, legal assets, or
|
|
// full locale catalogs into the portal artifact.
|
|
publicDir: webBuildSurface === "portal" ? false : "public",
|
|
resolve: {
|
|
alias: {
|
|
"./root-client-surface": rootClientSurfaceModule,
|
|
"./entry-runtime-bootstrap.server": entryRuntimeBootstrapModule,
|
|
"@hivegrid/queue/runtime": "/dev/null",
|
|
},
|
|
},
|
|
server: { port: 3000 },
|
|
plugins: [envOnlyMacros(), tailwindcss(), reactRouter(), tsconfigPaths(), ...sentryPlugin, ...visualizerPlugin],
|
|
ssr: {
|
|
noExternal: ["minimatch"],
|
|
},
|
|
optimizeDeps: {
|
|
esbuildOptions: {
|
|
target: "esnext",
|
|
},
|
|
},
|
|
build: {
|
|
target: "esnext",
|
|
// Monaco is an explicitly lazy, self-hosted feature runtime. Its raw and
|
|
// gzip sizes are enforced by verify-bundle-size instead of emitting the
|
|
// same generic Vite warning on every build.
|
|
chunkSizeWarningLimit: 4_000,
|
|
// 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,
|
|
chunkFileNames: ({ moduleIds }) => getChunkFileName(moduleIds),
|
|
},
|
|
onwarn(warning, defaultHandler) {
|
|
if (shouldSuppressBuildWarning(warning)) {
|
|
return;
|
|
}
|
|
|
|
defaultHandler(warning);
|
|
},
|
|
},
|
|
},
|
|
}));
|