Add versioned asset URL infrastructure

This commit is contained in:
scamiv
2026-03-22 01:53:38 +01:00
parent bf09b9c9be
commit 51eff2621e
4 changed files with 128 additions and 11 deletions
+7 -11
View File
@@ -7,8 +7,8 @@
content="width=device-width, initial-scale=1.0, viewport-fit=cover"
/>
<title data-i18n="main.title">OpenFront (ALPHA)</title>
<link rel="manifest" href="/manifest.json" />
<link rel="icon" type="image/svg+xml" href="/images/Favicon.svg" />
<link rel="manifest" href="<%- manifestHref %>" />
<link rel="icon" type="image/svg+xml" href="<%- faviconHref %>" />
<!-- Preload styles -->
<style>
@@ -50,16 +50,14 @@
property="og:description"
content="Conquer the world in this multiplayer battle royale! Expand your nation, eliminate opponents, and dominate the map in this fast-paced IO game."
/>
<meta
property="og:image"
content="https://openfront.io/images/GameplayScreenshot.png"
/>
<meta property="og:image" content="<%- gameplayScreenshotUrl %>" />
<meta property="og:type" content="game" />
<!-- Injected from Server env -->
<script>
window.GIT_COMMIT = <%- gitCommit %>;
window.INSTANCE_ID = <%- instanceId %>;
window.ASSET_BASE_PATH = <%- assetBasePath %>;
</script>
<!-- CrazyGames SDK -->
@@ -126,21 +124,19 @@
<div
id="background-layer"
class="absolute inset-0 bg-cover bg-center opacity-30 [filter:brightness(1.0)] dark:[filter:sepia(0.2)_saturate(1.2)_hue-rotate(180deg)_brightness(0.9)]"
style="
background-image: url(&quot;/resources/images/background.webp&quot;);
"
style="background-image: url(&quot;<%- backgroundImageUrl %>&quot;)"
></div>
<div
class="absolute inset-0 bg-center bg-no-repeat bg-contain hidden lg:block"
style="
background-image: url(&quot;/resources/images/OpenFront.webp&quot;);
background-image: url(&quot;<%- desktopLogoImageUrl %>&quot;);
opacity: 0.5;
"
></div>
<div
class="absolute inset-0 bg-center bg-no-repeat bg-contain lg:hidden"
style="
background-image: url(&quot;/resources/images/OF.webp&quot;);
background-image: url(&quot;<%- mobileLogoImageUrl %>&quot;);
opacity: 0.5;
"
></div>
+46
View File
@@ -0,0 +1,46 @@
export function normalizeAssetVersion(
version: string | null | undefined,
): string | null {
const trimmed = version?.trim();
if (!trimmed || trimmed === "DEV" || trimmed === "undefined") {
return null;
}
return trimmed;
}
export function buildVersionedAssetBasePath(
version: string | null | undefined,
): string {
const normalized = normalizeAssetVersion(version);
return normalized ? `/_assets/${encodeURIComponent(normalized)}` : "";
}
export function buildAssetUrl(
path: string,
assetBasePath: string = "",
): string {
const normalizedPath = path.replace(/^\/+/, "");
if (!assetBasePath) {
return `/${normalizedPath}`;
}
return `${assetBasePath}/${normalizedPath}`;
}
declare global {
var __ASSET_BASE_PATH__: string | undefined;
interface Window {
ASSET_BASE_PATH?: string;
}
}
export function getAssetBasePath(): string {
if (typeof window !== "undefined" && window.ASSET_BASE_PATH !== undefined) {
return window.ASSET_BASE_PATH;
}
return globalThis.__ASSET_BASE_PATH__ ?? "";
}
export function assetUrl(path: string): string {
return buildAssetUrl(path, getAssetBasePath());
}
+12
View File
@@ -1,12 +1,24 @@
import ejs from "ejs";
import type { Response } from "express";
import fs from "fs/promises";
import { buildAssetUrl, buildVersionedAssetBasePath } from "../core/AssetUrls";
export async function renderHtmlContent(htmlPath: string): Promise<string> {
const htmlContent = await fs.readFile(htmlPath, "utf-8");
const assetBasePath = buildVersionedAssetBasePath(process.env.GIT_COMMIT);
return ejs.render(htmlContent, {
gitCommit: JSON.stringify(process.env.GIT_COMMIT ?? "undefined"),
instanceId: JSON.stringify(process.env.INSTANCE_ID ?? "undefined"),
assetBasePath: JSON.stringify(assetBasePath),
manifestHref: buildAssetUrl("manifest.json", assetBasePath),
faviconHref: buildAssetUrl("images/Favicon.svg", assetBasePath),
gameplayScreenshotUrl: buildAssetUrl(
"images/GameplayScreenshot.png",
assetBasePath,
),
backgroundImageUrl: buildAssetUrl("images/background.webp", assetBasePath),
desktopLogoImageUrl: buildAssetUrl("images/OpenFront.webp", assetBasePath),
mobileLogoImageUrl: buildAssetUrl("images/OF.webp", assetBasePath),
});
}
+63
View File
@@ -1,10 +1,16 @@
import tailwindcss from "@tailwindcss/vite";
import fs from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";
import { defineConfig, loadEnv } from "vite";
import { createHtmlPlugin } from "vite-plugin-html";
import { viteStaticCopy } from "vite-plugin-static-copy";
import tsconfigPaths from "vite-tsconfig-paths";
import {
buildAssetUrl,
buildVersionedAssetBasePath,
normalizeAssetVersion,
} from "./src/core/AssetUrls";
// Vite already handles these, but its good practice to define them explicitly
const __filename = fileURLToPath(import.meta.url);
@@ -13,6 +19,52 @@ const __dirname = path.dirname(__filename);
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "");
const isProduction = mode === "production";
const assetVersion = normalizeAssetVersion(
env.GIT_COMMIT ?? process.env.GIT_COMMIT,
);
const assetBasePath = buildVersionedAssetBasePath(assetVersion);
const htmlAssetData = {
assetBasePath: JSON.stringify(assetBasePath),
manifestHref: buildAssetUrl("manifest.json", assetBasePath),
faviconHref: buildAssetUrl("images/Favicon.svg", assetBasePath),
gameplayScreenshotUrl: buildAssetUrl(
"images/GameplayScreenshot.png",
assetBasePath,
),
backgroundImageUrl: buildAssetUrl("images/background.webp", assetBasePath),
desktopLogoImageUrl: buildAssetUrl("images/OpenFront.webp", assetBasePath),
mobileLogoImageUrl: buildAssetUrl("images/OF.webp", assetBasePath),
};
const rewriteVersionedManifest = () => ({
name: "rewrite-versioned-manifest",
apply: "build" as const,
async closeBundle() {
if (!assetVersion) {
return;
}
const manifestPath = path.join(
__dirname,
"static",
"_assets",
assetVersion,
"manifest.json",
);
const manifest = JSON.parse(await fs.readFile(manifestPath, "utf8")) as {
icons?: Array<{ src?: string }>;
};
manifest.icons = manifest.icons?.map((icon) => ({
...icon,
src: buildAssetUrl(icon.src ?? "", assetBasePath),
}));
await fs.writeFile(
manifestPath,
`${JSON.stringify(manifest, null, 2)}\n`,
);
},
});
// In dev, redirect visits to /w*/game/* to "/" so Vite serves the index.html.
const devGameHtmlBypass = (req?: {
url?: string;
@@ -65,22 +117,33 @@ export default defineConfig(({ mode }) => {
data: {
gitCommit: JSON.stringify("DEV"),
instanceId: JSON.stringify("DEV_ID"),
...htmlAssetData,
},
},
}),
]),
viteStaticCopy({
targets: [
...(assetVersion
? [
{
src: "resources/**/*",
dest: `_assets/${assetVersion}`,
},
]
: []),
{
src: "proprietary/*",
dest: ".",
},
],
}),
...(isProduction ? [rewriteVersionedManifest()] : []),
tailwindcss(),
],
define: {
__ASSET_BASE_PATH__: JSON.stringify(assetBasePath),
"process.env.WEBSOCKET_URL": JSON.stringify(
isProduction ? "" : "localhost:3000",
),