1
0
Fork 0

Improve local builds

This commit is contained in:
Pabloader 2026-04-10 13:34:45 +00:00
parent 2b7a73e818
commit a307d4d3eb
8 changed files with 73 additions and 102 deletions

View File

@ -1,58 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0">
<title><!--$TITLE$--></title>
<style>
* {
box-sizing: border-box;
}
html,
body {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
overflow: hidden;
font-family: sans-serif;
}
/*$STYLE$*/
</style>
<!--$ICON$-->
</head>
<body>
<!--$SCRIPT$-->
<script>
function connect() {
const params = new URLSearchParams(location.search);
const game = params.get('game') || '';
const ws = new WebSocket('css-ws?game=' + encodeURIComponent(game));
const onMessage = (e) => {
try {
const { type, css } = JSON.parse(e.data);
if (type === 'css') {
const styleTag = document.querySelector('style');
if (styleTag) styleTag.textContent = css;
}
} catch { }
};
const onClose = () => {
ws.removeEventListener('message', onMessage);
ws.removeEventListener('close', onClose);
setTimeout(connect, 500);
};
ws.addEventListener('message', onMessage);
ws.addEventListener('close', onClose);
}
connect();
</script>
</body>
</html>

View File

@ -3,40 +3,16 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0">
<title><!--$TITLE$--></title>
<style>
* {
box-sizing: border-box;
}
html,
body {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
overflow: hidden;
font-family: sans-serif;
}
/*$STYLE$*/
</style>
<!--$MANIFEST$-->
<!--$STYLE$-->
<!--$MANIFEST$-->
<!--$ICON$-->
</head>
<body>
<!--$SCRIPT$-->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => console.log('SW registered'))
.catch(err => console.error('SW registration failed:', err));
});
}
</script>
</body>
</html>

View File

