Files
vending-machine-game/vite.config.js
T
HamedandClaude Opus 5.5 bc6f472f08
CI/CD / test (push) Successful in 1m10s
CI/CD / publish (push) Successful in 1m20s
CI/CD / deploy (push) Successful in 6s
feat: embeddable CompanyGame with loader, poster, no-WebGL fallback and SEO
- CompanyGame bundles the store, a branded loader with start-up progress, an optional
  light poster (Enter the game) and a company-info fallback when WebGL is missing.
- startMode prop and ?mode= start straight in a mode; ?poster shows the poster first.
- Pixel ratio capped at 1.5 on touch devices and 1.75 on desktop (constants/graphics.js).
- Build writes the title, description, Organization JSON-LD and a hidden Persian
  summary into index.html from the company config.
- README explains iframe and component embedding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 13:26:30 +03:00

90 lines
3.8 KiB
JavaScript

import react from '@vitejs/plugin-react';
import { fileURLToPath } from 'node:url';
import { defineConfig } from 'vite';
import { readdirSync, statSync } from 'node:fs';
import { join, relative } from 'node:path';
import { createHash } from 'node:crypto';
import { seoHead, seoStructuredData, seoSummary } from './src/utils/seoSummary.js';
const listFiles = (dir) =>
readdirSync(dir).flatMap((name) => {
const path = join(dir, name);
return statSync(path).isDirectory() ? listFiles(path) : [path];
});
// Emits sw.js listing every built file, so the installed game works offline. The cache name is a
// hash of that list: a new build gets a new cache and the page offers to reload into it.
// Satellite tiles (16 MB) are not precached; they are kept as the player passes them.
function offlineServiceWorker() {
return {
name: 'offline-service-worker',
apply: 'build',
generateBundle(_, bundle) {
const files = [
...Object.keys(bundle),
...listFiles('public').map((f) => relative('public', f).replaceAll('\\', '/')),
].filter(
(f) =>
f !== 'sw.js' && !f.endsWith('.map') && !(f.startsWith('imagery/') && f.endsWith('.jpg')),
);
const version = createHash('sha1').update(files.join('\n')).digest('hex').slice(0, 12);
this.emitFile({
type: 'asset',
fileName: 'sw.js',
source: `// Generated by vite.config.js — do not edit.
const CACHE = 'robot-market-${version}';
const TILES = 'robot-market-imagery';
const FILES = ${JSON.stringify(['./', ...files.map((f) => `./${f}`)])};
self.addEventListener('install', event => event.waitUntil(caches.open(CACHE).then(cache => cache.addAll(FILES))));
self.addEventListener('activate', event => event.waitUntil(caches.keys().then(keys =>
Promise.all(keys.filter(k => k.startsWith('robot-market-') && k !== CACHE && k !== TILES).map(k => caches.delete(k)))).then(() => self.clients.claim())));
self.addEventListener('message', event => event.data === 'skip-waiting' && self.skipWaiting());
self.addEventListener('fetch', event => {
const request = event.request;
if (request.method !== 'GET' || new URL(request.url).origin !== location.origin) return;
// Pages: network first so updates arrive, cached page when offline. Everything else: cache first.
if (request.mode === 'navigate') {
event.respondWith(fetch(request).catch(() => caches.match('./')));
return;
}
// Satellite tiles: cache first, and keep each one fetched so visited areas work offline.
if (new URL(request.url).pathname.includes('/imagery/')) {
event.respondWith(caches.open(TILES).then(cache => cache.match(request).then(hit => hit ?? fetch(request).then(response => {
if (response.ok) cache.put(request, response.clone());
return response;
}))));
return;
}
event.respondWith(caches.match(request, {ignoreSearch: true}).then(hit => hit ?? fetch(request)));
});
`,
});
},
};
}
// Writes the company's title, description, structured data and a hidden Persian summary into
// index.html, so search engines and link previews see real content without running the game.
function companySeo() {
return {
name: 'company-seo',
transformIndexHtml: (html) =>
html
.replace('<!--seo-head-->', `${seoHead()}\n ${seoStructuredData()}`)
.replace('<!--seo-summary-->', seoSummary()),
};
}
export default defineConfig({
// Relative assets also load under a GitHub Pages repository path.
base: './',
resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } },
plugins: [react(), companySeo(), offlineServiceWorker()],
// Top-level await loads the building and imagery files before the world is built.
build: { target: 'es2022' },
server: {
host: true,
allowedHosts: ['robotmarkets.ir', 'www.robotmarkets.ir'],
},
});