Example Vite Build Configuration
Vite Build Configuration for Terminal Application
/**
* Vite Configuration for the React App
*
* This configuration file sets up the Vite build and development environment for the
* React application, which is integrated into a Django project. It handles:
*
* - Building React assets with Vite and outputting them to the Django static directory for collectstatic.
* - Injecting custom build metadata (version, build time, environment) into the manifest for Django use.
* - Optionally deploying built assets to S3 and invalidating CloudFront for production CDN usage.
* - Proxying API and static asset requests to the Django development server during local development.
* - Optimizing caching by bundling xterm.js separately from the main app code.
* - Removing console.debug statements from production builds to avoid leaking sensitive info.
*
* Usage:
* - For development, run the Vite dev server. Static and API requests are proxied to Django.
* - For production, build assets with Vite. Output is placed in Django's static directory and can be deployed to S3/CDN.
*
* Integration:
* - The manifest.json is used by Django templatetags to resolve hashed asset filenames for cache busting.
* - The configuration supports both local and CDN-based static file serving.
*
* See README.md for more details on development and deployment workflows.
*/
import { defineConfig, type ConfigEnv, type PluginOption } from "vite";
import { execSync } from "child_process";
import react from "@vitejs/plugin-react";
import fs from "fs";
import path from "path";
import packageJson from "./package.json" with { type: "json" };
const packageName = packageJson.name;
/**
* Vite Plugin: addCustomManifestData
*
* This plugin injects custom metadata into the generated manifest.json file after each build.
* The metadata includes:
* - buildTime: ISO timestamp of the build
* - version: The version from package.json
* - config: The config object from package.json
* - buildEnv: The current NODE_ENV or 'development'
*
* This information is used by Django to display build details and for debugging purposes.
*/
const addCustomManifestData: PluginOption = {
name: "add-custom-manifest-data",
writeBundle() {
const manifestPath = path.resolve(__dirname, `../../smarter/static/react/${packageName}/manifest.json`);
if (fs.existsSync(manifestPath)) {
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
manifest._custom = {
buildTime: new Date().toISOString(),
version: packageJson.version,
config: packageJson.config,
buildEnv: process.env.NODE_ENV || "development",
};
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
}
},
};
/**
* Vite Plugin: postBuildPlugin
*
* After each build, this plugin optionally uploads the built assets to S3 and triggers a CloudFront invalidation,
* ensuring the latest files are served in production. This workflow is enabled by the `cdnDeploy` flag in package.json
* and allows Docker images to skip React build tools while supporting CDN-based static file serving.
*/
const postBuildPlugin: PluginOption = {
name: "post-build",
closeBundle() {
if (packageJson.config.cdnDeploy === true) {
execSync(
`aws s3 sync ../../../smarter/static/react/${packageName} ${packageJson.config.s3BucketPath} --acl public-read --delete`,
{ stdio: "inherit" },
);
execSync(
`aws --no-cli-pager cloudfront create-invalidation --distribution-id ${packageJson.config.cloudfrontDistributionId} --paths '/react/${packageName}/*'`,
{ stdio: "inherit" },
);
}
},
};
/**
* Main Vite Configuration Export
*
* This function exports the Vite configuration for the React app, dynamically adjusting
* settings based on the build command (development or production). It sets up plugins, build output,
* asset handling, and development server proxying to integrate seamlessly with the Django backend.
*
* Key features:
* - Uses custom plugins for manifest metadata and optional CDN deployment
* - Removes console.debug in production builds
* - Outputs assets to Django's static directory for collectstatic
* - Proxies API and static requests to Django during development
*/
export default defineConfig(({ command }: ConfigEnv) => ({
plugins: [react(), postBuildPlugin, addCustomManifestData],
// We use esbuild to remove console.debug statements in production builds
// in order to avoid leaking potentially sensitive information in
// production environments.
esbuild: {
pure: ["console.debug"],
},
// Builds are also saved into the Django static directory so that these
// files can be included in the Django collectstatic process and served by
// Django at runtime in local development environments. For development
// we need to be able to support serving these files both from the Vite
// dev server as well as the Django dev server. We set the base to '/'
// so that Vite's dev server can serve these files. Separately, we persist
// the actual build files to the Django static directory and set up a proxy
// in the Vite dev server to forward requests to the Django dev server.
base: command === "serve" ? "/" : `/static/react/${packageName}/`,
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
build: {
minify: "esbuild" as const,
// ------------------------------------------------------------------------
// The manifest is needed for hosting builds from Django (both dev and prod).
// It is used by Django templatetags to determine the correct file names to include
// in the HTML template. This is necessary because Vite includes a
// hash in the file names for cache busting.
// ------------------------------------------------------------------------
manifest: "manifest.json",
// ------------------------------------------------------------------------
// we're placing our build output in the primary Django static directory so
// that these files are automatically included in the Django collectstatic
// process and served by Django at runtime.
//
// In development, we rely on Vite's dev server to serve these files, so we
// set the outDir to a directory that is not used by the Django dev server.
// ------------------------------------------------------------------------
outDir: `../../../smarter/static/react/${packageName}`,
emptyOutDir: true,
// ------------------------------------------------------------------------
// We want to bundle xterm.js and its addons separately from the rest of the
// application code in order to optimize caching. This way, if we make changes
// to our application code, the xterm.js bundle can still be cached by the
// browser and won't need to be re-downloaded.
// ------------------------------------------------------------------------
rollupOptions: {
output: {
entryFileNames: "assets/[name]-[hash].js",
chunkFileNames: "assets/[name]-[hash].js",
assetFileNames: "assets/[name]-[hash][extname]",
manualChunks(id: string) {
if (id.includes("node_modules/xterm") || id.includes("node_modules/@xterm")) {
return "xterm";
}
return undefined;
},
},
},
},
// Django collects static files and serves them from /static/
// We need to create proxy servers in React's dev environment
// so that these requests are served from the Django dev server instead
// of the React dev server.
//
// Most of these cases stem from <link> elements added to this index.html
// containing platform-wide stylesheets and scripts that originate from
// and are served by the Django dev server. These are added to index.html
// in order to keep this React dev environment as close to the runtime
// environment as possible.
server: {
proxy: {
"/api": "http://localhost:9357",
"/assets": {
target: "http://localhost:9357", // Django dev server
changeOrigin: true,
rewrite: (path: string) => `/static${path}`,
},
"/common-styles.css": {
target: "http://localhost:9357",
changeOrigin: true,
rewrite: (path: string) => `/static${path}`,
},
"/dashboard/": "http://localhost:9357",
[`/static/react/${packageName}/`]: {
target: "http://localhost:5173",
changeOrigin: true,
rewrite: (path: string) => path.replace(new RegExp(`^/static/react/${packageName}/`), "/"),
},
"/static": {
target: "http://localhost:9357",
changeOrigin: true,
},
"/workbench/": "http://localhost:9357",
},
},
}));