perf+ui: gzip (api/static precompressed), immutable attachment cache, responsive dialogs/header, safe-area + dynamic theme-color, mobile tuning

This commit is contained in:
root
2026-10-16 23:34:36 +08:00
parent 3ba44aa2de
commit 275a0313e1
21 changed files with 145 additions and 95 deletions
+28 -5
View File
@@ -1,6 +1,7 @@
// Footprints 自建同步 + Web 服务(零依赖:node:http + node:sqlite)
import http from 'node:http';
import fs from 'node:fs';
import zlib from 'node:zlib';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { getDb } from './db.mjs';
@@ -26,6 +27,7 @@ const MIME = {
// 按用户惰性创建命令集(每个用户一个独立 SQLite 档案库)
const userCommands = new Map();
const gzipCache = new Map(); // 指纹资产 gzip 预压缩缓存
let demoUserId;
function commandsFor(userId) {
let c = userCommands.get(userId);
@@ -51,8 +53,16 @@ const server = http.createServer(async (req, res) => {
const url = new URL(req.url, 'http://localhost');
const route = url.pathname;
const send = (code, ok, data) => {
res.writeHead(code, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify(ok ? { ok: true, data } : { ok: false, error: data }));
let body = Buffer.from(JSON.stringify(ok ? { ok: true, data } : { ok: false, error: data }));
const headers = { 'Content-Type': 'application/json; charset=utf-8' };
// API JSON gzip:附件 base64 / 列表大负载体积降 70%+
if (body.length >= 1024 && /\bgzip\b/.test(String(req.headers['accept-encoding'] || ''))) {
body = zlib.gzipSync(body, { level: 6 });
headers['Content-Encoding'] = 'gzip';
headers.Vary = 'Accept-Encoding';
}
res.writeHead(code, headers);
res.end(body);
};
res.setHeader('Access-Control-Allow-Origin', '*');
@@ -198,7 +208,9 @@ const server = http.createServer(async (req, res) => {
if (!file) return send(404, false, '附件不存在');
res.writeHead(200, {
'Content-Type': file.mime || 'application/octet-stream',
'Content-Length': file.buf.length
'Content-Length': file.buf.length,
// 内容寻址 = 不可变:浏览器一年缓存(private:不进共享缓存,杜绝跨账号串缓存)
'Cache-Control': 'private, max-age=31536000, immutable'
});
return res.end(file.buf);
}
@@ -227,10 +239,21 @@ const server = http.createServer(async (req, res) => {
let p = route === '/' ? '/index.html' : decodeURIComponent(route);
const abs = path.join(WWW_DIR, path.normalize(p).replace(/^([.][.][\\/])+/, ''));
if (abs.startsWith(WWW_DIR) && fs.existsSync(abs) && fs.statSync(abs).isFile()) {
res.writeHead(200, {
const headers = {
'Content-Type': MIME[path.extname(abs).toLowerCase()] || 'application/octet-stream',
'Cache-Control': p.startsWith('/assets/') ? 'public, max-age=31536000, immutable' : 'no-cache'
});
};
// 指纹资产预压缩缓存(首次 gzip 后驻留内存,后续零 CPU)
if (p.startsWith('/assets/') && /\.(js|css|svg|json)$/.test(p) && /\bgzip\b/.test(String(req.headers['accept-encoding'] || ''))) {
let gz = gzipCache.get(abs);
if (!gz) { gz = zlib.gzipSync(fs.readFileSync(abs), { level: 6 }); gzipCache.set(abs, gz); }
headers['Content-Encoding'] = 'gzip';
headers['Content-Length'] = gz.length;
headers.Vary = 'Accept-Encoding';
res.writeHead(200, headers);
return res.end(gz);
}
res.writeHead(200, headers);
return res.end(fs.readFileSync(abs));
}
const indexHtml = path.join(WWW_DIR, 'index.html');