@ -13,6 +13,7 @@ const publish = process.env.PUBLISH_LOCATION;
const args = process.argv.slice(2);
const local = args.includes('--local');
const exported = args.includes('--exported');
let game = args.find(a => !a.startsWith('-')) ?? '';
while (!await isGame(game)) {
@ -22,7 +23,7 @@ while (!await isGame(game)) {
});
}
console.log(`Building ${game}...`);
const html = await buildHTML(game, { production: true, local });
const html = await buildHTML(game, { production: true, local, exported });
if (!html) {
process.exit(1);

View File

@ -33,13 +33,53 @@ interface Args {
production?: boolean;
mobile?: boolean;
local?: boolean;
exported?: boolean;
}
const SW_SCRIPT = `<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => console.log('SW registered'))
.catch(err => console.error('SW registration failed:', err));
});
}
</script>`;
const CSS_RELOAD_SCRIPT = `<script>
function connect() {
const params = new URLSearchParams(location.search);
const game = params.get('game') || '';
if (!game) return;
const ws = new WebSocket('css-ws?game=' + encodeURIComponent(game));
const onMessage = (e) => {
try {
const { type, css } = JSON.parse(e.data);
if (type === 'css') {
const styleTag = document.querySelector('style');
if (styleTag) styleTag.textContent = css;
}
} catch { }
};
const onClose = () => {
ws.removeEventListener('message', onMessage);
ws.removeEventListener('close', onClose);
setTimeout(connect, 500);
};
ws.addEventListener('message', onMessage);
ws.addEventListener('close', onClose);
}
connect();
</script>`;
async function buildBundle(game: string, production: boolean) {
const srcDir = path.resolve(import.meta.dir, '..', 'src');
return Bun.build({
outdir: '/tmp',
entrypoints: [path.resolve(srcDir, 'index.ts')],
entrypoints: [
path.resolve(srcDir, 'index.ts'),
path.resolve(srcDir, 'common', 'assets', 'global.css'),
],
sourcemap: production ? 'none' : 'inline',
define: {
global: 'window',
@ -56,13 +96,17 @@ async function buildBundle(game: string, production: boolean) {
});
}
export async function buildHTML(game: string, { production = false, mobile = false, local = false }: Args = {}) {
export async function buildHTML(game: string, {
production = false,
mobile = false,
local = false,
}: Args = {}) {
const assetsDir = path.resolve(import.meta.dir, 'assets');
const srcDir = path.resolve(import.meta.dir, '..', 'src');
const gameDir = path.resolve(srcDir, 'games', game);
const gameAssetsDir = path.resolve(gameDir, 'assets');
const html = await Bun.file(path.resolve(assetsDir, local ? 'index-local.html' : 'index.html')).text();
const html = await Bun.file(path.resolve(assetsDir, 'index.html')).text();
const bundle = await buildBundle(game, production);
if (bundle.success) {
@ -150,6 +194,12 @@ export async function buildHTML(game: string, { production = false, mobile = fal
const eruda = await Bun.file(path.resolve(import.meta.dir, '..', 'node_modules', 'eruda', 'eruda.js')).text();
scriptPrefix = `<script>${eruda};\neruda.init();</script>`;
}
if (!local) {
scriptPrefix += SW_SCRIPT;
}
if (local && !production) {
scriptPrefix += CSS_RELOAD_SCRIPT;
}
// function to avoid $& being replaced
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_string_as_the_replacement
@ -158,7 +208,7 @@ export async function buildHTML(game: string, { production = false, mobile = fal
.replace('<!--$TITLE$-->', () => title)
.replace('<!--$ICON$-->', () => icon)
.replace('<!--$MANIFEST$-->', () => manifest)
.replace('/*$STYLE$*/', () => style);
.replace('<!--$STYLE$-->', () => `<style>${style}</style>`);
return minify(resultHTML, {
collapseWhitespace: production,

View File

@ -27,18 +27,20 @@
scrollbar-color: var(--bg-active) transparent;
}
html {
padding: 0;
html,
body {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
overflow: hidden;
font-family: sans-serif;
}
body {
body {
background: var(--bg);
color: var(--text);
font-family: 'Georgia', serif;
font-size: 14px;
line-height: 1.5;
width: 100dvw;
height: 100dvh;
overflow: hidden;
}

View File

@ -38,7 +38,7 @@ export const ManageWorkersModal = ({ open, onClose }: Props) => {
const loading = useBool(false);
useEffect(() => {
if (!open || !user || !apiKey) return;
if (!open || !user?.worker_ids || !apiKey || !user) return;
let cancelled = false;
loading.setTrue();
@ -63,7 +63,7 @@ export const ManageWorkersModal = ({ open, onClose }: Props) => {
.finally(() => { if (!cancelled) loading.setFalse(); });
return () => { cancelled = true; };
}, [open, user?.worker_ids.join(',')]);
}, [open, user?.worker_ids?.join(',')]);
const setEdit = (id: string, patch: Partial<WorkerEdit>) => {
setWorkers(prev => prev.map(w =>
@ -110,7 +110,7 @@ export const ManageWorkersModal = ({ open, onClose }: Props) => {
};
const confirmDelete = async (id: string) => {
if (!apiKey) return;
if (!apiKey || !user?.worker_ids) return;
setWorkers(prev => prev.map(w =>
w.data.id === id ? { ...w, deleting: true, confirmDelete: false } : w
));

View File

@ -10,7 +10,7 @@ export const Navbar = () => {
<div class={styles.actions}>
<button onClick={leaderboard.setTrue}>Leaderboard</button>
<button onClick={options.setTrue}>Options</button>
{state.user && (
{state.user?.worker_ids && (
<button onClick={manageWorkers.setTrue}>Manage Workers</button>
)}
</div>

View File

@ -25,7 +25,7 @@ export interface UserData {
username: string;
kudos: number;
worker_count: number;
worker_ids: string[];
worker_ids?: string[];
records: {
contribution: { tokens: number };
fulfillment: { text: number };