125 lines
4.9 KiB
TypeScript
125 lines
4.9 KiB
TypeScript
import path from 'path';
|
|
import { minify } from 'html-minifier';
|
|
import UglifyJS from 'uglify-js';
|
|
|
|
import wasmPlugin from './wasmPlugin';
|
|
import imagePlugin from './imagePlugin';
|
|
import fontPlugin from './fontPlugin';
|
|
import audioPlugin from './audioPlugin';
|
|
import filePlugin from './filePlugin';
|
|
|
|
import { getGames } from './isGame';
|
|
|
|
interface Args {
|
|
production?: boolean;
|
|
portable?: boolean;
|
|
mobile?: boolean;
|
|
itch?: boolean;
|
|
}
|
|
export async function buildHTML(game: string, { production = false, portable = false, mobile = false, itch = false }: Args = {}) {
|
|
const html = await Bun.file(path.resolve(import.meta.dir, 'assets', itch ? 'index-itch.html' : 'index.html')).text();
|
|
const bundle = await Bun.build({
|
|
outdir: '/tmp',
|
|
entrypoints: [path.resolve(import.meta.dir, '..', 'src', 'index.ts')],
|
|
sourcemap: production ? 'none' : 'inline',
|
|
define: {
|
|
global: 'window',
|
|
GAME: `"${game}"`,
|
|
GAMES: JSON.stringify(await getGames()),
|
|
},
|
|
plugins: [
|
|
imagePlugin,
|
|
audioPlugin,
|
|
fontPlugin,
|
|
wasmPlugin({ production, portable }),
|
|
filePlugin,
|
|
]
|
|
});
|
|
|
|
if (bundle.success) {
|
|
const scriptFile = bundle.outputs.find(a => a.kind === 'entry-point' && a.path.endsWith('.js'));
|
|
if (!scriptFile) {
|
|
console.error('No entry point found:', bundle.outputs);
|
|
return;
|
|
}
|
|
const iconFile = Bun.file(path.resolve(import.meta.dir, '..', 'src', 'games', game, 'assets', 'favicon.ico'));
|
|
let icon = '';
|
|
if (await iconFile.exists()) {
|
|
icon = `<link rel="icon" href="data:;base64,${Buffer.from(await iconFile.arrayBuffer()).toString('base64')}" />`;
|
|
}
|
|
let style = '';
|
|
let styleFile = bundle.outputs.find(a => a.kind === 'asset' && a.path.endsWith('.css'));
|
|
if (styleFile) {
|
|
style = await styleFile.text();
|
|
}
|
|
const title = game[0].toUpperCase() + game.slice(1).toLowerCase();
|
|
let pwaIconFile = Bun.file(path.resolve(import.meta.dir, '..', 'src', 'games', game, 'assets', 'pwa_icon.png'));
|
|
if (!await pwaIconFile.exists()) {
|
|
pwaIconFile = Bun.file(path.resolve(import.meta.dir, 'assets', 'pwa_icon.png'));
|
|
}
|
|
let manifest = '';
|
|
if (production) {
|
|
const pwaIcon = `data:;base64,${Buffer.from(await pwaIconFile.arrayBuffer()).toString('base64')}`;
|
|
const publishURL = process.env.PUBLISH_URL ? `${process.env.PUBLISH_URL}${game}` : '.';
|
|
const manifestJSON = JSON.stringify({
|
|
name: title,
|
|
short_name: title,
|
|
start_url: publishURL,
|
|
id: `/${game}`,
|
|
display_override: ["window-controls-overlay"],
|
|
display: "fullscreen",
|
|
background_color: "#ffffff",
|
|
theme_color: "#000000",
|
|
icons: [{
|
|
src: pwaIcon,
|
|
sizes: '192x192',
|
|
type: 'image/png'
|
|
}]
|
|
});
|
|
manifest = `<link rel="manifest" href="data:;base64,${Buffer.from(manifestJSON).toString('base64')}" />`;
|
|
}
|
|
let script = await scriptFile.text();
|
|
const inits = new Set<string>();
|
|
script = script.replace(/var (init_[^ ]+) = __esm\(\(\)/g, (_, $1) => {
|
|
inits.add($1);
|
|
return `var ${$1} = __esm(async ()`;
|
|
});
|
|
|
|
for (const init of inits) {
|
|
script = script.replaceAll(`${init}()`, `await ${init}()`);
|
|
}
|
|
|
|
script = script.replaceAll('await Promise.resolve().then(() =>', '(');
|
|
let scriptPrefix = '';
|
|
if (production) {
|
|
const minifyResult = UglifyJS.minify(script, {
|
|
module: true,
|
|
toplevel: true,
|
|
});
|
|
if (minifyResult.error) {
|
|
console.warn(`Minify error: ${minifyResult.error}`);
|
|
} else {
|
|
script = minifyResult.code;
|
|
}
|
|
} else if (mobile) {
|
|
const eruda = await Bun.file(path.resolve(import.meta.dir, '..', 'node_modules', 'eruda', 'eruda.js')).text();
|
|
scriptPrefix = `<script>${eruda};\neruda.init();</script>`;
|
|
}
|
|
|
|
const resultHTML = html
|
|
.replace('<!--$SCRIPT$-->', () => `${scriptPrefix}<script type="module">${script}</script>`) // to avoid $& being replaced
|
|
.replace('<!--$TITLE$-->', () => title)
|
|
.replace('<!--$ICON$-->', () => icon)
|
|
.replace('<!--$MANIFEST$-->', () => manifest)
|
|
.replace('/*$STYLE$*/', () => style);
|
|
|
|
return minify(resultHTML, {
|
|
collapseWhitespace: production,
|
|
decodeEntities: true,
|
|
minifyCSS: production,
|
|
minifyJS: production,
|
|
});
|
|
} else {
|
|
console.error('Failed: ', !bundle.success, bundle);
|
|
}
|
|
} |