The web build now writes a Brotli and a gzip copy of each compressible file, and the static handler serves the best one the browser accepts. The bundle was gzipped again for every request and Brotli was never offered; the main chunk is 122 KB with Brotli against 144 KB gzipped. index.html and every static file carry an ETag, and a matching If-None-Match gets a 304. The shell and the worker are revalidated on every load and were downloaded whole each time. Attachment downloads pass a plain byte Range to Stalwart and relay a 206, so a PDF viewer or a video element can read in pieces where the server allows it. On the reader's own device a blob is cached as immutable, since its id names its content. The upstream session and account-info caches drop entries past their age on a timer; they lost an entry only on sign-out or refusal, not when a session expired. The mock answers byte ranges.
80 lines
3.8 KiB
TypeScript
80 lines
3.8 KiB
TypeScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { mkdtempSync, writeFileSync, mkdirSync, utimesSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { brotliCompressSync, brotliDecompressSync, gunzipSync, gzipSync } from "node:zlib";
|
|
|
|
/*
|
|
* The bundle goes out compressed once, at build time, and anything revalidated
|
|
* can be answered with a 304.
|
|
*
|
|
* Before this the server gzipped the bundle again for every request that asked,
|
|
* never offered Brotli, and sent no validator for the shell -- so each reload's
|
|
* revalidation of index.html and sw.js downloaded them in full.
|
|
*/
|
|
const root = mkdtempSync(join(tmpdir(), "ihasmail-precompressed-"));
|
|
mkdirSync(join(root, "assets"));
|
|
const js = `console.log(${JSON.stringify("x".repeat(4000))});\n`;
|
|
writeFileSync(join(root, "assets", "app-a1b2c3.js"), js);
|
|
writeFileSync(join(root, "assets", "app-a1b2c3.js.br"), brotliCompressSync(js));
|
|
writeFileSync(join(root, "assets", "app-a1b2c3.js.gz"), gzipSync(js));
|
|
writeFileSync(join(root, "assets", "plain-d4e5f6.js"), js);
|
|
// A copy left over from an older build of the same name must not be served.
|
|
writeFileSync(join(root, "assets", "stale-000000.js"), js);
|
|
writeFileSync(join(root, "assets", "stale-000000.js.br"), brotliCompressSync("old"));
|
|
const old = new Date(Date.now() - 60_000);
|
|
utimesSync(join(root, "assets", "stale-000000.js.br"), old, old);
|
|
writeFileSync(join(root, "sw.js"), "/* worker */\n");
|
|
writeFileSync(join(root, "index.html"), "<!doctype html><title>t</title>");
|
|
|
|
process.env.STATIC_DIR = root;
|
|
process.env.STALWART_URL = "http://127.0.0.1:1";
|
|
const { createApp } = await import("./app.js");
|
|
const app = createApp();
|
|
|
|
const get = (path: string, headers: Record<string, string> = {}) => app.request(path, { headers });
|
|
|
|
test("Brotli is served where the browser takes it", async () => {
|
|
const res = await get("/assets/app-a1b2c3.js", { "accept-encoding": "gzip, deflate, br" });
|
|
assert.equal(res.status, 200);
|
|
assert.equal(res.headers.get("content-encoding"), "br");
|
|
assert.equal(res.headers.get("vary"), "Accept-Encoding");
|
|
assert.equal(res.headers.get("content-type"), "text/javascript; charset=utf-8");
|
|
assert.equal(brotliDecompressSync(Buffer.from(await res.arrayBuffer())).toString(), js);
|
|
});
|
|
|
|
test("gzip where Brotli is not accepted, and nothing where neither is", async () => {
|
|
const gz = await get("/assets/app-a1b2c3.js", { "accept-encoding": "gzip, br;q=0" });
|
|
assert.equal(gz.headers.get("content-encoding"), "gzip");
|
|
assert.equal(gunzipSync(Buffer.from(await gz.arrayBuffer())).toString(), js);
|
|
const plain = await get("/assets/app-a1b2c3.js");
|
|
assert.equal(plain.headers.get("content-encoding"), null);
|
|
assert.equal(await plain.text(), js);
|
|
});
|
|
|
|
test("a file without a copy is compressed as before", async () => {
|
|
const res = await get("/assets/plain-d4e5f6.js", { "accept-encoding": "gzip" });
|
|
assert.equal(res.headers.get("content-encoding"), "gzip");
|
|
assert.equal(gunzipSync(Buffer.from(await res.arrayBuffer())).toString(), js);
|
|
});
|
|
|
|
test("a copy older than its file is ignored", async () => {
|
|
const res = await get("/assets/stale-000000.js", { "accept-encoding": "br" });
|
|
assert.notEqual(res.headers.get("content-encoding"), "br");
|
|
});
|
|
|
|
test("the shell and the worker answer a revalidation with 304", async () => {
|
|
for (const path of ["/", "/sw.js"]) {
|
|
const first = await get(path);
|
|
const etag = first.headers.get("etag");
|
|
assert.ok(etag, `${path} carries a validator`);
|
|
await first.arrayBuffer();
|
|
const again = await get(path, { "if-none-match": etag! });
|
|
assert.equal(again.status, 304, `${path} is not sent again`);
|
|
assert.equal(await again.text(), "");
|
|
const changed = await get(path, { "if-none-match": `"something-else"` });
|
|
assert.equal(changed.status, 200);
|
|
}
|
|
});
|