1
0
Fork 0
tsgames/build/html.ts

123 lines
4.7 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
}
export async function buildHTML(game: string, { production = false, portable = false, mobile = false }: Args = {}) {
const html = await Bun.file(path.resolve(import.meta.dir, 'assets', '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,
});
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>`)
.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);
}
}