social-kit / test / module-budget.test.mjs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**
 * The module budget — and specifically, the tripwire on the one edit that can
 * silently make this kit an order of magnitude more expensive for nine apps.
 *
 * social-kit's shipped dist contains NO ClojureScript collection of any kind.
 * That is not a style note, it is a load-bearing property of the build: with
 * nothing reaching a persistent map, vector, set or seq, :advanced
 * dead-code-eliminates the whole cljs.core collection/seq/keyword/protocol
 * runtime, and what is left is small enough that `./join` — the
 * dependency-free chip every app pulls at first paint — is 1.5 KB gzipped over
 * a 1.2 KB shared base.
 *
 * The first line of code that uses a Clojure collection puts all of it back,
 * and shadow hoists it into :shared because more than one module reaches it,
 * so `./join` pays for it too. Measured on this tree during the idiomatic
 * rewrite of the common core, all figures gzip -9:
 *
 *   baseline                                         shared 1.25 KB   index 17.0 KB
 *   + one 7-key {:keyword …} literal in envelope     shared 20.3 KB   index 17.2 KB
 *   + map/filter/reduce over a JS array              shared 20.9 KB   index 18.4 KB
 *   + a persistent vector literal on top of that     shared 20.9 KB   index 18.4 KB
 *   + a keyword map literal on top of that           shared 20.9 KB   index 18.6 KB
 *
 * One cliff, not a gradient: +19 KB gzipped for the first one, ~240 bytes for
 * everything after. This is why the idiomatic rewrite of friends/presence/
 * envelope/pair/selfsync uses named boundary macros, small pure functions and
 * JavaScript containers rather than "Clojure data above a declared wire
 * boundary" — the suite-wide plan's default costs nothing in chat, where
 * cljs.core is already 43% of the bundle, and costs 19 KB at first paint in
 * every one of the nine apps here.
 *
 * The caps below are deliberately loose enough not to fail on ordinary
 * drift and far tighter than the cliff, so the only thing that trips them is
 * the thing they are for. If one fails, do not raise it without measuring
 * WHICH module grew and why — a genuine feature will show up in :index.
 */
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { gzipSync } from "node:zlib";

const gz = (name) =>
  gzipSync(readFileSync(new URL(`../dist/${name}`, import.meta.url)), { level: 9 }).length;

const BUDGET_GZ = {
  // the collection-runtime tripwire: ~1.25 KB today, ~20.3 KB the moment any
  // Clojure collection is reachable from more than one module
  "shared.js": 4 * 1024,
  // first paint in nine apps — join depends only on :shared
  "join.js": 4 * 1024,
  "index.js": 24 * 1024,
  "net.js": 8 * 1024,
  "selftest.js": 8 * 1024,
};

for (const [name, cap] of Object.entries(BUDGET_GZ)) {
  test(`dist/${name} stays inside its gzip budget`, () => {
    const size = gz(name);
    assert.ok(size <= cap, `dist/${name} is ${size} B gzipped, budget ${cap} B`);
  });
}

test("the shared base really is collection-runtime free", () => {
  // The direct assertion behind the budget: :shared carries the ESM preamble
  // and almost nothing else. cljs.core's collection runtime is ~19 KB gzipped
  // and cannot hide inside this.
  assert.ok(gz("shared.js") < 2 * 1024,
            `shared.js is ${gz("shared.js")} B gzipped — something pulled cljs.core's ` +
            "collection runtime into the shared base; see this file's header");
});

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/social-kit.git