testDeployedChunksArriveAsDifferences

tcmts.cli.test.e2e.prod.testDeployedChunksArriveAsDifferences · #ybbvi4h5 · term

After a deploy, a browser that has the app's previous chunk gets the new one as the difference from it (Compression Dictionary Transport): Chrome asks with the old chunk's hash, and the server sends dcb, a few hundred bytes for a chunk of thousands.

import { CHROME, withPage } from "#codebase/tcmts/cli/test/browser";
import { plainEnv, tcmBin, withChildren } from "#codebase/tcmts/cli/test/e2e/harness";
import { setUp, waitFor } from "#codebase/tcmts/cli/test/e2e/prod";
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import type { ChildProcess } from "node:child_process";
import fs from "node:fs";
import path from "node:path";

export const testDeployedChunksArriveAsDifferences = () =>
  withChildren(async (keep) => {
    if (!CHROME) return console.log("skipped: no Chrome");
    const { dir, tcm, scratch } = setUp();
    // browser code with some bulk to it, so the difference is worth sending
    const app = (version: string) =>
      `// tcm: namespace app\n` +
      `export const words = (): string[] => Array.from({ length: 200 }, (_, i) => "word" + i + ["alpha", "beta", "gamma"][i % 3]);\n` +
      `export const describe = (n: number): string => n > 100 ? "many " + words().slice(0, 3).join(", ") : "few";\n` +
      `export function main() { document.querySelector("#app")!.textContent = "${version} " + describe(words().length); }\n`;
    scratch(app("v1"));
    tcm("add");
    tcm("build", "app.main");
    const child: ChildProcess = keep(
      spawn(process.execPath, [tcmBin(), "start", "app.main", "--port", "0"], {
        cwd: dir,
        env: plainEnv(),
      }),
    );
    let out = "";
    child.stdout!.setEncoding("utf8").on("data", (d: string) => (out += d));
    child.stderr!.setEncoding("utf8").on("data", (d: string) => (out += d));
    await waitFor(() => /url\s+http:\/\/\S+/.test(out));
    const url = /url\s+(http:\/\/\S+)/.exec(out)![1]!;
    await withPage(url, async (p) => {
      assert.match(
        await p.waitFor<string>(`document.querySelector("#app")?.textContent ?? ""`, (t) =>
          t.startsWith("v1"),
        ),
        /^v1 many/,
      );
      scratch(app("v2"));
      tcm("update");
      await waitFor(() => /deployed app\.main/.test(out));
      await p.evaluate("location.reload()");
      assert.match(
        await p.waitFor<string>(`document.querySelector("#app")?.textContent ?? ""`, (t) =>
          t.startsWith("v2"),
        ),
        /^v2 many/,
      );
      assert.deepEqual(p.errors, []);
      // what Chrome received for the app's new chunk
      const sizes = await p.evaluate<{ name: string; encoded: number; decoded: number }[]>(
        `performance.getEntriesByType("resource").filter((e) => e.name.includes("/_app/assets/app-")).map((e) => ({ name: e.name, encoded: e.encodedBodySize, decoded: e.decodedBodySize }))`,
      );
      const deltas = path.join(dir, ".tcm", "apps", "deltas");
      const made = fs.existsSync(deltas)
        ? fs.readdirSync(deltas).filter((f) => f.endsWith(".dcb"))
        : [];
      assert.equal(made.length, 1, `the server made the difference once: ${made.join(", ")}`);
      const [newer] = sizes.slice(-1);
      assert.ok(
        newer &&
          newer.encoded > 0 &&
          newer.encoded === fs.statSync(path.join(deltas, made[0]!)).size,
        JSON.stringify(sizes),
      );
      const br = fs.statSync(
        path.join(
          dir,
          ".tcm",
          "apps",
          "assets",
          `${path.basename(new URL(newer.name).pathname)}.br`,
        ),
      ).size;
      assert.ok(
        newer.encoded < br / 2,
        `the difference (${newer.encoded} bytes) is much less than the chunk (${br} bytes, compressed)`,
      );
    });
  })

uses