- Introduce cache busting (to circumvent aggressive caching on iOS - but ideal in other contexts as well)

- Change the build process to allow cache busting
- Optimisations to the build process
- Several improvements of UI geared towards mobile experience
-
This commit is contained in:
Josako
2025-09-25 17:28:01 +02:00
parent cc47ce2d32
commit 16ce59ae98
32 changed files with 1538 additions and 899 deletions

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Chat Client Entry</title>
</head>
<body>
<!-- Minimal HTML entry used solely for Parcel to produce hashed JS/CSS bundles. -->
<script type="module" src="../js/chat-client.js"></script>
</body>
</html>

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Main Entry</title>
</head>
<body>
<!-- Minimal HTML entry used solely for Parcel to produce hashed JS/CSS bundles. -->
<script type="module" src="../js/main.js"></script>
</body>
</html>

View File

@@ -62,6 +62,12 @@ http {
alias /etc/nginx/static/;
}
# Long-term caching for hashed assets built by Parcel
location ^~ /static/dist/ {
alias /etc/nginx/static/dist/;
add_header Cache-Control "public, max-age=31536000, immutable" always;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html

1357
nginx/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -38,14 +38,25 @@
"scripts": {
"prebuild": "mkdir -p static/dist && npm run sync-assets",
"sync-assets": "rsync -av ../eveai_app/static/assets/ static/assets/ && rsync -av ../eveai_chat_client/static/assets/ static/assets/",
"build": "npm run prebuild && npm run build:main && npm run build:chat",
"build:main": "parcel build frontend_src/js/main.js --dist-dir static/dist --public-url /static/dist/ --no-source-maps",
"build:chat": "parcel build frontend_src/js/chat-client.js --dist-dir static/dist --public-url /static/dist/ --no-source-maps",
"build": "npm run prebuild && npm run build:main && npm run build:chat && npm run postbuild",
"build:main": "NODE_ENV=production parcel build frontend_src/entries/main.html",
"build:chat": "NODE_ENV=production parcel build frontend_src/entries/chat-client.html",
"predev": "mkdir -p static/dist && npm run sync-assets",
"dev": "npm run predev && parcel frontend_src/js/main.js --dist-dir static/dist --public-url /static/dist/ & parcel frontend_src/js/chat-client.js --dist-dir static/dist --public-url /static/dist/",
"prewatch": "mkdir -p static/dist && npm run sync-assets",
"watch": "npm run prewatch && parcel watch frontend_src/js/main.js --dist-dir static/dist --public-url /static/dist/ & parcel watch frontend_src/js/chat-client.js --dist-dir static/dist --public-url /static/dist/",
"clean": "rm -rf static/dist/* static/assets .parcel-cache"
"clean": "rm -rf static/dist/* static/assets .parcel-cache",
"postbuild": "node scripts/generate-manifest.mjs"
},
"targets": {
"default": {
"context": "browser",
"distDir": "static/dist",
"publicUrl": "/static/dist/",
"outputFormat": "esmodule",
"isLibrary": false,
"optimize": true,
"sourceMap": false
}
}
}

View File

@@ -0,0 +1,55 @@
import { promises as fs } from 'node:fs';
import path from 'node:path';
const DIST_DIR = path.resolve(process.cwd(), 'static', 'dist');
const OUT_FILE = path.join(DIST_DIR, 'manifest.json');
const LOGICAL_ENTRIES = [
{ key: 'dist/chat-client.js', base: 'chat-client', exts: ['.js'] },
{ key: 'dist/chat-client.css', base: 'chat-client', exts: ['.css'] },
// Optionally include main entries if referenced via templates in the future
{ key: 'dist/main.js', base: 'main', exts: ['.js'] },
{ key: 'dist/main.css', base: 'main', exts: ['.css'] },
];
function isHashedFile(file, base, ext) {
return file.startsWith(base + '.') && file.endsWith(ext) && file !== base + ext;
}
async function generate() {
const files = await fs.readdir(DIST_DIR);
const manifest = {};
for (const entry of LOGICAL_ENTRIES) {
const matches = [];
for (const ext of entry.exts) {
for (const f of files) {
if (isHashedFile(f, entry.base, ext)) {
matches.push(`dist/${f}`);
}
}
}
if (matches.length === 1) {
manifest[entry.key] = matches[0];
} else if (matches.length > 1) {
const stats = await Promise.all(
matches.map(async m => ({
file: m,
mtime: (await fs.stat(path.join(DIST_DIR, path.basename(m)))).mtimeMs,
}))
);
stats.sort((a, b) => b.mtime - a.mtime);
manifest[entry.key] = stats[0].file;
} else {
manifest[entry.key] = entry.key; // fallback zodat pagina niet breekt
}
}
await fs.writeFile(OUT_FILE, JSON.stringify(manifest, null, 2), 'utf8');
console.log(`[manifest] written: ${OUT_FILE}`);
}
generate().catch(err => {
console.error('[manifest] generation failed:', err);
process.exit(1);
});