mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 06:10:42 +00:00
a5c346bd4a
## Description: It first sends the manifest to the worker to get a list of missing files, then for each missing file it uploads them to r2 via cf worker. This PR also has us write out the manifest in plan json instead of an mjs file. This makes it easier for the shell script to parse ## Please complete the following: - [x] I have added screenshots for all UI updates - [ ] I process any text displayed to the user through translateText() and I've added it to the en.json file - [ ] I have added relevant tests to the test directory - [ ] 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
211 lines
6.5 KiB
TypeScript
211 lines
6.5 KiB
TypeScript
import tailwindcss from "@tailwindcss/vite";
|
|
import fs from "fs";
|
|
import { lookup as lookupMime } from "mrmime";
|
|
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,
|
|
writePublicAssetManifest,
|
|
} 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(
|
|
proprietaryDir: string,
|
|
resourcesDir: string,
|
|
): Plugin {
|
|
return {
|
|
name: "serve-proprietary-dir",
|
|
configureServer(server) {
|
|
// Must run before Vite's htmlFallback; skip when resources/ has the file
|
|
// so publicDir keeps precedence.
|
|
server.middlewares.use((req, res, next) => {
|
|
if (!req.url) return next();
|
|
const rel = decodeURIComponent(
|
|
new URL(req.url, "http://x").pathname,
|
|
).replace(/^\//, "");
|
|
if (rel.includes("..")) return next();
|
|
if (fs.existsSync(path.join(resourcesDir, rel))) return next();
|
|
const filePath = path.join(proprietaryDir, rel);
|
|
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile())
|
|
return next();
|
|
const mime = lookupMime(filePath);
|
|
if (mime) res.setHeader("Content-Type", mime);
|
|
res.setHeader("Cache-Control", "no-store");
|
|
fs.createReadStream(filePath).pipe(res);
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
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);
|
|
writePublicAssetManifest(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, resourcesDir)]
|
|
: []),
|
|
...(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,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
});
|