Files
OpenFrontIO/vite.config.ts
T
Evan 1ebac8e854 Move brand images to proprietary/ and support multi-dir asset pipeline (#3662)
## Description:

* Move proprietary brand images (logos, favicon) from resources/images/
to proprietary/images/ to separate open-source assets from proprietary
ones
* Extend the asset pipeline (PublicAssetManifest, vite.config.ts) to
support multiple source directories (resources/ + proprietary/), so
buildAssetUrl resolves assets from either location transparently
* In dev, serve proprietary/ as a fallback middleware (registered after
Vite's publicDir handler) so resources/ takes precedence when files
exist in both. The idea is we could have placeholder assets placeholders
that can be used by forks, and only the production build uses
proprietary assets.

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

evan
2026-04-16 08:42:11 -07:00

209 lines
6.5 KiB
TypeScript

import tailwindcss from "@tailwindcss/vite";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
import { defineConfig, loadEnv, type Plugin } from "vite";
import { createHtmlPlugin } from "vite-plugin-html";
import tsconfigPaths from "vite-tsconfig-paths";
import { type AssetManifest, buildAssetUrl } from "./src/core/AssetUrls";
import {
buildPublicAssetManifest,
copyRootPublicFiles,
createHashedPublicAssetFiles,
getProprietaryDir,
getResourcesDir,
writePublicAssetManifestModule,
} from "./src/server/PublicAssetManifest";
// Vite already handles these, but its good practice to define them explicitly
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
function serveProprietaryDir(dir: string): Plugin {
const resolvedDir = path.resolve(dir) + path.sep;
return {
name: "serve-proprietary-dir",
configureServer(server) {
// Return a function so the middleware is registered after Vite's internal
// static-file handler (publicDir). This makes proprietary/ a fallback
// rather than taking precedence over resources/.
return () => {
server.middlewares.use((req, res, next) => {
if (!req.url) return next();
const urlPath = new URL(req.url, "http://localhost").pathname;
const filePath = path.resolve(
dir,
decodeURIComponent(urlPath).replace(/^\//, ""),
);
if (!filePath.startsWith(resolvedDir)) return next();
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
res.setHeader("Cache-Control", "no-cache");
fs.createReadStream(filePath).pipe(res);
} else {
next();
}
});
};
},
};
}
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "");
const isProduction = mode === "production";
const resourcesDir = getResourcesDir(__dirname);
const proprietaryDir = getProprietaryDir(__dirname);
const sourceDirs = [resourcesDir, proprietaryDir];
const assetManifest: AssetManifest = isProduction
? buildPublicAssetManifest(sourceDirs)
: {};
const htmlAssetData = {
assetManifest: JSON.stringify(assetManifest),
gameEnv: JSON.stringify(env.GAME_ENV ?? "dev"),
manifestHref: buildAssetUrl("manifest.json", assetManifest),
faviconHref: buildAssetUrl("images/Favicon.svg", assetManifest),
gameplayScreenshotUrl: buildAssetUrl(
"images/GameplayScreenshot.png",
assetManifest,
),
backgroundImageUrl: buildAssetUrl("images/background.webp", assetManifest),
desktopLogoImageUrl: buildAssetUrl("images/OpenFront.png", assetManifest),
mobileLogoImageUrl: buildAssetUrl("images/OF.png", assetManifest),
};
const syncHashedPublicAssets = () => ({
name: "sync-hashed-public-assets",
apply: "build" as const,
closeBundle() {
const outDir = path.join(__dirname, "static");
copyRootPublicFiles(resourcesDir, outDir);
createHashedPublicAssetFiles(sourceDirs, outDir, assetManifest);
writePublicAssetManifestModule(outDir, assetManifest);
},
});
// In dev, redirect visits to /w*/game/* to "/" so Vite serves the index.html.
const devGameHtmlBypass = (req?: {
url?: string;
method?: string;
headers?: { accept?: string | string[] };
}) => {
if (req?.method !== "GET") return undefined;
const accept = req.headers?.accept;
const acceptValue = Array.isArray(accept)
? accept.join(",")
: (accept ?? "");
if (!acceptValue.includes("text/html")) return undefined;
if (!req.url) return undefined;
if (/^\/w\d+\/game\/[^/]+/.test(req.url)) {
return "/";
}
return undefined;
};
return {
test: {
globals: true,
environment: "jsdom",
setupFiles: "./tests/setup.ts",
},
root: "./",
base: "/",
publicDir: isProduction ? false : "resources",
resolve: {
alias: {
"protobufjs/minimal": path.resolve(
__dirname,
"node_modules/protobufjs/minimal.js",
),
resources: path.resolve(__dirname, "resources"),
},
},
plugins: [
tsconfigPaths(),
...(!isProduction ? [serveProprietaryDir(proprietaryDir)] : []),
...(isProduction
? []
: [
createHtmlPlugin({
minify: false,
entry: "/src/client/Main.ts",
template: "index.html",
inject: {
data: {
gitCommit: JSON.stringify("DEV"),
...htmlAssetData,
},
},
}),
]),
...(isProduction ? [syncHashedPublicAssets()] : []),
tailwindcss(),
],
define: {
__ASSET_MANIFEST__: JSON.stringify(assetManifest),
"process.env.WEBSOCKET_URL": JSON.stringify(
isProduction ? "" : "localhost:3000",
),
"process.env.GAME_ENV": JSON.stringify(isProduction ? "prod" : "dev"),
"process.env.STRIPE_PUBLISHABLE_KEY": JSON.stringify(
env.STRIPE_PUBLISHABLE_KEY,
),
"process.env.API_DOMAIN": JSON.stringify(env.API_DOMAIN),
// Add other process.env variables if needed, OR migrate code to import.meta.env
},
build: {
outDir: "static", // Webpack outputs to 'static', assuming we want to keep this.
emptyOutDir: true,
assetsDir: "assets", // Sub-directory for assets
rollupOptions: {
output: {
manualChunks: {
vendor: ["pixi.js", "howler", "zod", "protobufjs"],
},
},
},
},
server: {
port: 9000,
// Automatically open the browser when the server starts
open: process.env.SKIP_BROWSER_OPEN !== "true",
proxy: {
"/lobbies": {
target: "ws://localhost:3000",
ws: true,
changeOrigin: true,
},
// Worker proxies
"/w0": {
target: "ws://localhost:3001",
ws: true,
secure: false,
changeOrigin: true,
bypass: (req) => devGameHtmlBypass(req),
rewrite: (path) => path.replace(/^\/w0/, ""),
},
"/w1": {
target: "ws://localhost:3002",
ws: true,
secure: false,
changeOrigin: true,
bypass: (req) => devGameHtmlBypass(req),
rewrite: (path) => path.replace(/^\/w1/, ""),
},
// API proxies
"/api": {
target: "http://localhost:3000",
changeOrigin: true,
secure: false,
},
},
},
};
});