The desktop main process moved to ESM with code splitting a while ago; the
server stayed on a single 13.6 MB CJS bundle, which V8 parsed in full at every
boot. That also made the backend lazy-loading pattern a no-op here: a dynamic
import() only moved code around inside one file that was loaded anyway.
Build src/main.ts with { format: "esm" }, and rename the entry everywhere that
launches it: the two Docker entrypoint scripts (all five Dockerfiles go through
them), the trilium.sh written into the Linux tarball, start-prod-no-dir (also
the Playwright webServer), the sync e2e harness, and the Nix wrapper. The image
worker stays CJS -- the pool spawns it by its .cjs path.
Measured on a demo-sized document, old and new bundles built from the same
source and benchmarked interleaved with a pristine data dir per boot (n=6):
loaded at startup 13.56 MB (1 file) -> 6.64 MB (98 of 313 files)
spawn -> first response 439 ms -> 328 ms
heap used after GC 56.9 MB -> 32.4 MB
RSS improved too but was too noisy on the test machine to quote. The largest
chunks that no longer load at boot are pdfjs (1.61 MB), the Anthropic agent SDK
(1.35 MB), express-openid-connect, undici and the share theme.
flake.nix also pointed the desktop wrapper at main.cjs, left behind by the
desktop ESM migration; it would have failed to launch. Fixed alongside, with
the stale main.cjs mentions in nearby comments.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds a server-only e2e spec that exercises the real sync protocol over HTTP
between two spawned production-bundle instances:
- seeds a source through ETAPI (916 notes across parents, labels, plus one
2.5 MB note), sized so the pull crosses the server's 1000-row fetch batches
many times and includes a record larger than the response byte cap — the
regressions most likely to be reintroduced by future sync work
- runs a full initial sync into a fresh target (setup/sync-from-server, HMAC
login, batching) and polls /api/sync/stats until converged
- verifies spot notes and the large note byte-for-byte via ETAPI on both sides
(the synced password/ETAPI credentials themselves are part of the test)
- checks incremental sync in both directions (a target restart is used as a
deterministic, auth-free sync trigger)
- finally compares both databases offline: entity counts and blob byte sums
(CAST(content AS BLOB) — locally-saved text notes are TEXT where LENGTH()
counts characters, synced content is a Buffer where it counts bytes)
The SyncInstance helper (spawn/init/password/ETAPI/sync plumbing) is reusable
for future sync e2e tests such as conflict or protected-note scenarios.
Runs in ~25 s; skipped in Docker mode (spawns local server processes).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>